GTK+

GUI Basics

Writing a GTK+ based GUI application is in essence extremely simple, and we'll use the Hello World example from Ian Main's excellent GTK+ tutorial, which is a very useful guide to writing gnome applications. But first we'll talk about the basic philosophy behind GTK+.

GTK+ is a container based toolkit, meaning you don't specify where the widget is, but you specify in what container it is. Some widgets, such as a window or a frame or a button, are containers that hold only one other widget. For example a button with a label is actually a button into which we added a label widget. If you need to put more widgets into that container, you will need to add another container into them, one that holds more then one widget such as a horizontal box.

In fact most layout of windows is usually done with containers such as horizontal boxes, vertical boxes and tables, those are the most important to learn. A horizontal box is a widget that you can add several widgets into and they will be added in a horizontal row. The height of the horizontal box is the height of the highest widget added, and the length is the length of all widgets combined. Vertical box behaves exactly the same, except that it's vertical instead of horizontal. A table can take in widgets at different rows and columns.

Figure 2-1. Example window hierarchy

GTK+ Object Model

Gtk's object model is an object oriented framework for C. It includes singular object inheritance, virtual methods, signals, runtime object modification, runtime type checking, and other goodies. While writing a GTK+ object is more involved then say writing an object in something like Java, it does have many advantages. GTK+ is an object model which doesn't require inheritance for most things you do with objects. For one, since methods are just functions that take the pointer to the object as the first argument, it's easy to write more methods in your own code, which are missing in the original object.

Data on Objects

There is a way to store arbitrary named data in objects to extend the object. This is done with the method, gtk_object_set_data (or gtk_object_set_user_data for a single unnamed pointer). To retrieve data, one uses gtk_object_get_data. Example:

GtkObject *obj;
void *some_pointer;
...
/*here we set "some_data" data on obj to point to some_pointer*/
gtk_object_set_data(obj,"some_data",some_pointer);
...
/*retrieve pointer to some_data from obj and store it in
  some_pointer*/
some_pointer = gtk_object_get_data(obj,"some_data");

The pointer can be a pointer to anything since it's manipulated as a (void *).

GTK+/GNOME Naming Conventions

Both GTK+ and GNOME use the same naming convention when naming objects and functions. GTK+ uses a prefix of gtk_ for functions, and Gtk for objects, and GNOME uses gnome_ and Gnome. When a function is a method for an object, the name (lower case) is appended to the prefix. For example the button object is named GtkButton (that is the name of the C struct holding the data for the object), and say the "new" method for GtkButton is then called gtk_button_new. Macros associated with objects use the same naming convention as functions, but are all capitalized. For example a macro that casts an object to a GtkButton is called GTK_BUTTON. There are exceptions, notably the type checking macro, which is called GTK_IS_BUTTON for GtkButton.

Using GTK+ Methods

Since GTK+ is object oriented, it uses inheritance for it's widgets. For example GtkHBox and GtkVBox are derived from GtkBox. And thus you can use any GtkBox method on a GtkVBox or GtkHBox. However you need to cast the GtkVBox object to GtkBox before you call the function. This could be done with standard C casts such as:

GtkVBox *vbox;
...
gtk_box_pack_start((GtkBox *)vbox, ...);
...

This would work, however it is unsafe. GTK+ provides a mechanism of checking the types, so that it can warn you if you are casting an object which does not derive from the object you are casting to, or if you try to cast a NULL pointer. The macro is all capital name of the widget. For example the above code snippet would be

GtkVBox *vbox;
...
gtk_box_pack_start(GTK_BOX(vbox), ...);
...

GNOME uses the exact same form so anything you learn about GTK+ can be used for GNOME widgets, you just replace the GTK prefix with GNOME.

Example Hello World Program

Here is the promised example code for the hello world program. It doesn't use any advanced containers, just a window and a button onto which a label is added. It illustrates the basic workings of a GUI program written in GTK+. Don't be scared by it's size, it's mostly comments.

/* example-start helloworld helloworld.c */

#include <gtk/gtk.h>

/* this is a callback function. the data arguments are ignored in
 * this example.. More on callbacks below. */
void
hello (GtkWidget *widget, gpointer data)
{
	g_print ("Hello World\\n");
}

gint
delete_event(GtkWidget *widget, GdkEvent *event, gpointer data)
{
	g_print ("delete event occurred\\n");
	/* if you return FALSE in the "delete_event" signal
	 * handler, GTK will emit the "destroy" signal.
	 * Returning TRUE means you don't want the window
	 * to be destroyed. This is useful for popping up
	 * 'are you sure you want to quit ?' type dialogs. */

	/* Change TRUE to FALSE and the main window will
	 * be destroyed with a "delete_event". */

	return (TRUE);
}

/* another callback */
void
destroy (GtkWidget *widget, gpointer data)
{
	gtk_main_quit ();
}

int
main (int argc, char *argv[])
{
	/* GtkWidget is the storage type for widgets */
	GtkWidget *window;
	GtkWidget *button;

	/* this is called in all GTK applications.
	 * arguments are parsed from the command line and
	 * are returned to the application. */
	gtk_init (&argc, &argv);

	/* create a new window */
	window = gtk_window_new (GTK_WINDOW_TOPLEVEL);

	/* when the window is given the "delete_event" signal
	 * (this is given by the window manager, usually by
	 * the 'close' option, or on the titlebar), we ask
	 * it to call the delete_event () function as defined
	 * above.  The data passed to the callback function
	 * is NULL and is ignored in the callback function. */
	gtk_signal_connect (GTK_OBJECT (window), "delete_event",
			    GTK_SIGNAL_FUNC (delete_event), NULL);

	/* here we connect the "destroy" event to a signal
	 * handler. This event occurs when we call
	 * gtk_widget_destroy() on the window, or if we
	 * return 'FALSE' in the "delete_event" callback. */
	gtk_signal_connect (GTK_OBJECT (window), "destroy",
			    GTK_SIGNAL_FUNC (destroy), NULL);

	/* sets the border width of the window. */
	gtk_container_border_width (GTK_CONTAINER (window), 10);

	/* creates a new button with the label "Hello World". */
	button = gtk_button_new_with_label ("Hello World");

	/* When the button receives the "clicked" signal, it
	 * will call the function hello() passing it NULL as
	 * it's argument.  The hello() function is defined
	 * above. */
	gtk_signal_connect (GTK_OBJECT (button), "clicked",
			    GTK_SIGNAL_FUNC (hello), NULL);

	/* This will cause the window to be destroyed by
	 * calling gtk_widget_destroy(window) when "clicked".
	 * Again, the destroy signal could come from here,
	 * or the window manager. */
	gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
				   GTK_SIGNAL_FUNC (gtk_widget_destroy),
				   GTK_OBJECT (window));

	/* this packs the button into the window
	 * (a gtk container). */
	gtk_container_add (GTK_CONTAINER (window), button);

	/* the final step is to display this newly created
	 * widget... */
	gtk_widget_show (button);

	/* and the window */
	gtk_widget_show (window);

	/* all GTK applications must have a gtk_main().
	 * Control ends here and waits for an event to occur
	 * (like a key press or mouse event). */
	gtk_main ();

	return 0;
}
/* example-end */

For more information look at the header files in <prefix>/include/gtk/ and <prefix>/include/gdk/ and at the documentation on the www.gtk.org web site.