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

       

Внешний вид


Общая структура апплета аналогична таковой во второй части: наследуемся от ProxyGnomeApplet:

class ProxySwitcherGnomeApplet(ProxyGnomeApplet)

И переопределяем нужные методы. Во-первых, роль главного виджета играет gtk.Image, а не gtk.Label как в "модельном" апплете.

def init_additional_widgets(self): """Create additional widgets""" self._init_pixbufs() self.image = gtk.Image() self.ev_box.add(self.image)

gtk.Image это некий "контейнер" изображения. Его можно "заполнить" данными из различных источников, мы будем использовать пиксельный буфер (pixbuf). Само изображение (как пиксельный буфер) будем брать из значка "Прокси" текущей темы. Если в текущей теме не будет такой иконки - будем использовать тему Tango.

def _init_pixbufs(self): """Init pixbufs from current theme, or from Tango, if 'proxy' icon not in current theme""" self.pixbufs = {} self.theme = self._get_theme() try: self._reload_pixbufs() except gobject.GError: self.theme = self._get_theme('Tango') self._reload_pixbufs()

Мы будем использовать словарь pixbufs для хранения пиксельных буферов для включенного и выключенного состояния прокси. В первой строке мы инициализируем этот словарь. Во второй - получаем текущую тему. Потом пробуем подгрузить значок "Прокси" из текущей темы (self._reload_pixbufs()), если такого значка нет (исключение gobject.GError), то используем тему Tango и уже с нее загружаем значок.

Название текущей темы берем из GConf, ключ /desktop/gnome/interface/icon_theme: def _get_theme(self, name=None): """Return a theme by name, or current one if name is None""" if name is None: name = gconf.client_get_default().get_string('/desktop/gnome/interface/icon_theme') theme = gtk.IconTheme() theme.set_custom_theme(name) return theme

В методе _reload_pixbufs мы решаем сразу несколько задач:

подгружаем значок для включенного прокси (как пиксельный буфер) из текущей темы


на основе полученного изображения формируем пиксельный буфер для выключенного прокси

def _reload_pixbufs(self, size=None): """Reload pixbufs from current theme for specified size, or for panel's size if size is None""" if size is None: size = self.applet.get_size() pixbuf = self.theme.load_icon('proxy', size, gtk.ICON_LOOKUP_FORCE_SVG) faded_pixbuf = gtk.gdk.Pixbuf(pixbuf.get_colorspace(), pixbuf.get_has_alpha(), pixbuf.get_bits_per_sample(), pixbuf.get_width(), pixbuf.get_height()) pixbuf.saturate_and_pixelate(faded_pixbuf, 1, True) self.pixbufs[True] = pixbuf self.pixbufs[False] = faded_pixbuf



И вот в этом коде четко проявляется, что PyGTK - лишь "прослойка" между Python и C-библиотекой GTK: для того, чтобы получить "затемненный" пиксельный буфер (faded_pixbuf), нужно воспользоваться методом saturate_and_pixelate объекта gtk.gdk.Pixbuf, причем метод ничего не возвращает, а "затемненный" пиксельный буфер должен быть передан первым параметром. Что еще более не типично для Python - он обязательно должен быть типа gtk.gdk.Pixbuf. Т.е. нельзя, скажем, инициализировав новый пиксельный буфер значением None, передать его методу saturate_and_pixelate - будет ошибка несовпадения типов. Еще один момент - объект gtk.gdk.Pixbuf не получится скопировать при помощи copy.deepcopy() - опять таки по причине C-природы GTK. Поэтому приходится абсолютно неестественно для Python создавать новый пиксельный буфер, передавая конструктору gtk.gdk.Pixbuf параметры исходного пиксельного буфера. И уже этот, новый пиксельный буфер, "отдавать" saturate_and_pixelate.

Все остальное в этом методе достаточно просто: в самом начале, если не передан параметр size, то берем размер панели, на которую помещается данный апплет (self.applet.get_size()); а в самом конце сохраняем полученные пиксельные буферы в словарь pixbufs.

