[Previous] [Contents] [Next]

2. Getting Started

The FPC GTK Unit is a wrapper for the standard GTK libraries. Therefore the first thing you need to do is make sure that they are installed on your system. If you're running a typical version of Linux (RedHat, S.u.S.E., etc.) chances are they're already installed. Have a look for a file like libgdk-1.2.so.0.9.1 in /usr/lib. Otherwise download the GTK source and install it. You can always get the latest version from ftp://ftp.gtk.org in /pub/gtk. You can also view other sources of GTK information on http://www.gtk.org/. GTK uses GNU autoconf for configuration. Once untar'd, type ./configure --help to see a list of options.

The GTK source distribution also contains the complete source to all of the examples used in this tutorial, along with Makefiles to aid compilation. The bad news (probably) is that they're all in C. The good news is that they're translated into Pascal in this tutorial. If you see examples in the GTK sources that you'd like to translate yourself it may be useful to compare some that have already been translated.

To begin our introduction to GTK, we'll start with the simplest program possible. This program will create a 200x200 pixel window and has no way of exiting except to be killed by using the shell (Ctrl-C).

program base;

uses gtk2;

var
   window : PGtkWidget;

begin
   gtk_init(@argc, @argv);

   window := gtk_window_new(GTK_WINDOW_TOPLEVEL);
   gtk_widget_show(window);

   gtk_main();
end.

Assuming the file is saved as base.pas you can compile the above program using:

$ fpc base.pas

All programs will of course use Gtk2 which declares the variables, functions, structures, etc. that will be used in your GTK application.

The next line:

gtk_init(@argc, @argv);

calls the function gtk_init(gint *argc, gchar ***argv) which will be called in all GTK applications. This sets up a few things for us such as the default visual and color map and then proceeds to call gdk_init(gint *argc, gchar ***argv). This function initializes the library for use, sets up default signal handlers, and checks the arguments passed to your application on the command line, looking for one of the following:

It removes these from the argument list, leaving anything it does not recognize for your application to parse or ignore. This creates a set of standard arguments accepted by all GTK applications.

The next two lines of code create and display a window.

window := gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_widget_show(window);

The GTK_WINDOW_TOPLEVEL argument specifies that we want the window to undergo window manager decoration and placement. Rather than create a window of 0x0 size, a window without children is set to 200x200 by default so you can still manipulate it.

The gtk_widget_show() function lets GTK know that we are done setting the attributes of this widget, and that it can display it.

The last line enters the GTK main processing loop.

gtk_main();

gtk_main() is another call you will see in every GTK application. When control reaches this point, GTK will sleep waiting for X events (such as button or key presses), timeouts, or file IO notifications to occur. In our simple example, however, events are ignored.


2.1 Hello World in GTK

Now for a program with a widget (a button). It's the classic hello world a la GTK.

Hello GTK Example

program helloworld;

uses gtk2, gdk2, glib2;

{ This is a callback function. The data arguments are ignored in this
  example. More on callbacks later }

procedure hello (widget : PGtkWidget; data : gpointer); cdecl;
begin
   writeln('Hello World!');
end;

function delete_event (widget : PGtkWidget; event : PGdkEvent;
                       data : gpointer) : gboolean; cdecl;
  { If you return false from 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. }

begin
   writeln('delete event occurred');

   { Change true to false and the main window will be destroyed with
     a "delete-event". }
   delete_event := true;
end;


{ Another callback }

procedure destroy (widget : PGtkWidget; data : gpointer); cdecl;
begin
   gtk_main_quit();
end;

var
   window, button : PGtkWidget;

