Быстрая разработка приложений при помощи PyGTK

Автор: (C) Krishnakumar R.
Перевод: (C) Александр Куприн


В нашем конкурентном мире один из важнейших критериев разработки приложений -- скорость их создания. Этой цели можно достичь при помощи PyGTK, соединяющего устойчивость Python'а и необузданную мощь GTK. Эта статья является практическим руководством по созданию научного калькулятора при помощи пакета pygtk.

1. Что такое PyGTK ?

Итак, позвольте мне привести цитату, взятую из исходного текста PyGTK:

"Этот архив содержит модули, позволяющие использовать gtk в программах, написанных на Python'е. На текущий момент, он представляет из себя практически завершённый набор классов. Не смотря на низкий номер версии, код готов для использования и может применяться для написания комплексных программ умеренного уровня сложности."

- README, pygtk-0.6.4

2. Что мы будем делать?

Мы собираемся создать небольшой научный калькулятор используя pygtk. Я детально объясню каждый шаг создания программы, что позволит вам ближе познакомиться пакетом. Чтобы облегчить работу с материалом, в конце статьи я поместил ссылки на исходники.

3. Пакеты и базовые навыки, которыми вы должны обладать

python
Этот пакет входит в состав многих дистрибутивов. Мои объяснения основаны на Python 1.5.2, установленном на Linux-машине под управлением RedHat 6.2. Будет неплохо, если вы умеете программировать на python'е. Но даже если вы не умеете программировать -- не волнуйтесь! Просто следуйте инструкциям.
pygtk
  1. Последние версии пакета вы найдёте здесь:
    1. ftp://ftp.daa.com.au/pub/james/python
    2. ftp://ftp.gtk.org/pub/gtk/python/
    3. ftp://ftp.python.org/pub/contrib/Graphics
    Изложение предполагает версию pygtk-0.6.4.

4. Приступим

Изложение материала разделено на три этапа. На каждом приводятся исходный код и скриншоты.

5. Этап 1: создание окна

Во-первых, нам нужно создать окно. Окно, по своей сути, контейнер. Таблицы кнопок и прочие элементы располагаются в границах этого окна. Создайте новый файл stage1.py и с помощью текстового редактора добавьте следующие строки:



from gtk import *

win = GtkWindow()

def main():
        win.set_usize(300, 350)
        win.connect("destroy", mainquit)
        win.set_title("Scientific Calculator")
        win.show()
        mainloop()

main()

Первая строка предназначена для импортирования методов из модуля gtk. Это означает, что теперь мы можем использовать его функции (классы, типы и пр.).

Затем мы создаём объект класса GtkWindow и называем его win. После этого настраиваем размер созданного окна. Первый аргумент -- ширина, второй -- высота.

Далее создаётся ассоциативная связь между сигналом "delete" (удалить окно) и функцией mainquit. Это встроенная функция gtk, которая будет вызываться в том случае, если текущее приложение может быть закрыто. Не стоит волноваться об этом. Просто примите к сведению, что каждый раз, когда мы закрываем окно (может быть кликнув на кнопке с "крестиком" в его правом верхнем углу или как-то иначе) будет вызываться функция mainquit. Иными словами, закрывая окно, мы тем самым вынуждаем приложение-владельца прекращать свою работу.

Теперь настроим заголовок окна и вызываем метод show. Этот метод является неотъемлемой частью всех визуальных объектов. Мы всегда должны вызывать метод show после настройки параметров визуализации конкретного объекта. Только после этого изменения, внесённые в объект, становятся видны пользователю. Помните, что вы можете создать объект "логически", но до тех пор пока вы не вызовете его метод show, "физически" объект не будет виден.

mainloop тоже является встроенной функцией библиотеки gtk. Когда мы вызываем mainloop, то запущенное приложение ожидает в цикле события, которые должны произойти.1 В нашем случае окно появляется на экране и просто ждёт. Ожидание наших действий происходит в теле функции mainloop. Только после того, как мы удалим окно, приложение покинет этот цикл.

