Using libglade for GTK+ interfaces

Contents

Introduction

This talk covers using glade and libglade to rapidly create GTK+ applications, in C. It assumes knowledge of C, and a fair degree of familiarity with GTK+. Glade is a GUI builder for GTK+ and Gnome applications. It allows you to visually create your interface, and can generate source code for the interface in several languages. libglade takes Glade savefiles, which are XML descriptions of an interface, and loads them at runtime.

There are several advantages to this. First, you can make changes to your interface without recompiling, which makes development a little easier. Second, in a collaborative environment, it provides a very clear delineation of work. If you can agree on labels for key interface elements, work on the GUI layout and on the code can proceed independently. Third, power users can customise the interface without needing to touch source code, using a graphical tool.

Creating the interface

For the purpose of this talk, I will take you through the libglade specific steps of building a turn viewer for the play by email game, GalaxyNG. The problem, then, is to display information about a 2 dimensional starmap.

This talk won't actually solve the problem, rather it will show how libglade can be used to create the interface to solve the problem. For those that are interested, the source code to the solved problem is available at http://users.bigpond.net.au/mlm/planner.

Our first interface, then, will be very simple, a GtkDrawingArea widget in a frame, in a window. A GtkDrawingArea widget is simply a widget that provides a GdkWindow for an application to draw onto. Unlike a GtkPixmap widget, the image is not stored server side, and can be modified. Unlike a GtkImage widget, the image is a gdk drawable, and the gdk drawing functions can be used on it.

Here is a screen shot of libglade, with our window, frame and drawing area:

Notice that we called the main window "rootnode." We also named the GtkDrawingArea "map." This is so our code can gain access to these widgets.

When saving the project, most options can be ignored, since they're only used when building source code. The LibGlade option tab allows you to enable i18n support - libglade has a gettext() wrapper for strings in the interface file. Once saved, you can take a look at the contents of the glade file:

<?xml version="1.0"?>
<GTK-Interface>

<project>
  <name>talk</name>
  <program_name>talk</program_name>
  <directory></directory>
  <source_directory>src</source_directory>
  <pixmaps_directory>pixmaps</pixmaps_directory>
  <language>C</language>
  <gnome_support>False</gnome_support>
  <gettext_support>True</gettext_support>
</project>

<widget>
  <class>GtkWindow</class>
  <name>rootnode</name>
  <visible>True</visible>
  <title>GalaxyNG Planner</title>
  <type>GTK_WINDOW_TOPLEVEL</type>
  <position>GTK_WIN_POS_NONE</position>
  <modal>False</modal>
  <allow_shrink>False</allow_shrink>
  <allow_grow>True</allow_grow>
  <auto_shrink>False</auto_shrink>

  <widget>
    <class>GtkFrame</class>
    <name>mapframe</name>
    <width>400</width>
    <height>300</height>
    <label_xalign>0</label_xalign>
    <shadow_type>GTK_SHADOW_IN</shadow_type>

    <widget>
      <class>GtkDrawingArea</class>
      <name>map</name>
    </widget>
  </widget>
</widget>

</GTK-Interface>

You can see the names we gave our widgets visible in the file. We're now ready to write code to load this file.

Using the interface

Our first program (full source here) is very simple. This code is based on the example code included with the libglade documentation. First, some standard includes:

#include <stdlib.h>
#include <stdio.h>
Then, the includes for GTK+ and libglade:
#include <gtk/gtk.h>
#include <glade/glade.h>
There is another file we could include, glade-build.h, which is used for custom widgets. We will show an example of using that later.

Next we declare two constants, specifying the filename and the root node. These could be declared in some other way; the current version of the planner has a config file option for interface location.

/* the filename and root node of the interface */
#define FILENAME "../talk.glade"
#define ROOTNODE "rootnode"

And then, a global variable to hold the interface's xml tree:

/* we're using a global variable since it will be referenced later.
 * note it's a module global, not a program global. */