begin
   { 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 nil and is ignored
     in the callback function. }
   g_signal_connect(G_OBJECT(window), 'delete-event',
                    G_CALLBACK(@delete_event), nil);

   { 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. }
   g_signal_connect(G_OBJECT(window), 'destroy',
                    G_CALLBACK(@destroy), nil);

   { Sets the border width of the window. }
   gtk_container_set_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
     procedure hello() passing it nil as its argument. }
   g_signal_connect(G_OBJECT(button), 'clicked',
                    G_CALLBACK(@hello), nil);

   { 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. }
   g_signal_connect_swapped(button, 'clicked',
                            G_CALLBACK(@gtk_widget_destroy),
                            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();
end.

2.2 Compiling Hello World

To compile it use (assuming the file is called hellogtk.pas):

fpc helloworld.pas

The libraries that are usually linked in are:


2.3 Theory of Signals and Callbacks

In version 2.0, the signal system has been moved from GTK to GLib, therefore the functions and types explained in this section have a "g_" prefix rather than a "gtk_" prefix. We won't go into details about the extensions which the GLib 2.0 signal system has relative to the GTK 1.2 signal system.

Before we look in detail at hellogtk, we'll discuss signals and callbacks. GTK is an event driven toolkit, which means it will sleep in gtk_main() until an event occurs and control is passed to the appropriate function.

This passing of control is done using the idea of ``signals''. (Note that these signals are not the same as the UN*X system signals, and are not implemented using them, although the terminology is almost identical.) When an event occurs, such as the press of a mouse button, the appropriate signal will be ``emitted'' by the widget that was pressed. This is how GTK does most of its useful work. There are signals that all widgets inherit, such as ``destroy'', and there are signals that are widget specific, such as ``toggle'' on a toggle button.

To make a button perform an action, we set up a signal handler to catch these signals and call the appropriate function. This is done by using a function such as:

function g_signal_connect (object : gpointer;
                           name : pgchar;
                           func : GCallback;
                           func_data : gpointer) : gulong;

where the first argument is the widget which will be emitting the signal, and the second the name of the signal you wish to catch. The third is the function you wish to be called when it is caught, and the fourth, the data you wish to have passed to this function.

The function specified in the third argument is called a callback function, and should generally be of the form

procedure callback_proc (widget : PGtkWidget;
                         ...  { other signal arguments }
                         callback_data : gpointer);

where the first argument will be a pointer to the widget that emitted the signal, and the last a pointer to the data given as the last argument to the gtk_signal_connect() function as shown above.

Note that the above form for a signal callback function declaration is only a general guide, as some widget specific signals generate different calling parameters.

Another call used in the helloworld example, is:

function g_signal_connect_swapped (object : gpointer;
                                   name : pgchar;
                                   func GCallback;
                                   callback_data :gpointer) : gulong;

g_signal_connect_swapped() is the same as g_signal_connect() except that the the instance on which the signal is emitted and data will be swapped when calling the handler. So when using this function to connect signals, the callback should be of the form

procedure callback_func (callback_data : gpointer;
                         ...  { other signal arguments }
                         widget : PGtkWidget);

where the object is usually a widget. We usually don't setup callbacks for g_signal_connect_swapped() however. They are usually used to call a GTK function that accepts a single widget or object as an argument, when a signal is emitted on some other object. In the helloworld example, we connect ot the "clicked" signal on the button, but call gtk_widget_destroy() on the window.

If your callbacks need additional data, use g_signal_connect() instead of g_signal_connect_swapped().


2.4 Events

In addition to the signal mechanism described above, there is a set of events that reflect the X event mechanism. Callbacks may also be attached to these events. These events are:

In order to connect a callback to one of these events, you use the function gtk_signal_connect(), as described above, using one of the above event names as the name parameter. The callback for events has a slightly different form than that for signals:

function callback_func (widget : PGtkWidget;
                        event : PGdkEvent;
                        callback_data : gpointer) : gint;

GdkEvent is a C union structure whose type will depend upon which of the above events has occurred. In order for us to tell which event has been issued each of the possible alternatives has a type parameter which reflects the event being issued. The other components of the event structure will depend upon the type of the event. Possible values for the type are:

GDK_NOTHING
GDK_DELETE
GDK_DESTROY
GDK_EXPOSE
GDK_MOTION_NOTIFY
GDK_BUTTON_PRESS
GDK_2BUTTON_PRESS
GDK_3BUTTON_PRESS
GDK_BUTTON_RELEASE
GDK_KEY_PRESS
GDK_KEY_RELEASE
GDK_ENTER_NOTIFY
GDK_LEAVE_NOTIFY
GDK_FOCUS_CHANGE
GDK_CONFIGURE
GDK_MAP
GDK_UNMAP
GDK_PROPERTY_NOTIFY
GDK_SELECTION_CLEAR
GDK_SELECTION_REQUEST
GDK_SELECTION_NOTIFY
GDK_PROXIMITY_IN
GDK_PROXIMITY_OUT
GDK_DRAG_ENTER
GDK_DRAG_LEAVE
GDK_DRAG_MOTION
GDK_DRAG_STATUS
GDK_DROP_START
GDK_DROP_FINISHED
GDK_CLIENT_EVENT
GDK_VISIBILITY_NOTIFY
GDK_NO_EXPOSE
GDK_SCROLL
GDK_WINDOW_STATE
GDK_SETTING

So, to connect a callback to one of these events we would use something like:

   g_signal_connect(button, 'button_press_event',
                    G_CALLBACK(@button_press_callback), nil);

This assumes that button is a Button widget. Now, when the mouse is over the button and a mouse button is pressed, the function button_press_callback() will be called. This function may be declared as:

function button_press_callback (widget : PGtkWidget;
                                event : PGdkEventButton;
                                data : gpointer) : gboolean; cdecl;

Note that we can declare the second argument as type PGdkEventButton as we know what type of event will occur for this function to be called.

The value returned from this function indicates whether the event should be propagated further by the GTK event handling mechanism. Returning true indicates that the event has been handled, and that it should not propagate further. Returning false continues the normal event handling. See the section on Advanced Event and Signal Handling for more details on this propagation process.

For details on the GdkEvent data types, see the appendix entitled GDK Event Types.

The GDK selection and drag-and-drop APIs also emit a number of events which are reflected in GTK by the signals. See Signals on the source widget and Signals on the destination widget for details on the signatures of the callback functions for these signals:


2.5 Stepping Through Hello World

Now that we know the theory behind this, let's clarify by walking through the example hello world program.

Here is the callback function that will be called when the button is 'clicked'. We ignore both the widget and the data in this example, but it is not hard to do things with them. The next example will use the data argument to tell us which button was pressed. Note the use of cdecl, because our callback procedure is called by a C function through gtk_signal_connect() and we have to tell the compiler that the C calling convention will be used. If you forget to do this the program will sometimes compile, but then will crash with a runtime error.

procedure hello (widget : PGtkWidget; data : gpointer); cdecl;
begin
   writeln('Hello World!');
end;

The next callback is a bit special. The "delete-event" occurs when the window manager sends this event to the application. We have a choice here as to what to do about these events. We can ignore them, make some sort of response, or simply quit the application.

The value you return in this callback lets GTK know what action to take. By returning true, we let it know that we don't want to have the 'destroy' signal emitted, keeping our application running. By returning false, we ask that 'destroy' be emitted, which in turn will call our 'destroy' signal handler.

function delete_event (widget : PGtkWidget; event : PGdkEvent;
                       data : gpointer) : gboolean; cdecl;
begin
   writeln('delete event occurred');
   delete_event := true;
end;

Here is another callback function which causes the program to quit by calling gtk_main_quit(). This function tells GTK that it is to exit from gtk_main() when control is returned to it.

procedure destroy (widget : PGtkWidget; data : gpointer); cdecl;
begin
   gtk_main_quit();
end;

The next part declares pointers to a structure of type GtkWidget. These are used below to create a window and a button.

var
   window, button : PGtkWidget;

I assume you know about the main function... yes, as with other applications, all GTK applications will also have one of these. We start by calling gtk_init() again. As before, this initializes the toolkit, and parses the arguments found on the command line. Any argument it recognizes from the command line, it removes from the list, and modifies argc and argv to make it look like they never existed, allowing your application to parse the remaining arguments.

begin
   gtk_init(@argc, @argv);

Create a new window. This is fairly straightforward. Memory is allocated for the GtkWidget so window now points to a valid structure. It sets up a new window, but it is not displayed until we call gtk_widget_show(window) near the end of our program.

   window := gtk_window_new(GTK_WINDOW_TOPLEVEL);

Here are two examples of connecting a signal handler to an object, in this case, the window. Here, the "delete-event" and "destroy" signals are caught. The first is emitted when we use the window manager to kill the window. The second is emitted when we use the gtk_widget_destroy() call passing in the window widget as the object to destroy, or when, in the "delete-event" handler, we return false.

The G_CALLBACK is a macro that performs type casting and checking for us, as well as aid the readability of the code. Note that we supply function/procedure references (using the @ symbol) to the G_CALLBACK macro.

   g_signal_connect(window, 'delete-event',
                    G_CALLBACK(@delete_event), nil);
   g_signal_connect(window, 'destroy',
                    G_CALLBACK(@destroy), nil);

The next function is used to set an attribute of a container object. This just sets the window so it has a blank area along the inside of it 10 pixels wide where no widgets will go. There are other similar functions which we will look at in the section on Setting Widget Attributes

And again, GTK_CONTAINER is a macro to perform type casting.

   gtk_container_set_border_width(GTK_CONTAINER(window), 10);

This call creates a new button. It allocates space for a new GtkWidget structure in memory, initializes it, and makes the button pointer point to it. It will have the label 'Hello World!' on it when displayed.

   button := gtk_button_new_with_label('Hello World!');

Here, we take this button, and make it do something useful. We attach a signal handler to it so that when it emits the 'clicked' signal, our hello() function is called. The data is ignored, so we simply pass in nil to the hello() callback function. Obviously, the 'clicked' signal is emitted when we click the button with our mouse pointer.

   g_signal_connect(button, 'clicked',
                    G_CALLBACK(@hello), nil);

We are also going to use this button to exit our program. This will illustrate how the 'destroy' signal may come from either the window manager, or our program. When the button is 'clicked', same as above, it calls first the hello() callback function, and then this one in the order they are set up. You may have as many callback functions as you need, and all will be executed in the order you connected them. Because the gtk_widget_destroy() function accepts only a PGtkWidget widget as an argument, we use the g_signal_connect_swapped() function here instead of straight g_signal_connect().

   g_signal_connect_swapped(button, 'clicked',
                            G_CALLBACK(@gtk_widget_destroy),
                            window);

This is a packing call, which will be explained in depth later in 4. Packing Widgets. But it is fairly easy to understand. It simply tells GTK that the button is to be placed in the window where it will be displayed. Note that a GTK container can only contain one widget. There are other widgets, that are described later, which are designed to layout multiple widgets in various ways.

   gtk_container_add(GTK_CONTAINER(window), button);

Now we have everything set up the way we want it to be. With all the signal handlers in place, and the button placed in the window where it should be, we ask GTK to 'show' the widgets on the screen. The window widget is shown last so the whole window will pop up at once rather than seeing the window pop up, and then the button form inside of it. Although with such a simple example, you'd never notice.

   gtk_widget_show(button);

   gtk_widget_show(window);

And of course, we call gtk_main() which waits for events to come from the X server and will call on the widgets to emit signals when these events come.

   gtk_main();

After gtk_main)quit() is called the control returns to the line following gtk_main() which, in our example, is:

end.

Now, when we click the mouse button on a GTK button, the widget emits a "clicked" signal. In order for us to use this information, our program sets up a signal handler to catch that signal, which dispatches the function or procedure of our choice. In our example, when the button we created is "clicked", the hello() procedure is called with a nil argument, and then the next handler for this signal is called. This calls the gtk_widget_destroy() function, passing it the window widget as its argument, destroying the window widget. This causes the window to emit the "destroy" signal, which is caught, and calls our destroy() callback function, which simply exits GTK.

Another course of events is to use the window manager to kill the window, which will cause the "delete-event" to be emitted. This will call our "delete-event" handler. If we return true here, the window will be left as is and nothing will happen. Returning false will cause GTK to emit the 'destroy' signal, which of course calls the 'destroy' callback, exiting GTK.


[Previous] [Contents] [Next]