Сохраните файл. Выйдите из редактора и, запустив терминал, введите команду:

python stage1.py

Помните: чтобы увидеть результат, "иксы" должны быть запущены:).

Скриншот примера показан ниже.

stage1.png

6. Этап 2: создание таблицы и кнопок

Давайте приступим к созданию следующего файла, stage2.py. Запишите в него вот такой код:



from gtk import *

rows    =   9
cols    =   4


win     =   GtkWindow()
box     =   GtkVBox()
table   =   GtkTable(rows, cols, FALSE)
text    =   GtkText()
close   =   GtkButton("close")

button_strings  =   [   'hypot(' , 'e' , ',' , 'clear' , 'log(' , 'log10(' , 'pow(' ,
                        'pi' , 'sinh(' , 'cosh(' , 'tanh(' , 'sqrt(' ,
                        'asin(' , 'acos(' , 'atan(' , '(' ,
                        'sin(' , 'cos(' , 'tan(' , ')' ,
                        '7' , '8' ,'9' , '/' , '4' , '5' , '6' , '*' , '1' , '2' , '3' ,
                        '-' , '0' , '.' , '=' , '+' ]

button  =   map(lambda i:GtkButton(button_strings[i]), range(rows*cols))


def main():
        win.set_usize(300, 350)
        win.connect("destroy", mainquit)
        win.set_title("Scientific Calculator")

        win.add(box)
        box.show()

        text.set_editable(FALSE)
        text.set_usize(300,1)
        text.show()
        text.insert_defaults(" ")
        box.pack_start(text)

        table.set_row_spacings(5)
        table.set_col_spacings(5)
        table.set_border_width(0)

        box.pack_start(table)
        table.show()

        for i in range(rows*cols) :
              y,x = divmod(i, cols)
              table.attach(button[i], x,x+1, y,y+1)
              button[i].show()

        close.show()
        box.pack_start(close)

        win.show()
        mainloop()

main()

Переменные rows и cols используются соответственно для хранения количества строк и столбцов с кнопками. Создаются четыре новых объекта: table, box, text и button (элементы table, box, text box и button, соответственно). В качестве аргумента для инициализации объекта класса GtkButton используется надпись. И обратите внимание, что close -- это кнопка с надписью "closed".2

Массив button_strings используется для хранения надписей на кнопках. Символы и надписи, которые изображаются на вспомогательной клавиатуре калькулятора хранятся именно здесь. Функция map создаёт 36 кнопок (rows*cols = 9*4). Надписи для них берутся из массива button_strings. Получается, что i-я кнопка содержит надпись, взятую из i-го элемента массива button_strings. Диапазон i -- от 0 до 35 (rows*cols-1 = 9*4-1)

Далее... Мы вставляем объект box в окно. В свою очередь в него вставляется таблица, и уже в таблицу мы вставляем кнопки. Соответствующие методы show окна, таблицы и кнопок вызываются после того как они "логически" созданы.

Используя метод set_editable объекта text с параметром FALSE (фактически, этот элемент представляет из себя строку ввода), настраиваем его для использования в режиме "только-для-чтения". Это означает, что мы не сможем изменять его содержимое напрямую, при помощи клавиатуры или мышки. Метод set_usize настраивает размер объекта text, а insert_defaults задаёт пустую строку как значение используемое по умолчанию. По окончанию всех настроек он внедряется в объект box (см. box.pack_start(text)).

После этого в объект box вставляется таблица, в которой будут содержаться кнопки. Настройка атрибутов таблицы элементарна -- в цикле вставляются девять строк по четыре кнопки в каждой. Для получения координат расположения каждой кнопки, выполняем операцию целочисленного деления значения переменной i на cols -- y,x = divmod(i, cols), сохраняя частное в переменной y, а остаток в x.