Теперь нужно переопределить методы after_init, где инициализируется начальное состояние апплета и callback-функции _cb_proxy_change на переключение прокси и on_enter на попадание курсора мыши внутрь апплета.


Ну и неплохо было бы изменить диалог "О программе", переопределив on_ppm_about.

def after_init(self): """Init additional attributes of applet""" self.proxy = ProxyGconfClient(callback=self._cb_proxy_change) self.proxy_state = self.proxy.get_state() self.proxy_is_on = self.proxy.is_on() self.set_visual_state(self.proxy_state, self.proxy_is_on) self.button_actions[1] = self.switch_proxy

Метод after_init повторяет таковой у класса ProxyGnomeApplet за небольшими изменениями: дополнительно в атрибут proxy_is_on записываем данные, включен ли прокси; визуальное состояние апплета устанавливается методом set_visual_state.

def set_visual_state(self, state, is_on): """Set overall visual state for corresponding proxy's state""" msg_on_state = u"Proxy is on" msg_off_state = u"Proxy is off" mode = u"mode: %s" % state variant = (is_on and msg_on_state) or msg_off_state self.info = u"%s (%s)" % (variant, mode) self._set_image(is_on)

def _set_image(self, kind): """Set image for specified state"""

self.image.set_from_pixbuf(self.pixbufs[kind])

Здесь код незамысловат: в начале формируются строки всплывающей подсказки, в зависимости от значения параметра is_on выбирается текст "Proxy is on" или "Proxy is off". Дополнительно, в скобках отображается режим (параметр state). Последняя строка - установка соответствующего значка (метод _set_image). Ну а в методе _set_image - заполнение контейнера gtk.Image данными из пиксельного буфера. Какой пиксельный буфер (из двух, что хранятся в self.pixbufs) использовать, определяет переданный параметр kind.

Следующая пара методов, которые нужно переопределить, это _cb_proxy_change и on_enter - callback-функции на переключение прокси и на попадание указателя мыши в апплет. Тут очень просто и понятно:

def _cb_proxy_change(self, client, cnxn_id, entry, params): """Callback for changing proxy, change visual state of applet""" self.proxy_state = self.proxy.get_state() self.proxy_is_on = self.proxy.is_on() self.set_visual_state(self.proxy_state, self.proxy_is_on)



def on_enter(self, widget, event): """Callback for 'on-enter' event, show tooltip""" self.tooltips.set_tip(self.ev_box, self.info)

И последний метод - это показ диалога "О программе". Здесь мы используем стандартный виджет gnome.ui.About. Параметры конструктора у него такие: имя приложения, версия, лицензия, краткое описание, список авторов, список авторов документации, переводчики, логотип. Версия, лицензия и автор у нас указаны в начале файла, в "магических" переменных __version__, __license__ и __author__. В качестве логотипа используем все тот же значок "Прокси" из текущей темы, только бОльшего размера (80 пикселов). Все остальное понятно из кода:

def on_ppm_about(self, event, data=None): """Callback for pop-up menu item 'About', show About dialog""" pixbuf_logo = self.theme.load_icon('proxy', 80, gtk.ICON_LOOKUP_FORCE_SVG) msg_applet_name = u"Proxy switcher" msg_applet_description = u"Applet for turning proxy on/off" gnome.ui.About(msg_applet_name, __version__, __license__, msg_applet_description, [__author__,], # programming None, # documentation None, # translating pixbuf_logo, ).show()

С внешним видом вроде бы все.

Здесь я намеренно опустил некоторые вещи, чтобы не перегружать код непринципиальными моментами:

Реакция апплета на изменение ориентации панели - сигнал change-orient

Реакция апплета на изменение размера панели - сигнал change-size

Реакция апплета на изменение фона панели - сигнал change-background

Примеры callback-функций на эти сигналы (а они идентичны у большинства апплетов) можно найти при помощи Google Codesearch: например, для change-orient.


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