Создание апплета GNOME

       

Скелет апплета


Перво-наперво, выделю то, что необходимо для функционирования любого апплета, вне зависимости от его природы:

некий виджет-контейнер (например, HBox)

некий "полезный" виджет (у меня это будет Label)

всплывающая подсказка

некое действие по левой кнопки мыши

контекстное меню

возможность запуска как отдельного приложения (для отладки)

регистрация в GNOME как апплета к панели

Займемся реализацией:

import sys import gtk import gtk.gdk import gnome.ui import gnomeapplet

class GnomeAppletSkeleton(gnomeapplet.Applet): """Simple applet skeleton"""

def __init__(self, applet): """Create applet"""

self.applet = applet self.__init_core_widgets() self.init_additional_widgets() self.init_ppmenu() self.__connect_events() self.applet.connect("destroy", self._cleanup) self.after_init() self.applet.show_all()



Прежде чем приступить к пояснениям, скажу о конвенции насчет имен методов. Если имя метод начинается с двух подчеркиваний, то перегружать (переопределять) такой метод нежелательно. Если же имя метода начинается с буквы, то такой метод можно практически безболезненно перегружать. Но все же, если Вы будете писать свой апплет, то все же гляньте код соответствующего метода GnomeAppletSkeleton прежде чем перегружать его.

Итак, первым делом инициализирую ключевые виджеты, без которых не обойдется ни один апплет:

def __init_core_widgets(self): """Create internal widgets""" self.tooltips = gtk.Tooltips() self.hbox = gtk.HBox() self.ev_box = gtk.EventBox() self.applet.add(self.hbox) self.hbox.add(self.ev_box)

Поскольку апплет - безоконный виджет (у него нет окна), то для того, чтобы была возможность реагировать на события, я помещаю EventBox в него. А уж все дополнительные виджеты (в моем случае это будет только Label) добавляются к ev_box.

def init_additional_widgets(self): """Create additional widgets""" self.label = gtk.Label("Dummy") self.ev_box.add(self.label)


Далее, указываю необходимую информацию для контекстного меню (popup menu):

def init_ppmenu(self): """Create popup menu""" self.ppmenu_xml = """ <popup name="button3">

<menuitem name="About Item" verb="About" stockid="gtk-about" /> </popup> """

self.ppmenu_verbs = [ ("About", self.on_ppm_about), ]

Заметьте, что в XML-описании пункта меню "О программе" нет собственно названия пункта, а лишь его StockID. Это сделано по той простой причине, что пункт меню "О программе" стандартен для большинства приложений и в случае указания StockID Вы получаете:

стандартную иконку для данного пункта (причем, с изменением темы оформления GNOME эта иконка может меняться)

стандартное название пункта меню, причем автоматически переведенное на нужный язык

Каждый пункт меню имеет "глагол"-действие, который ставится ему в соответствие. self.ppmenu_verbs же задает соответствие между "глаголом"-действием и callback-функцией.

Следующий шаг по созданию апплета - "соединение" callback-функций и событий:

def __connect_events(self): """Connect applet's events to callbacks""" self.ev_box.connect("button-press-event", self.on_button) self.ev_box.connect("enter-notify-event", self.on_enter) self.button_actions = { 1: lambda: None, 2: lambda: None, 3: self._show_ppmenu, }

Еще раз отмечу, что апплет - безоконный виджет, поэтому все события генерирует ev_box. В данном случае, я соединил события "нажатие на кнопку" с callback-функцией self.on_button и событие "попадание курсора в область виджета" с callback-функцией self.on_enter. Здесь же при помощи словаря self.button_actions задал соответствие между кнопками мыши и функциями-действиями. Стоит заметить, что callback-функции, соединенные с событиями, должны быть определенной сигнатуры (об этом чуть позже), а функции-действия не должны принимать ни один параметр.



Следующий по порядку вызов - это метод after_init. В скелете он пустой, предназначен специально для переопределения в потомках.

С этапами создания апплета вроде завершил, остались callback-функции… Я не буду пересказывать PyGTK reference, лишь перечислю типы callback-функций и их сигнатуры, которые встречаются у меня:

callback-функция на событие destroy апплета. Сигнатура function(event). Реализация - _cleanup

callback-функция на события ev_box. Сигнатура function(widget, event). Реализации - on_enter, on_button

callback-функция на пункт меню. Сигнатура function(event, data=None). Реализация - on_ppm_about

функция-действие (мое название) на нажатие одной из кнопок мыши. Сигнатура function(). Реализация - _show_ppmenu.

Содержимое callback-функции _cleanup не буду приводить - оно слишком тривиально (удаляется объект self.applet) для того, чтобы занимать место, а кому интересно - гляньте в полном исходном тексте апплета. Что касается остальных callback-функций, я их приведу и прокомментирую, поскольку они все же представляют интерес. def on_button(self, widget, event): """Action on pressing button in applet"""

if event.type == gtk.gdk.BUTTON_PRESS: self.button_actions[event.button]()

Callback-функция on_button вызывается при нажатии любой кнопки мыши внутри виджета. И внутри этой функции я, во-первых, убеждаюсь, что присоединили к правильному событию (нажатию на клавишу), а, во-вторых, вызываю нужную функцию-действие, выбирая (в event.button хранится номер кнопки, нажатие на которую и вызвало появление данного события) из ранее описанного словаря self.button_actions. Для кнопок 1 и 2 у меня пустые действия, для 3 - контекстное меню. Показ контекстного меню - специальный метод класса Applet - setup_menu. Первый аргумент - XML-описание меню, второй - "глаголы"-действия, третий - пользовательские данные (передаются третьим параметром в callback-функцию).

def _show_ppmenu(self): """Show popup menu""" self.applet.setup_menu(self.ppmenu_xml, self.ppmenu_verbs, None)



Что касается события "попадание курсора в область виджета", то на него я реагировать буду так: показывать какую-нибудь простенькую подсказку, ради разнообразия сделав ее динамической.

def on_enter(self, widget, event): """Action on entering""" info = "Hey, it just skeletonnAnd on_enter event time is %d" % event.time self.tooltips.set_tip(self.ev_box, info)

И последняя callback-функция - на вызов пункта меню "О программе". Здесь я воспользуюсь стандартным диалогом из модуля gnome.ui:

def on_ppm_about(self, event, data=None): """Action on choosing 'about' in popup menu""" gnome.ui.About("GNOME Applet Skeleton", "0.1", "GNU General Public License v.2", "Simple skeleton for Python powered GNOME applet", ["Pythy <the.pythy@gmail.com>",] ).show()

Класс-костяк апплета написан, теперь нужно описать его "фабрику":

def applet_factory(applet, iid): GnomeAppletSkeleton(applet, iid) return True

Ух. С первым этапом закончил. Костяк апплета сделан. Осталось дело за малым. Запустить и посмотреть, что же получилось :)


Содержание раздела