Последней вставляем кнопку close. Помните, что метод pack_start располагает вставляемый объект на первое свободное место в элементе box. 3

Сохраните файл и выполните команду

python stage2.py

Результат наших трудов перед вами.

stage2.png

7. Этап 3: Вдохнём в калькулятор жизнь

Для того, чтобы "оживить" калькулятор, было добавлено несколько новых функций. Их принято называть backend'ом (никогда не мог придумать такого перевода для термина backend [как противоположность frontend'у], который бы меня устроил. В жизни мы обычно говорим что-то вроде "движок" [как противоположность "морде" программы] -- прим. ред.). Строки, содержащие реализацию backend'а добавлены в scical.py, и мы с вами подходим к заключительному этапу. Скрипт scical.py содержит всё, что нужно для работы калькулятора. Впрочем довольно "предвыборных" речей, обратимся к исходнику:



from gtk import *
from math import *

toeval      =   ' '
rows        =   9
cols        =   4

win         =   GtkWindow()
box         =   GtkVBox()
table       =   GtkTable(rows, cols, FALSE)
text        =   GtkText()
close       =   GtkButton("close")

button_strings  =   [   'hypot(' , 'e' , ',' , 'clear' , 'log(' , 'log10(' ,
                        'pow(' , 'pi' ,
                        'sinh(' , 'cosh(' , 'tanh(' , 'sqrt(' ,
                        'asin(' , 'acos(' , 'atan(' , '(' ,
                        'sin(' , 'cos(' , 'tan(' , ')' ,
                        '7' , '8' , '9' , '/' , '4' , '5' , '6' , '*' ,
                        '1' , '2' , '3' , '-' ,  '0' , '.' , '=' , '+'  ]

button      =   map(lambda i:GtkButton(button_strings[i]), range(rows*cols))

def myeval(*args):
        global toeval
        try   :
                b   =   str(eval(toeval))
        except:
                b   =   "error"
                toeval  =   ''
        else  : toeval  =   b
        text.backward_delete(text.get_point())
        text.insert_defaults(b)



def mydel(*args):
        global toeval
        text.backward_delete(text.get_point())
        toeval  =   ''

def calcclose(*args):
        global toeval
        myeval()
        win.destroy()

def print_string(args,i):
        global toeval
        text.backward_delete(text.get_point())
        text.backward_delete(len(toeval))
        toeval = toeval + button_strings[i]
        text.insert_defaults(toeval)


def main():
        win.set_usize(300, 350)
        win.connect("destroy", mainquit)
        win.set_title("Scientific Calculator: scical (C) 2002 Krishnakumar.R, Share Under GPL.")

        win.add(box)
        box.show()

        text.set_editable(FALSE)
        text.set_usize(300,1)
        text.show()
        text.insert_defaults(" ")
        box.pack_start(text)

        table.set_row_spacings(5)
        table.set_col_spacings(5)
        table.set_border_width(0)
        box.pack_start(table)
        table.show()

        for i in range(rows*cols) :
              if i==(rows*cols-2) : button[i].connect("clicked",myeval)
              elif  (i==(cols-1)) : button[i].connect("clicked",mydel)
              else                : button[i].connect("clicked",print_string,i)
              y,x = divmod(i, 4)
              table.attach(button[i], x,x+1, y,y+1)
              button[i].show()

        close.show()
        close.connect("clicked",calcclose)
        box.pack_start(close)

        win.show()
        mainloop()

main()

Добавлена новая переменная toeval. Она хранит строку, значение которой интерпретируется и которая хранит результаты этой операции (имеется ввиду интерпретация:). Её значение отображается в объекте text в верхней части окна. Расчёт производится после того, как нажата кнопка "=". Это событие приводит к тому, что вызывается пользовательская функция myeval. Содержимое строки интерпретируется, используя встроенную функцию python'а eval и результат показывается в объекте text. Если вычисление произвести невозможно, то в качестве результата выводится строка 'error'. Для этого используется механизм исключений (try...except).

