GTK+ Basics

Table of Contents
Whirlwind Tour of GTK+
Containers And Widget Layout
Widget Concepts
The Main Loop

This chapter does the usual Hello, World to give you an overview of GTK+, then moves on to discuss some of the essential details you need to start developing GTK+ applications.

If you've already read the GTK+ Tutorial from http://www.gtk.org/, or the book Developing Linux Applications with Gtk+ and Gdk (also from New Riders), you may be able to skip or just skim this chapter. If you haven't used GTK+ before, this chapter is going to be very fast; read with care.

Whirlwind Tour of GTK+

GTK+'s object-oriented coding style, clean design, and carefully followed API-naming conventions make programs simple to write and simple to understand. To make the point, here's a complete "Hello, World" in GTK+; most likely you can guess what 80% of the code does with no GTK+ experience whatsoever.

A Complete Hello, World

Figure 1. Hello, World


#include <gtk/gtk.h>

static gint delete_event_cb(GtkWidget* w, GdkEventAny* e, gpointer data);
static void button_click_cb(GtkWidget* w, gpointer data);

int 
main(int argc, char* argv[])
{
  GtkWidget* window;
  GtkWidget* button;
  GtkWidget* label;

  gtk_init(&argc, &argv);  

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);

  button = gtk_button_new();

  label  = gtk_label_new("Hello, World!");

  gtk_container_add(GTK_CONTAINER(button), label);
  gtk_container_add(GTK_CONTAINER(window), button);

  gtk_window_set_title(GTK_WINDOW(window), "Hello");
  gtk_container_set_border_width(GTK_CONTAINER(button), 10);
  
  gtk_signal_connect(GTK_OBJECT(window),
                     "delete_event",
                     GTK_SIGNAL_FUNC(delete_event_cb),
                     NULL);

  gtk_signal_connect(GTK_OBJECT(button),
                     "clicked",
                     GTK_SIGNAL_FUNC(button_click_cb),
                     label);

  gtk_widget_show_all(window);

  gtk_main();

  return 0;
}

static gint 
delete_event_cb(GtkWidget* window, GdkEventAny* e, gpointer data)
{
  gtk_main_quit();
  return FALSE;
}

static void 
button_click_cb(GtkWidget* w, gpointer data)
{
  GtkWidget* label;
  gchar* text;
  gchar* tmp;

  label = GTK_WIDGET(data);

  gtk_label_get(GTK_LABEL(label), &text);

  tmp = g_strdup(text);

  g_strreverse(tmp);

  gtk_label_set_text(GTK_LABEL(label), tmp);

  g_free(tmp);
}



Compiling Hello, World

GTK+ comes with a shell script called gtk-config; this script is created when GTK+ is built. Its purpose is to report the compiler flags you need to compile GTK+ programs. The following shell session demonstrates its features:


$ gtk-config --version
1.2.0
$ gtk-config --prefix
/home/hp/local
$ gtk-config --exec-prefix
/home/hp/local
$ gtk-config --libs       
-L/home/hp/local/lib -L/usr/X11R6/lib -lgtk -lgdk -rdynamic -lgmodule -lglib -ldl -lXext -lX11 -lm
$ gtk-config --libs gthread
-L/home/hp/local/lib -L/usr/X11R6/lib -lgtk -lgdk -rdynamic -lgmodule -lgthread -lglib -lpthread -ldl -lXext -lX11 -lm
$ gtk-config --cflags 
-I/usr/X11R6/include -I/home/hp/local/lib/glib/include -I/home/hp/local/include
$ 

If you're using a Bourne shell variant, such as bash, you can use backticks (not single quotes!) to execute gtk-config and substitute its output. A simple Makefile for compiling Hello, World might look like this:


CC=gcc

all: hello.c
        $(CC) `gtk-config --libs` `gtk-config --cflags` -o hello hello.c

clean:
        /bin/rm -f *.o *~

Of course, this Makefile is far too simple for real-world applications; the chapter called Creating Your Source Tree describes how to set up a more realistic build using automake and autoconf.

gtk-config allows you to locate GTK+ on the user's system, instead of hard-coding a location in your Makefile. It also comes in handy if you have two versions of GTK+ on your own system; if you install them each in a dedicated directory tree, you can choose one or the other by placing the correct gtk-config in your shell's search path.

How It Works

This simple program contains all the essential elements of a GTK+ application. It doesn't contain any Gnome features; but since Gnome builds on GTK+, the same concepts will apply.

Initialization

First, GTK+ must be initialized:


  gtk_init(&argc, &argv);

This call connects to an X server, and parses some default arguments understood by all GTK+ programs. Parsed arguments are removed from argv, and argc is decremented accordingly. gtk_init() also registers a "cleanup function" using atexit(). In practice, this is only important when you fork(); the child process must exit with _exit() rather than exit() to avoid shutting down GTK+ in the parent.

Widgets

Next, any program will have some user interface elements. In the X tradition, these are called widgets. All widgets are subclasses of the GtkWidget base class, so you can use a GtkWidget* to refer to them. (Since C has no native support for object inheritance, GTK+ has its own mechanism---the chapter called The GTK+ Object and Type System describes this.)


  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);

  button = gtk_button_new();

  label  = gtk_label_new("Hello, World!");

  gtk_container_add(GTK_CONTAINER(button), label);
  gtk_container_add(GTK_CONTAINER(window), button);

  gtk_window_set_title(GTK_WINDOW(window), "Hello");
  gtk_container_set_border_width(GTK_CONTAINER(button), 10);


Each widget has a function called gtk_widgetname_new(), analagous to a constructor in C++ or Java. This function allocates a new object, initializes it, and returns a pointer to it. All of the _new() routines return a GtkWidget*, even though they allocate a subclass; this is for convenience.

