This page outlines what needs to be done to get libglade handling arbitrary GObjects instead of just GtkWidgets. This is still a draft.
-- JamesHenstridge 2005-02-28
Use Cases
There are a number of things that people would like to declare within a glade file that they currently can't due to the libglade's restriction to GtkWidgets:
declare GtkSizeGroups in the glade file, and get other widgets to reference the size group.
declare columns in GtkTreeView widgets, along with the cell renderers contained within.
declare canvas items in a GnomeCanvas (or similar widget).
There are some other things that I don't particularly want to handle, including:
embedding GtkUIManager XML files within the .glade file.
although it may be useful to support declaration of GtkActionGroups and GtkActions inside a glade file.
Implementation plan
This is a list of the things that need to happen to get things implemented. It will probably change as things get implemented.
XML Schema Changes
An incompatible schema change is out of the question, for two reasons:
- the pain of the 1.x to 2.x migration was huge, and would prefer not to inflict it on developers again.
- the main aim of the schema change in 2.x was to create a schema that could easily be extended in a non-ambiguous way (with the 1.x schema, any element name might be refering to a widget property). So if there is any information that needs to be added, it should be able to be done in a backward compatible fashion.
That said, the current schema can probably handle the use cases listed above. For GObjects defined in the .glade file, we want to specify the following:
a tree of GObjects.
for each GObject, we want to declare:
properties for the GObject.
handlers that should be connected to the GObject's signals.
a list of children, possibly with some properties attached to the parent <-> child relationship.
The current <widget> tag covers this pretty well, so we may as well use it. The main problem with it is that it seems a bit unclean to declare a non-widget using that tag. One solution to this would be to add an <object> tag that is essentially a synonym (and treated as such by the parser).
DanWinship: We should make sure that it's not possible to open a new-style .glade file in an old version of Glade, and then edit it, save it, and end up losing information. If Glade will refuse to open a file with unrecognized "class" attributes or property nodes, or if it will refuse to let the user edit those things, then we're ok. Otherwise, doing something like using <object> instead of <widget> to force Glade to choke seems like a good plan.
The Parser
While the 2.x .glade file format is defined to be extensible, the parser for the format in libglade will spit out warnings if it sees anything that is unexpected.
Ignoring unknown elements or only printing warnings if $LIBGLADE_DEBUG includes some flags would be one way to better allow for future extensions, without making apps unnecessarily verbose. We probably still want a warning if the file isn't valid XML or the toplevel element is wrong, since this indicates a real problem.
The Name -> Object Mapping
Libglade maintains a dictionary mapping from widget names to objects, that is primarily accessed via glade_xml_get_widget().
This is implemented in terms of the self->priv->name_hash hash table. In order to make sure that a valid object or NULL is always returned, a handler is attached to the GtkObject::destroy signal of each constructed object, that removes the corresponding entry from name_hash.
If we are storing arbitrary GObjects, then we can't rely on this signal being available. The equivalent of GtkObject::destroy signal handlers in GObject are weak references. Unfortunately, weak references are run when the object is in an inconsistent state so we can't use object data to find the object name like we do currently. This could probably be handled by maintaining a second hash table mapping objects to object names.
Memory Management
Currently, libglade does not claim ownership of any of the objects it constructs. It keeps track of them in an internal dictionary, but these are weak references that are broken when the constructed object is destroyed. While this sounds like it would leak memory, in practice it works quite well.
Due to the way GtkObject floating references are handled, each object libglade constructs should end up with its one reference being held by its container parent. When the top level GtkWindows get destroyed, all sub-widgets get destroyed too. So as long as all toplevel windows constructed by libglade get destroyed, you won't get any memory leaks.
Now for GObjects without a floating reference that are children of some other object, the above scheme can probably continue to work -- just make the parent's "build_children" handler assume ownership of the reference (which is slightly different semantics than what we currently have, but might be the best solution). For top level GObjects though, there isn't necessarily anything that would cause them to be destroyed.
Do we just say that the application author will need to manage these objects? Or do we need to add something to handle them differently?
DanWinship: I'm not sure this will end up being a problem. Tree stuff will be reffed by its view, size groups will be reffed by the widgets in the group, etc.
API Changes
There are two distinct APIs for libglade:
- the public API used by applications
- the semi-public API used by libglade extension modules.
The first API can not be broken at all -- only additions can be made. The second API has lower stability guarantees, but it would be nice to keep to additions there too. We currently do a version check when loading modules, so we could increment the module API version to "2", and accept modules of versions 1 and 2.
At the moment, the following libglade extension modules exist:
- libbonoboui
- libgnomecanvas
- libgnomeui
- libgnome-media-profiles
So breaking this API is manageable, but it would still be nice to avoid it.
Public API
From the public API, we have the following functions that mention GtkWidget or GtkObject:
GtkWidget *glade_xml_get_widget (GladeXML *self, const char *name); GList *glade_xml_get_widget_prefix (GladeXML *self, const char *name); /* don't free the results of these two ... */ const char *glade_get_widget_name (GtkWidget *widget); GladeXML *glade_get_widget_tree (GtkWidget *widget); /* interface for changing the custom widget handling */ typedef GtkWidget *(* GladeXMLCustomWidgetHandler) (GladeXML *xml, gchar *func_name, gchar *name, gchar *string1, gchar *string2, gint int1, gint int2, gpointer user_data); void glade_set_custom_handler(GladeXMLCustomWidgetHandler handler, gpointer user_data);
None of these functions aren't really GtkWidget specific. The quick dirty method would be to keep the existing API but sometimes not return GtkWidgets, or accept objects that aren't GtkWidgets. Alternatively, new "object" versions could be added, and the old versions could be implemented in terms of those.
DanWinship: Assuming this is being combined with the LibgladeInGtk work, we would just have a new API in gtk, and rewrite the glade_* functions as wrappers around that
Module API
The libglade module API is used to tell libglade about widget types, and register various handlers for them:
a "new" function, responsible for constructing a widget. Calls glade_xml_set_common_params(), which handles signals, a11y, accelerators, building child widgets, etc.
a "build_children" function, that creates the child widgets, and adds them to the container. Child widgets are created using the glade_xml_build_widget() function.
a "find_internal_child" function, to handle composite widgets. With composite widgets that expose their internals (such as GtkDialog), there will be some child widgets you want to manipulate that shouldn't be created manually (eg. GtkDialog->vbox). This function is used to find an internal child by name. This function is only needed for some composite widgets.
Rather than requiring these functions be written for every single widget, default implementations are available:
a default "new" function that uses g_object_newv() to build the widget using standard GObject properties.
a default "build_children" function that uses GtkContainer child packing properties to build children.
The "new" implementation should work pretty much the same for plain GObjects. The "build_children" implementation won't work for arbitrary GObjects (or arbitrary GtkWidgets for that matter), but then GObjects with children will probably need a custom "build_children" implementation anyway.
Some parts of glade_xml_set_common_params() will probably need to be made conditional on whether the object is a widget or not.
In addition to helper functions registered against types, libglade lets you register custom property handlers against a (widget type, property name) tuple. These would function pretty much the same for arbitrary GObjects as for GtkWidgets.
Lastly, these functions suffer from the type problem of the public API. Ignoring it and making the user insert extra casts may be acceptable here given the low number of users. On the other hand, it would just involve adding alternatives for the following functions:
glade_register_widget
glade_register_custom_prop
New Objects
The following snippets show how some GObjects might be represented in a .glade file.
GnomeCanvas Items
<widget id="canvas1" class="GnomeCanvas"> ... <child internal-child="root"> <widget id="canvas1-root" class="GnomeCanvasGroup"> ... <child> <widget id="rect1" class="GnomeCanvasRect"> <property name="x1">0</property> <property name="y1">0</property> <property name="x2">10</property> <property name="y2">10</property> <property name="fill_color">blue</property> </widget> </child> </widget> </child> </widget>
Tree View Columns
<widget id="treeview" class="GtkTreeView"> ... <child> <widget id="col1" class="GtkTreeViewColumn"> <property name="resizable">yes</property> <property name="title">Column 1</property> <child> <widget id="col1-renderer1" class="GtkCellRendererText"> <property name="text">0</property> <property name="foreground">1</property> </widget> <packing> <property name="expand">yes</property> </packing> </child> <child> <widget id="col1-renderer2" class="GtkCellRendererToggle"> <property name="active">2</property> </widget> <packing> <property name="expand">no</property> </packing> </child> </widget> </widget>
I'm not too sure about this representation of GtkCellRenderers. There are two things you might want to do with a cell renderer property:
- set it to some value, like other object properties.
- bind it to a model column (idea of the numbers above).
The second case here would require custom property handlers for every single property on the cell renderer, so might not be worth it. This would mean that all the programmer would need to do to hook up the tree view would be:
- get the tree view, and set its model
get each tree view column and cell renderer, and call gtk_tree_view_column_set_attributes() to bind attributes, or gtk_tree_view_column_set_cell_data_func() to set a data function.
Size Groups
<widget id="sizegroup1" class="GtkSizeGroup"> <property name="mode">GTK_SIZE_GROUP_HORIZONTAL</property> </widget> ... <widget id="vbox" class="GtkVBox"> <child> <widget id="hbox1" class="GtkHBox"> <child> <widget id="label1"> <property name="text">Hello</property> <property name="sizegroup">sizegroup1</property> </widget> <packing> <property name="expand">no</property> </packing> </child> ... </widget> </child> <child> <widget id="hbox2" class="GtkHBox"> <child> <widget id="label2"> <property name="text">Some other label</property> <property name="sizegroup">sizegroup1</property> </widget> <packing> <property name="expand">no</property> </packing> </child> ... </widget> </child>
Here, "sizegroup" is implemented as a custom property on GtkWidget. Is there a better way to do this?
DanWinship: Seems fine to me. It's similar to how tooltips are represented
Reference implementation
I've started to do write an implementation, which can be downloaded via svn:
svn co svn://async.com.br/libglade
-- JohanDahlin
Widget support
GtkTreeViewColumn
GtkCellRenderer{Text,Pixbuf,Toggle}
GtkTreeModel{List,Tree,TreeSort}
GtkUIManager
GtkActionGroup
GtkAction{Action,Toggle,Radio}
GtkSizeGroup
Examples of files which can be parsed and constructed:
Note that data for GtkTreeModels are not supported.
DTD Changes
widget becomes object
JamesHenstridge: do we keep <widget> as a synonym for backwards compatibility? The answer here probably depends on what extent backwards compatibility can be maintained.
JohanDahlin: Is not done yet, if can easily be added if desired. Backwards compatibility needs support of deprecated and broken widgets. Do we really want to go there? Or use this opportunity to clean up?
DTD Additions
- ui subtag for objects, only to be used by GtkUIManager
- required attribute id
- optional attribute filename
- optional CDATA data
layout subtag for objects, only to be used by GtkTreeViewColumn
- constructor attribute for object, specifies which ui definition is going to be used to construct the object,
- if the tag is absent it's assumed it's created by g_object_new.
JamesHenstridge: the schema should say that the <ui> tag either has a filename attribute or contains character data (but not both). This is difficult to specify with a DTD, but easy with RELAX-NG. The spec shouldn't state how the character data is represented (<![CDATA[..]]> vs. entity escaping), since some XML serialisers don't give you that choice, and XML parsers are supposed to treat the two cases as identical. It seems a bit weird having escaped XML in the format as character data though ...
The constructor attribute definition seems a bit weird.
Why is it referencing .ui file? Shouldn't it be referencing a GtkUIManager?
JohanDahlin: So programs can merge/unmerge. None of the uis are actually added to the UI Manager before they are referenced by an object tag. So by referencing by the UI tag we give the GladeXML object a hint of what needs to be added/merged to the manager so it can be constructed.
What if the UI files don't create the named widget? What if the UI files create some widgets that aren't referenced anywhere in the .glade file?
JohanDahlin: A warning will be raised in the first case. Nothing happens if a widget isn't referenced by a program. It may be referenced later, by user code.
If a single .glade file declares two GtkUIManager's, each of which define a menubar called "menubar", what do we do? Object/widget ID's are meant to be unique in the glade file, but you probably want to use the same names in the .ui files to make it easier to apply common overlays.
JohanDahlin: A limitation of the current implementation is one GtkUIManager per file. I can't really see any reason for removing this. Maybe a good idea is to make the construction of a GtkUIManager implicit (and lazily constructed) and move the ui tags to a child of the root node <glade-interface>
Perhaps a better solution here would be to have a "window with ui manager" widget that took care of the menu and toolbar placement for you, similar to GnomeApp or BonoboWindow.
JohanDahlin: Agreed, this would be nice to have in Gtk, but it's out of scope for this subject.
UI Manager Representation
I am not convinced that the proposed representation of GtkUIManager in the .glade file makes sense. To explain this, I'll start with a description of Johan's representation (feel free to correct any errors -- this is based on previous comments and IRC discussions):
a GtkUIManager is defined at the toplevel, along with some number of UI definitions (either inline, or referencing an external file). Those UI definitions are not loaded by default, but made available to add at a later stage.
- Each UI definition has a unique name (presumably, this namespace is parallel to the object name namespace).
The UI manager will have one or more GtkActionGroups associated with it, each of which contain GtkActions.
If an <object> tag is encountered with a constructor attribute, the named UI file is merged into its associated UI manager. The value of the id attribute of the element is looked up in that UI manager, and the corresponding widget is added, instead of constructing the widget as normal.
The immediate problems I see with this model are:
If multiple windows with menus/toolbars are defined in a single .glade file, they will need to use different names since the <object id="foo" constructor="bar"> syntax ties the widget naming inside the UI manager with the object naming in the .glade file (irrespective of whether a single UI manager is used, or multiple ones). There are benefits to using common naming of menus and toolbars in a UI manager for things like plug-ins.
The constructor attribute seems to imply that a particular named widget built by a GtkUIManager belongs to a particular UI definition. In reality, the set of widgets constructed by the UI manager depends on all the merged UI definitions. A more natural way to represent this would be to list which UI definitions should be merged in the GtkUIManager's definition, and have the constructor attribute point at the UI manager rather than a UI definition.
- The UI manager is designed to be somewhat dynamic -- menus and toolbars can be created and destroyed at runtime as UI definitions are merged and unmerged. The proposed format seems suited more for a static configuration of menubar and toolbars. Of course, this is enough for cases where only children (menus and toolbar items) are added/removed, but it would be nice to handle the general case too.
On top of this, the above doesn't really match how I'd use GtkUIManager. Here's a rough description of what I'd do in a larger application:
For each toplevel window that needs menus and/or toolbars, a GtkUIManager is created to handle them.
- Every menu or toolbar created by a UI manager is added to its associated window, and toolbar additions/removals are handled at runtime.
Each window's UI manager has one or more GtkActionGroups added to it. Some of these action groups will be shared between multiple UI managers (eg. for application wide actions, such as quit, new document, open, preferences, help and about). Some might only be added to a single UI manager (eg. actions associated with a particular document/window like save, print, etc).
- UI definitions are loaded into the UI managers. Some UI definitions may be shared between windows (eg. a common underlay giving structure to the menus). The application may add and remove other UI definitions as the user switches modes in the application.
Naturally, the above would be simplified greatly with the addition of a window widget that handled menubar and toolbar addition/removal, which is why I suggested it earlier. Even without such a widget, it would be nice if the representation doesn't use something totally different (assuming that there is consensus on how to use GtkUIManager).
API Changes
get_widget(widget) -> get_object(object)
get_widget_prefix(prefix) -> get_objects()
get_widget_name(widget) -> get_object_name(object)
get_widget_tree(widget) -> get_object_tree(object)
And mostly a general s/GtkWidget/GObject/
API Additions
- void get_ui_manager()
- int merge_ui_from_name(name)
- void remove_ui_from_name(name)
These are used from glade-gtk.c, which needs to access internals of the GladeXML object.
- set_tooltips(widget, prop_value)
- set_default_widget(widget)
- set_focus_widget(widget)
Added properties
GtkWidget::sizegroup sizegroup of the widget
GtkListStore::types space separated list of types
GtkTreeStore::types ditto
GtkAction::accelerator accelerator for actions
Deprecated widgets
Support for all broken (GtkList, GtkListItem, GtkTree, GtkText) removed
Support for all deprecated (GtkCombo, GtkCList, GtkCTree, GtkOptionMenu, GtkPixmap, GtkPreview, GtkProgress, GtkTipsQuery) removed
JonathanBlandford: I don't think we can remove deprecated widgets entirely. There are still quite a few applications that use GtkCList and GtkCombo in it's glade files.
JohanDahlin: Perhaps we can add support for them, but raise a warning (similar to the python bindings of gtk+) when a deprecated widget is used. Should we also keep broken widgets?
LorenzoGilSanchez: I think such applications can keep using libglade. It they want to use new technologies they have to upgrade their widgets too IMHO