Нажатие любой кнопки (используя мышку), за исключением кнопок с надписью 'closed', 'clear' или '=', приводит к тому, что вызывается функция print_string. Эта функция сперва очищает элемент text box, а затем добавляет к переменной toeval строку, которая связана с нажатой кнопкой и отображает новое значение toeval на экране.

Если мы нажмём кнопку close, то будет вызвана функция calcclose, которая уничтожит окно. Если же нажмём кнопку clear, то функция mydel очистит элемент text box. В основной функции (main), в цикле создания и добавления кнопок можно увидеть три новых выражения. Они нужны для того, чтобы настроить связи соответствующих кнопок с вызываемыми функциями. Таким образом к кнопке '=' присоединяется функция myeval, к кнопке 'clear' функция mydel и т.д.

Вот и всё: научный калькулятор готов к работе. Просто наберите в терминале 'python scical.py' и вы получите работающий инструмент для расчётов.

Скриншот окончательного варианта программы представлен ниже.

scical.png

8. В заключение

Файлы с исходным кодом, рассмотренные выше, можно найти по этим ссылкам:

  1. stage1.py
  2. stage2.py
  3. scical.py

Все они имеют расширение .txt. Удалите его и можете запускать. Например, нужно переименовать stage1.py.txt в stage1.py перед запуском4.

Несколько примеров по использованию pygtk находятся в каталоге, который расположен там же, где и документация на пакет. В RedHat 6.2 их можете найти в /usr/doc/pygtk-0.6.4/examples/. Запустите примеры, изучите их код -- это поможет вам при создании ваших собственных приложений на pygtk.

Счастливо. Удачной компиляции (и, в случае с Python'ом, интерпретации)!


Krishnakumar R.

Кришнакумар -- студент последнего курса B.Tech в Govt. Engg. College Thrissur, Kerala, Индия. Его путешествие в земли Операционных Систем началось с программирования модулей для Linux. Он создал операционную систему GROS, основная цель которой -- выполнение функции маршрутизатора. (Детали вы можете найти на его домашней странице: www.askus.way.to ) Другие его интересы -- сетевые драйвера, драйвера устройств, портирование компиляторов и встроенные системы (Compiler Porting and Embedded systems).

1 Следует заметить, что события, ожидаемые в mainloop могут быть как пользовательскими (т.е. определённые пользователем) так и системными (например, перемещение окна, его минимизация и пр.)
2 Далее по тексту, как правило, упоминания об используемых объектах будут основываться на их именах. Например, box -- экземпляр класса GtkVBox; table, таблица -- GtkTable. Но возможны и исключения -- например, для упрощения изложения экземпляр класса GtkWindow (win) будет называться окном. Прошу прощения за такую дотошность, но одно дело читать мануал по программированию и другое его переводить, -- хочется быть уверенным в том, что перевод предельно ясен, особенно для новичков в таком запутанном деле, как программирование.
3 Имея привычку по возможности проверять в процессе перевода всё что излагает автор, хочу заметить, что вопрос о визуальной компоновке объектов рассмотрен в статье несколько "мутно". Чтобы не надоедать своими измышлениями на эту тему, советую закоментировать (символ #) строку win.set_usize(300, 350) и, запустив скрипт, поиграться размерами окна при помощи мышки.
4 На самом деле, если вы наберёте python stage1.py.txt, приложение запустится. Хотя будет лучше, если вы добавите в начало скрипта строку (расположение python'а в ваше системе можно выяснить вот так -- 'whereis python'):

#!/usr/bin/python

и сделаете файл исполняемым, -- тогда вам не придётся при использовании калькулятора каждый раз набирать 'python stage1.py'.


Copyright (C) 2002, Krishnakumar R..
Copying license http://www.linuxgazette.com/copying.html
Published in Issue 78 of Linux Gazette, May 2002

Вернуться на главную страницу