static GladeXML *xml;
This is global since it will be used in some callback routines later on to access named widgets.
int main (int argc, char **argv)
{
    /* standard gtk+ init, followed by a glade init */
    gtk_init(&argc, &argv);
    glade_init();
The only new thing here is glade_init(), and it's very simple.
    /* try to load the interface and verify it happened */
    xml = glade_xml_new(FILENAME, ROOTNODE);
glade_xml_new() takes two arguments, a filename and an optional root node. If a root node is specified, the widget tree is built from that widget down, otherwise the tree is built from the first widget encountered. Specifying a root node allows you to have multiple window definitions in a glade file, along with definitions for popup menus, dialog boxes, and so on. When you need to use them, call glade_xml_new() with the appropriate rootnode name. The XML tree is cached between calls.
    if (!xml) {
	g_warning("something bad happened while creating the interface");
	return 1;
    }
Of course, you should check the return value.
    /* then just run gtk_main() as per usual */
    gtk_main();

    return 0;
}
And that is all there is to it. With a simple Makefile:
CC=gcc
CFLAGS=-g -W -Wall -ansi-pedantic `libglade-config --cflags`
LIBS=`libglade-config --libs`

TARGET=stage1
OBJECTS=stage1.o

$(TARGET): $(OBJECTS)
	$(CC) -o $@ $^ $(LIBS)

clean:
	rm -f *.o *~ $(TARGET) *.bak
we soon have our first libglade program up and running.

It's not a very attractive program, though. There's no menubar, for one. Let's add one using glade, and see the change in our program without recompilation.

Signals

This is all well and good, but our interface doesn't actually do anything. To make our program useful, we will need to have some signal handlers. We'll add these using glade, handling the "destroy" signal emitted by our top level window when its close icon is clicked, and the Exit menu item's activation. We'll set both of them to gtk_main_quit() to exit the program.

There's one change we need to make to our source code to support this. Just before the gtk_main() call, we add

    glade_xml_signal_autoconnect(xml);
This will search for signals defined in the glade file and bind them at runtime to symbols exported by the program, using glib's gmodule facilities. This makes connecting signals a breeze - just write the signal handler, and add it to the glade project. Compiling this program (source available in
stage2/stage2.c) and running it now lets us quit our program.

Let's get a little more adventurous, and add a handler for the GtkDrawingArea's expose event. To do this, we'll add a new file to our project, map.c, which will export one function, on_map_expose_event().

gboolean on_map_expose_event(GtkWidget *map, GdkEventExpose *event)
{
    gdk_window_clear_area(map->window,
	    event->area.x, event->area.y,
	    event->area.width, event->area.height);

    gdk_gc_set_clip_rectangle(map->style->fg_gc[map->state],
	    &map->area);
    gdk_draw_arc(map->window, map->style->fg_gc[map->state],
	    TRUE, 0, 0, map->allocation.width, map->allocation.height,
	    0, 64 * 360);
    gdk_gc_set_clip_rectangle(map->style->fg_gc[map->state], NULL);

    return TRUE;
}
This code is from the GTK+ documentation for a GtkDrawingArea, and simply draws a large black circle filling the entire widget. We want to display a map, but take it on faith for now that this is easy to do.

We would also now want to add a signal handler for mouse clicks on the map, and a toolbar with map display options, and so on. These are all done the same way as we did the map handler, so they're not interesting enough to do here.

Note that the main source file doesn't even know about on_map_expose_event()! libglade lends itself to a functional module design. If we put all the signal handlers for map stuff in map.c, we would never need a header file at all. I'm not fond of functional design, however, so the final project has map object related stuff in map.c, and stuff like click handlers are elsewhere, and use map.c to get space coordinates from click coordinates.

Dialog boxes

Earlier I said that dialog boxes could be defined in the interface file, and loaded when they were needed. To demonstrate this, we'll add an About dialog box to the glade project, and a menu item for accessing it. Call the dialog box "about."

We need to bind a signal handler to the signal emitted by our About menu item. The handler will create a new xml tree using the same filename, but a different root node.

gboolean on_about_activate(void)
{
    glade_xml_new(FILENAME, "about");

    return TRUE;
}
The next thing is to handle that Ok button. We'll provide a generic dialog box close function to do this:
gboolean on_close_dialog(GtkWidget *button)
{
    gtk_widget_destroy(gtk_widget_get_toplevel(button));

    return TRUE;
}
This won't work as is, though, since we haven't connected the dialog boxes signals. Adding the appropriate call to on_about_activate() will fix this.
    glade_xml_signal_autoconnect(glade_xml_new(FILENAME, "about"));

