Заметки о встраивании сторонних виджетов в Glade
Будем считать, что у нас есть виджет
My Widget из набора виджетов My, и мы хотим добавить его поддержку в glade и libglade.
0. Требования к библиотеке виджетов
Зачастую программисты, пишущие расширения для Gtk+ не утруждают себя следованием всем канонам описания виджетов в gtk. В результате получается, что виджеты правильно работают, если их использовать в коде, написанном руками, но в glade с ними возникают проблемы. Главным образом это касается случая, когда у виджета есть набор свойств (properties). Для glade недостаточно, если библиотека будет содержать функции my_widget_set_property(widget, property) и my_widget_get_property(widget), а в самой структуре виджета будет содержаться поле property. В этом случае он просто не увидит такого свойства. Нужно, во-первых, чтобы при инициализации класса виджета (функция my_widget_class_init) свойства были соответствующим образом зарегистрированы средствами glib (функция g_object_class_install_property). Помимо этого должны быть определены методы gobject_class->set_property и gobject_class->get_property. Glade будет пользоваться как раз ими.
Ещё один момент относится к выбору родительского класса. Gtk+ позволяет в этом отношении большие вольности, например, можно в качестве родительского класса взять
Gtk Vbox и создать на его основе виджет, не являющийся контейнером. В Glade такое не пройдёт, т.к. он проверяет тип родительского класса, и в данном примере будет считать виджет контейнером со всеми вытекающими из этого последствиями. Точнее, похоже, что так делать всё-таки можно (например,
Gtk Color Selection является наследником как раз
Gtk Vbox), но нужно соблюсти ряд формальностей: построение содержимого виджета следует делать внутри функции my_widget_init (а не my_widget_new), и всю процедуру создания содержимого следует разместить между вызовами функций gtk_widget_push_composite_child и gtk_widget_pop_composite_child.
В Glade и libglade загрузка модулей расширения происходит по-разному, несмотря на то, что они имеют дело с одним и тем же описанием интерфейса в xml. Поэтому нужно осуществлять поддержку сторонних виджетов отдельно для glade и libglade.
1. Glade
Для того, чтобы glade увидел наш виджет и знал, как с ним обращаться, требуются следующие файлы:
1. Так называемый
файл каталога. Это файл в формате xml, откуда glade узнаёт, как ему работать с нашим(и) виджет(ами). Называется он произвольно, но лучше, наверное, чтобы его название указывало на набор виджетов. Т.е. в нашем случае это будет my.xml Этот файл кладётся в каталог /usr/share/glade3/catalogs Его содержимое примерно таково:
Во второй строке (тэг <glade-catalog>)определяются глобальные параметры:
- “name” — название файла каталога (my, т.е. my.xml);
- “library” — название библиотеки сопровождения (про неё несколько позже, здесь замечу, что в данном случае glade будет искать библиотеку под названием libglade-my.so);
- “icon-prefix” — указание на способ названия пиктограмм (про это тоже попозже);
- “domain” — указание на то, где glade будет искать переводы строк, встречающихся в файле каталога и библиотеке сопровождения (в нашем случае это будет файл /usr/share/locale/ru/LC_MESSAGES/myglade.mo Если атрибут “domain” не определён, то будет использован параметр “library”);
- “depends” — название (параметр “name”) модуля (а если их несколько?), от которых зависит наш модуль (обычно — «gtk+");
- “book” — используется для определения названия файла справки;
- “init-function” — название функции, которая будет вызвана после загрузке модуля поддержки наших виджетов перед вызовом функций инициализации классов виджетов.
Есть ещё атрибуты “version” и “targeable”, но смысл их применения мне не ясен. Из всего перечисленного обязательными являются только атрибуты “name” и “library”.
Далее, внутри пары тэгов <glade-widget-classes></glade-widget-classes>, идут описания самих классов виджетов. Описание каждого класса виджета заключено внутри пары тэгов <glade-widget-class></glade-widget-class>. У тега <glade-widget-class> имеются три обязательных атрибута:
- “name” — название класса виджета (должно соответствовать его названию в библиотеке. При построении виджета glade использует соответствующую функцию get_type для создания объекта виджета, и если поле “name” имеет значение "My Widget", то glade будет искать функцию my_widget_get_type);
- “generic-name” — используется для создания названий экземпляров виджета при построении интерфейса с его использованием в glade (в нашем случае виджеты будут называться “widget1”, “widget2” и т.д.) и при определении названия файла пиктограммы данного виджета;
- “title” — название виджета в палитре glade.
Остальные свойства являются необязательными:
- “deprecated” — двоичное свойство, указывающее, что виджет является устаревшим;
- “get-type-function” — используется для явного указания названия функции _get_type виджета (полезно, если glade неверно определяет название функции на основании параметра “name”);
- “icon-name” — используется для явного указания названия пиктограммы виджета;
- “parent” — используется, если мы по каким-то причинам хотим изменить для glade тип родительского класса виджета;
- “toplevel” — используется для указания glade, что данный класс виджетов является окном верхнего уровня. Это свойство наследуется от родительских классов виджета;
- “fixed” — используется для указания glade, что данный класс виджетов использует свободное позиционирование с помощью специального контейнера Glade Fixed (по-видимому, это свойство используется, если сам виджет является контейнером). Это свойство наследуется от родительских классов виджета;
- “use-placeholders” — указывает glade, что данный контейнер использует псевдовиджеты-заместители (placeholders) для резервирования места для размещаемых в нём виджетов;
- “default-width”, “default-height” — ширина и высота виджета по умолчанию.
Есть ещё парочка свойств “since”, “builder-unsupport”, назначение которых мне не ясно. В принципе, во многих случаях для описания виджета кроме пары тэгов <glade-widget-class></glade-widget-class> больше ничего не надо. В более сложных случаях там могут находиться описания методов, действий, параметров и т.п.; это всё мы рассмотрим далее.
Наконец, пара тэгов <glade-widget-group></glade-widget-group> описывает раздел палитры glade, в котором появятся наши виджеты. У тега <glade-widget-group> есть два обязательных атрибута: “name” — честно говоря, я не знаю где он используется, т.к. в заголовке раздела палитры glade отображается второй параметр — “title”. Далее идут теги <glade-widget-class-ref name="
My Widget"/>, в которых параметр “name” соответствует полю “name” виджета, который мы хотим видеть в данном разделе палитры.
Косая черта в конце тэга не является опечаткой!!! В одном файле каталога может быть несколько групп виджетов <glade-widget-group></glade-widget-group>; каждый из которых будет соответствовать своему разделу палитры glade. Ещё внутри группы может присутствовать тэг <default-palette-state expanded="False”/>, указывающий glade, что данный раздел при запуске отображается свёрнутым.
2.
Библиотека сопровождения. Это динамически загружаемый модуль, который glade загружает, когда встречает соответствующий параметр “library” в файле каталога. Он лежит в /usr/lib/glade/modules, а его название образуется из значения поля “library” путём прибавления приставки “lib” вначале и расширения “.so” в конце. В нашем случае это будет libglade-my.so Одна из главных задач, выполняемых библиотекой сопровождения — загрузка библиотеки, реализующей набор виджетов, поддержку которого мы хотим получить в glade. Для этого она, во-первых, должна динамически связываться с библиотекой виджетов, а во-вторых, содержать в своём коде обращение к какой-либо функции из библиотеки виджетов (иначе в силу излишнего, в данном случае, интеллекта ld он не будет динамически связывать библиотеку сопровождения с библиотекой виджетов несмотря на указание ему параметра -lmy (считаем, что сама библиотека виджетов называется libmy.so)). Кроме того, в среди параметров линкера должно присутствовать -module. Вот пример практически минимального кода библиотеки сопровождения:
Здесь единственная функция — как раз упомянутая «пустышка» для того, чтобы библиотека сопровождения связывалась с библиотекой виджетов. Следует отметить, что все функции, упоминаемые в файле каталога (тэги и параметры тэгов типа “xxx-function”) glade ищет в библиотеке сопровождения.
3.
Пиктограмма. Последнее, что необходимо glade для поддержки нашего виджета это пиктограмма, отображающая его в палитре виджетов. В настоящее время glade поддерживает пиктограммы двух размеров, 16х16 и 22х22 точки, которые лежат в каталогах /usr/share/glade3/pixmaps/hicolor/16x16/actions/ и /usr/share/glade3/pixmaps/hicolor/22x22/actions/ (лучше, чтобы для каждого виджета имелись пиктограммы обоих размеров), а название складывается по правилу widget-CATALOG_NAME-GENERIC_NAME, где CATALOG_NAME — содержимое поля “name” тэга <glade-catalog>, а GENERIC_NAME — содержимое поля “generic-name” тэга
<glade-widget-class> соответствующего виджета. В нашем случае получается widget-my-widget.png (я видел пиктограммы только типа .png, возможно, поддерживаются любые изображения, загружаемые библиотекой gtk-pixbuf). Возможно явное указание названия пиктограммы параметром “icon-name” тэга <glade-widget-class>.
О свойствах виджета
Glade просматривает все свойства каждого загружаемого виджета. Поэтому в файле каталога во многих случаях нет необходимости их описывать. Однако описание свойств в файле каталога даёт много дополнительных возможностей. Свойства описываются внутри пары тэгов <properties></properties> внутри описания класса виджета. Каждое свойство задаётся парой тегов <property></property> (Возможна и однострочная запись <property что-то там/>). Тэг <property> имеет следущие атрибуты (обязательным из которых является только атрибут “id”):
- “id” — идентификатор свойства (должен совпадать с названием свойства виджета);
- “name” — название свойства, которое будет отображаться в интерфейсе. Если название не задано, то оно будет взято из структуры GParamSpec виджета;
- “disabled” — указывает на то, что данное свойство не используется (например, в целях отладки);
- “default” — используется для задания значения свойства по умолчанию;
- “translatable” — указывает glade, что данное текстовое свойство является переводимым. По значение по умолчанию — «нет»;
- “common” — помеченное таким образом свойство появляется в списке свойств виджета независимо от того, присутствует ли оно в структуре Gtk Widget Class;
- “optional” — указывает glade на то, что данное свойство является необязательным. В этом случае поле ввода значения свойства будет неактивным, а рядом появится пункт выбора активации данного свойства (как например свойство «width/height-request”);
- “optional-default” — необязательное свойство, активное по умолчанию;
- “query” — указывает на необходимость запроса свойства (открывается диалоговое окно) при добавлении экземпляра виджета к проекту;
- “save” — определяет, следует ли сохранять свойство в файле описания интерфейса glade (по умолчанию — «да»);
- “visible” — определяет, отображается или нет свойство в редакторе glade (по умолчанию — «да»);
- “ignore” — установка этого атрибута сообщает glade, что следует игнорировать изменение данного свойства и не вызывать функцию g_object_set_property или функции, указанной внутри тегов <set-property-func></set-property-func> внутри определения класса виджета;
- “themed-icon” — указывает на то, что данное свойство описывает пиктограмму из набора (темы). Вызывает соответствующий диалог выбора;
- “weight” — числовое свойство, описывающее положение поля ввода данного свойства в окне редактора свойств;
- “transfer-on-paste” — используется для свойств упаковки и указывает на то, что данное свойство сопровождает виджет при его перетаскивании в другой контейнер, поддерживающий данное свойство;
- “save-always” — указывает на то, что данное свойство всегда будет сохраняться в файле описания интерфейса, даже в том случае, когда оно имеет значение по умолчанию (обычно свойства, имеющие значение по умолчанию, не сохраняются).
Есть ещё парочка атрибутов, “since” и “resource”, назначение которых мне не ясно.
Внутри пары тэгов <property></property> могут присутсвовать дочерние тэги:
- <spec></spec>, внутри которых задаётся функция, возвращающая структуру GParamSpec (список свойств виджета) для этого свойства взамен той, которая берётся из свойств реального класса виджета, что позволяет задавать фиктивные свойства у виджетов;
- <tooltip></tooltip> — позволяет задавать текст всплывающей подсказки, отличающийся от того, который берётся из структуры GParamSpec;
- <visible-lines></visible-lines> — задаёт количество отображаемых строк в поле редактирования текстовых свойств (в настоящее время до конца не реализовано: если параметр равен 2, то в поле редактирования появляется полоса прокрутки);
- <displayable-values></displayable-values> — позволяет задавать более понятные названия вариантов свойства, имеющего тип «перечисление», либо двоичный тип. Атрибут тэга “id” определяет наименование варианта свойства, а атрибут “name” — его отображаемое значение.
Иногда бывает нужно, чтобы при изменении свойств виджета выполнялись какие-то действия, отличные от просто вызова метода set_property класса данного виджета. Для этого в каталоге в описании класса виджета можно использовать пару тэгов <set_property_func></set_property_func>, между которыми заключено название функции, вызываемой при изменении какого-либо из свойств виджета. Сама функция определяется в исходниках библиотеки сопровождения. Вот пример кода функции изменения свойства:
Здесь установка свойств “property1” и “property2” требует особых действий, выполняемых функциями glade_my_widget_set_property1 и glade_my_widget_set_property2; конструкция, к которой происходит обращение в случае изменения других свойств фактически представляет собой обёртку для запуска метода set_property класса нашего виджета. Само значение свойства можно получить с помощью функций g_value_get_xxx(value) (в зависимости от типа свойства это может быть g_value_get_int, g_value_get_string и т.п.). Параметром у макроса GWA_GET_CLASS должен быть тип
родительского виджета. (Считаем, что родительским классом
My Widget является
Gtk Drawing Area).
Аналогично можно определить и функцию, отвечающую за получение свойств виджета. Её название задаётся внутри пары тэгов <get_property_func></get_property_func> в файле каталога. Свойство передаётся через параметр value этой функции (сначала следует выполнить вызов g_value_reset(value), а затем — g_value_set_xxx(value, value_itself).
У виджета-контейнера могут присутствовать
свойства упаковки дочерних виджетов. Насколько я понимаю, их синтаксис идентичен просто свойствам за исключением того, что их описания располагаются внутри пары тэгов <packing-properties></packing-properties>. Свойства упаковки дочерних виджетов должны быть зарегистрированы при инициализации класса виджета (в самой библиотеке виджета) с помощью функции gtk_container_class_install_child_property().
Что ещё может быть у виджета
Внутри описания класса виджета кроме уже описанных функций <set_property_func> и <get_property_func> могут быть определены следующие функции (каждый тэг является контейнером, внутри которого помещается название определяемой в библиотеке сопровождения функции):
- сonstructor-function — вызывается вместо функции создания объекта GObject при инициализации данного класса виджета;
- post-create-function — вызывается каждый раз, когда создаётся экземпляр виджета данного класса;
- deep-post-create-function — то же самое, что и post-create-function. Я так и не понял, чем они отличаются;
- get-property-function — это мы уже знаем (см. выше);
- set-property-function — также см. выше;
- verify-function — функция, вызываемая для проверки свойств виджета;
- add-child-function — вызывается при добавлении дочернего виджета;
- remove-child-function — вызывается при удалении дочернего виджета;
- replace-child-function — вызывается при замене дочернего виджета;
- get-children-function — вызывается для получения списка дочерних виджетов;
- child-get-property-function — вызывается для получения свойств упаковки дочерних виджетов;
- child-set-property-function — вызывается для установки свойств упаковки дочерних виджетов;
- child-verify-function — вызывается для проверки свойств упаковки дочерних виджетов;
- get-internal-child-function — вызывается для получения внутреннего дочернего виджета составного виджета;
- action-activate-function — вызывается при выполнении действия над виджетом;
- child-action-activate-function — вызывается при выполнении действия над дочерним виджетом;
- read-widget-function — функция, осуществляющая чтение виджета из xml файла;
- write-widget-function — функция, осуществляющая запись виджета в xml файл;
- read-child-function — функция, осуществляющая чтение дочернего виджета из xml файла;
- write-child-function — функция, осуществляющая запись дочернего виджета в xml файл;
- create-editor-property-function — функция, создающая виджеты, которые будут использоваться в редакторе свойств glade;
- string-from-value-function — что-то совсем непонятное...
В классе виджета могут быть определены
действия (actions). Каждому действию соответствует пункт меню, выпадающего при нажатии правой кнопки мыши на виджете в проекте или его названии в дереве виджетов. Для этого в файле каталога внутри описания класса виджета следует поместить конструкции вида:
Каждое определение действия (тэг <action>) имеет обязательные атрибуты “id” — идентификатор действия и “name” — его название, как оно будет отображаться в редакторе свойств glade. Необязательный атрибут “stock” указывает на то, что данному действию соответствует определённая пиктограмма. Как видно из примера, описания действий могут быть вложенными. Если пользователь совершает действие, то вызвается функция из библиотеки сопровождения, название которой glade строит следующим образом: вначале идёт “glade_", затем — название виджета, угаданное glade на основании названия («name”) виджета по тому же принципу, что и для функции, возвращающей тип виджета (в нашем случае my_widget), и в конце — “_action_activate”. Итого в нашем случае получаем glade_my_widget_action_activate.
Функция, обрабатывающая выполнение действия, имеет примерно следующий вид:
Последним аргументом ей передаётся идентификатор действия, по которому можно узнать, какое действие произвёл пользователь. Если было выбрано вложенное действие, то строка идентификатора будет содержать полный идентификатор, состоящий из идентификатора выбранного действия и идентификаторов вышележащих действий, в которое выбранное действие было вложено, например, «add_parent/Alignment».
Если виджет является контейнером, то описание класса может содержать
действия упаковки (packing actions) — действия, выполняемые над заключёнными в него виджетами (например, «вставить новый в начало», «вставить новый в конец», «удалить»). Их описание полностью идентично действиям, за исключением того, что они помещаются внутри пары тэгов <packing-actions></packing-actions>.
Описание класса виджета может содержать
параметры упаковки по умолчанию. Они имеют следующий вид:
здесь пара тэгов <packing-defaults></packing-defaults> ограничивает список параметров упаковки по умолчанию, пара тэгов <parent-class></parent-class> определяет, для какого родительского класса (т.е. при упаковке нашего виджета в контейнер, относящийся к соответствующему классу) эти параметры предназначаются (параметр “name” тэга <parent-class> определяет наименование родительского класса), а между тэгами <parent-class></parent-class> располагаются сами значения параметров по умолчанию (наименование параметра задается атрибутом “id” тега <child-property>, а его значение по умолчанию — атрибутом “default” того же тэга). В приведённом примере установлено, что при упаковке нашего виджета в контейнер класса
Gtk V Box параметр “expand” по умолчанию будет иметь значение “false”.
2. libglade
Для того, чтобы наши сторонние виджеты могли использоваться в интерфейсе, созданном с помощью Glade, следует ещё позаботиться об их поддержки в библиотеке libglade, которая занимается построением интерфейса во время исполнения программы. Для этого следует написать ещё одну
библиотеку сопровождения, — на это раз уже для libglade. Вот образец кода подобной библиотеки:
Основное значение здесь выполняет функция glade_register_widget(). Первым её аргументом идёт тип виджета, возвращающую тип нашего виджета. Затем — указатель на функцию, отвечающую за построение виджета. В подавляющем большинстве случаев нет необходимости самим что-то здесь изобретать, и следует воспользоваться функцией libglade glade_standard_build_widget(). Последние два аргумента (у нас оба равны NULL) используются для контейнеров: третий аргумент — указатель на функцию, отвечающую за построение дочерних виджетов (часто можно опять пользоваться стандартной функцией glade_standard_build_children()), четвёртый — указатель на функцию, осуществляющую поиск внутренних виджетов составного виджета. Эта функция имеет следующий вид:
Для нас важны последние два аргумента этой функции:
Gtk Widget *parent, возвращающий указатель на наш виджет, и const gchar *name — название внутреннего виджета, указатель на который мы желаем получить.
Если в нашей библиотеке имеется несколько виджетов (и мы желаем получить поддержку их всех в libglade), то все они должны быть зарегистрированы посредством вызова функции glade_register_widget(). Кроме того, внутри функции glade_module_register_widgets() могут ещё находиться процедуры регистрации фиктивных свойств виджетов (т.е. таких свойств, которые не зарегистрированы с помощью вызова функции g_object_class_install_property() при инициализации класса виджета. Честно говоря, наличие таких свойств не вполне корректно, но glade/libglade позволяют выкручиваться и в этих случаях). Регистрация производится вызовом функции
У этой функции первый аргумент — тип виджета, для которого мы хотим зарегистрировать свойство, второй — название свойства и третий — функция, которая вызывается при изменении свойства. Она имеет вид change_function(
Glade XML *xml,
Gtk Widget *widget, const char *name, const char *value), где второй аргумент — указатель на виджет, свойство которого изменилось, третий — название свойства, и четвёртый — его значение. Четвёртый аргумент имеет тип «gchar*" (видимо, создатели libglade не захотели связываться с GValue), и если свойство имеет отличный от gchar* тип, то приходится преобразовывать его в то, что нам надо (например, если нам нужен целочисленный тип, то следует писать INT(value)).
Библиотека поддержки наших виджетов в libglade должна динамически связываться с самой библиотекой виджетов, а параметры компоновщика должны быть следующие: -export-dynamic -module -avoid-version -no-undefined. Модуль кладётся в /usr/lib/libglade/2.0 В нашем случае рекомендуемое его название — libmy.so
Источники информации
http://glade.gnome.org/docs/catalog.html
http://www.jamesh.id.au/software/libglade/
Кроме того многое вырыто из исходников glade (рекомендую посмотреть на glade-%version/plugins/gtk+/glade-gtk.c и gtk+.xml там же) и libglade (libglade-%version/glade/glade-gtk.c).