Once you have a GtkWidget* representing an object, you can manipulate the object using its methods. All GTK+ widget functions begin with the name of the type they operate on, and accept a pointer to that type as the first argument. In the above code, gtk_container_add() accepts a GtkContainer* as the first argument. The macro GTK_CONTAINER() casts the GtkWidget*, and also performs a runtime type check. Casting is required because C does not understand the inheritance relationship.

As you might imagine, GtkButton and GtkWindow are both subclasses of GtkContainer. A GtkContainer can hold any other widget inside. The code creates a toplevel window, places a button inside it, and places a label (line of text) inside the button. Then it sets the window title, and adds a small cosmetic border around the button.

Signals

Next, you'll want to arrange to respond when users manipulate the widgets. In this simple application, there are two interesting things that can happen: the user can click the button, or close the window using a window manager decoration. Widgets (actually, all GtkObjects) emit signals when something interesting happens a program might want to respond to. To respond to a signal, you "connect a callback" to it---i.e., register a function to be called when the signal is emitted. Here's that code again:


  gtk_signal_connect(GTK_OBJECT(window),
                     "delete_event",
                     GTK_SIGNAL_FUNC(delete_event_cb),
                     NULL);

  gtk_signal_connect(GTK_OBJECT(button),
                     "clicked",
                     GTK_SIGNAL_FUNC(button_click_cb),
                     label);


gtk_signal_connect() specifies the GtkObject to monitor, which signal to connect to, the callback to connect, and finally a user_data argument---this is an arbitrary gpointer which will be passed to the callback. The macro GTK_SIGNAL_FUNC() casts the callback to a standard function signature; since callbacks have a variety of type signatures, the alternative would be dozens of gtk_signal_connect() variants.

GTK+ performs copious runtime sanity checking; the GTK_OBJECT() macro includes a runtime type check in addition to a C cast, and gtk_signal_connect() will verify that the object can actually emit the signal you've specified.

Entering the Main Loop

Once everything is set up, two steps remain: you need to show the window on the screen, and wait for user input.


  gtk_widget_show_all(window);

  gtk_main();

  return 0;


gtk_widget_show_all() recursively calls gtk_widget_show() on a container and its children. The following code would have the same effect in this case:


  gtk_widget_show(label);
  gtk_widget_show(button);
  gtk_widget_show(window);

It's necessary to show each and every widget that you want to appear on the screen. The opposite operation is called gtk_widget_hide(); widgets start their life hidden, and can be re-hidden/re-shown any number of times. It's good practice to show all child widgets before showing the outermost container; otherwise, the user will see the container appear first, followed by its children. Widgets are not actually visible on the screen until their parent container is shown---the exception to the rule is GtkWindow, since it has no parent.

Once your widgets have been shown, you want to wait for the user to do something with them. gtk_main() enters the GTK+ main loop; the main loop is event-driven. That is, user actions trigger events which generally result in signals being emitted and your callbacks being called. gtk_main() blocks indefinitely, waiting for and responding to user input. The main loop is described in more detail in the section called The Main Loop. Events and their relation to the main loop are described in the section called Events in the chapter called GDK Basics.

More on Signals and Callbacks

If either of the signals the program connects to is emitted, the corresponding callback is called. Our "delete_event" callback ends the gtk_main() event loop by calling gtk_main_quit(); this causes gtk_main() to return, ending the program. The "clicked" callback replaces the text from the label with the same text in reverse. Notice that the label was passed to the callback as the user_data parameter to gtk_signal_connect().

A common mistake is to assume that all signals use the same kind of callback---not true. Each signal requires a callback with a particular type signature and particular behavior. The "clicked" signal has a very common callback type; its callback receives a pointer to the widget emitting the signal and any user_data provided by the programmer. This callback must return void or memory corruption is likely to occur.

"delete_event", on the other hand, is something of a special case. It accepts three arguments; the first and last are analagous to "clicked", while the second is a pointer to the event which triggered the signal (events are messages from X to the application, reporting mouse movements, key presses, and the like). The "delete_event" callback returns a "magic" value---if FALSE is returned, GTK+ will destroy the window; if TRUE is returned, GTK+ will do nothing. Return TRUE if you need to do something other than destroy the window; for example, you might want to warn the user about an unsaved document.

Widget header files are the best quick reference for callback signatures. The "class structure" for the widget will have a space for a default signal handler; your handler should be modeled on the default one. For example, in gtk/gtkbutton.h the GtkButton class struct looks like this:


struct _GtkButtonClass
{
  GtkBinClass        parent_class;
  
  void (* pressed)  (GtkButton *button);
  void (* released) (GtkButton *button);
  void (* clicked)  (GtkButton *button);
  void (* enter)    (GtkButton *button);
  void (* leave)    (GtkButton *button);
};

the chapter called The GTK+ Object and Type System explains exactly what a class struct is for; for now, just pay attention to the function pointers, and note that they correspond to signals. To get from this:


  void (* clicked)  (GtkButton *button);

to this:


  static void button_click_cb(GtkWidget* w, gpointer data);

simply add a gpointer data to the class struct function's signature. In Hello, World I've also changed the type from GtkButton* to GtkWidget*; this is common, since it can be more convenient to have a GtkWidget*. The argument will always be the GtkButton emitting the signal.

Another example may be useful; here is "delete_event" from gtk/gtkwidget.h:


  gint (* delete_event)            (GtkWidget          *widget,
                                    GdkEventAny        *event);

and the callback from Hello, World:


static gint delete_event_cb(GtkWidget* w, GdkEventAny* e, gpointer data);

That's all there is to it. You can write simple GTK+ applications using only the information presented in this section. GTK+ and Gnome are powerful application development tools because you can think about real functionality, instead of struggling to get a window on the screen.