If we hadn't declared the about dialog box as modal, we could spawn many about boxes, since each call to glade_xml_new() creates a new widget tree. If this isn't the desired behaviour, you can use a static variable for the dialog box, and gtk_widget_hide() and gtk_widget_show() it instead. This is useful for things like configuration dialog boxes, since the state of the dialog box won't be destroyed.

Custom widgets

Sometimes libglade may not have support for a widget you want, or you may have created your own widget type. There is support for dealing with this in libglade, using glade-build.h. As an example, if we start a new project of a 320x200 window containing nothing but a GtkImage, and a program to load it, we get the following message:

** WARNING **: unknown widget class 'GtkImage'
and a tiny window with "[a GtkImage]" label in it. GtkImage is one of the currently unsupported widget types. Fortunately, it is quite easy to add support for new widget types to a libglade program. We must include glade/glade-build.h, and provide a function for creating the new widget type:
GtkWidget *NewGtkImage(GladeXML *xml, GladeWidgetInfo *info)
{
    int width = 0, height = 0;
    GList *attrlist = info->attributes;
    
    while (attrlist) {
        GladeAttribute *attr = (GladeAttribute *)attrlist->data;

        if (strcmp(attr->name, "image_width") == 0) {
            width = atoi(attr->value);
        } else if (strcmp(attr->name, "image_height") == 0) {
            height = atoi(attr->value);
        }
        attrlist = g_list_next(attrlist);
    }
    if (width == 0 || height == 0) {
        printf("Missing width or height for GtkImage %s\n", info->name);
        return NULL;
    }
    return gtk_image_new(gdk_image_new(GDK_IMAGE_FASTEST,
	    gdk_visual_get_system(), width, height), NULL);
}
There are few things worth noting about this routine. First, it scans through a list of XML attributes searching for image_width and image_height. If we look at the glade source, we see the following information about a GtkImage:
  <widget>
    <class>GtkImage<class>
    <name>image1<name>
    <xalign>0.5<xalign>
    <yalign>0.5<yalign>
    <xpad>0<xpad>
    <ypad>0<ypad>
    <image_width>320<image_width>
    <image_height>200<image_height>
    <image_type>GDK_IMAGE_FASTEST<image_type>
    <image_visual>GDK_VISUAL_SYSTEM<image_visual>
  <widget>
The most important attributes to set are the width and height of the image. It would also be good to check the image_type and image_visual attributes, but this function does not do so.

Next, we need to tell libglade about our custom widget creation code. We do this through a GladeWidgetBuildData array:

GladeWidgetBuildData customwidgets[] = {
    { "GtkImage", NewGtkImage, NULL },
    { NULL, NULL, NULL },
};
The first argument in the structure is the name of the widget. The second is a function that returns a new widget of that type, and the third is a function to pack children into the widget. If the widget has no children, or is a derivative of GtkContainer, you can simply specify NULL. For more information about complicated widget building, see the libglade documentation and source code.

After we call glade_init(), but before we call glade_xml_new(), we now need to call glade_register_widgets(customwidgets) to tell glade about the custom widget handler. Once we've done this, we should get a GtkImage widget created properly. I've provided some code to draw something onto the GtkImage, horribly nonportable code that relies on 24bpp or better. GdkImages aren't very useful.

Dynamic modules

One final aspect of libglade I'd like to demonstrate is dynamic modules. If you specify a signal handler in your Glade interface file, but don't provide a handler in your code, libglade's signal_autoconnect() method will warn you that it cannot find the method, and that signal will go unhandled. This provides a neat hook for dynamic libraries. If you write a dynamic library providing an unhandled signal, and then load it via LD_PRELOAD or using g_module_load(), you can add new behaviour to your application without recompiling the main code.

For example, if we add a signal for click events to the GtkDrawingArea (and change the event mask appropriately!), and then run our code, we will be warned about a missing signal handler. If we then write that signal handler and produce a shared library with gcc -shared, we can load it in:

$ LD_PRELOAD=./library.so ./stage5
and now we handle clicks. We could easily now rewrite library.c to behave in a totally different manner, and not need to recompile stage5 at all, or we could allow users to supply plugins for new functions. This functionality is fairly simple to code without libglade, but the combination of libglade's runtime interface generation and runtime signal handler bindings makes for a powerful combination.

Source files

You can download a tarball of all the sources and the final glade project file
here.
Talk written and presented by Byron Ellacott <bje@apnic.net>