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.
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:
You can see the names we gave our widgets visible in the file.
We're now ready to write code to load this file.
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:
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.
And then, a global variable to hold the interface's xml tree:
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.
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
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, 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.
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.
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.
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:
Next, we need to tell libglade about our custom widget creation
code. We do this through a GladeWidgetBuildData array:
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.
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:
Creating the interface
<?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>
Using the interface
#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.
/* the filename and root node of the interface */
#define FILENAME "../talk.glade"
#define ROOTNODE "rootnode"
/* 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.
Signals
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.
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.
Dialog boxes
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"));
Custom widgets
** 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.
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.
Dynamic modules
$ 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.