[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. (Assuming you actually need to install).

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.

Next, of course, you need to have the Free Pascal Compiler installed. If you're reading this you've probably already done that. Otherwise download it from that Free Pascal website:
    http://www.freepascal.org/
If you're running Linux installation is just a matter of downloading two rpm's, becoming root, and typing:

 # rpm -i fpc-1.0.6-1.i386.rpm
 # rpm -i fpc-docs-1.0.6-1.i386.rpm

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).
Skeleton Window Example

 PROGRAM base;

 USES gtk;

 { -------------------------------Global Variable---------------------------- }
 VAR window : pGtkWidget;
 { ---------------------------------Main Program------------------------------ }
 BEGIN gtk_init(@argc, @argv); { Initialise GTK }
window := gtk_window_new(GTK_WINDOW_TOPLEVEL); { Create a new window }
gtk_widget_show(window);
gtk_main();
 END. { ----------------------------------Main Program------------------------------ }

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

 $ fpc base.pas

When you type that command you'll be hoping for a response like this:

 fpc base.pas
 Free Pascal Compiler version 1.0.6 [2002/05/23] for i386
 Copyright (c) 1993-2002 by Florian Klaempfl
 Target OS: Linux for i386
 Compiling base.pas
 Assembling base
 Linking base
 15 Lines compiled, 1.8 sec

 Compilation finished at Tue Jul 8 10:33:37

However, on some systems you might get a response like this:

 /usr/bin/ld: cannot find -lgdk
 base.pas(55) Error: Error while linking
 Closing script ppas.sh

The FPC is looking for libraries called libgdk.so, libgtk.so, and libglib.so. If you take a look in the /usr/lib directory you'll find that these libraries have names like libgdk-1.2.so.0.9.1. To remedy this it's a simple matter of becoming root and creating a few soft links. First, find out what your libraries are actually called (they might be the same as shown here if you're running S.u.S.E. 8.1). Change directory to /usr/lib and then adapt the following to suit:

 linux:/usr/lib # ln -s libgdk-1.2.so.0.9.1 libgdk.so
 linux:/usr/lib # ln -s libgtk-1.2.so.0.9.1 libgtk.so
 linux:/usr/lib # ln -s libglib-1.2.so.0.0.10 libglib.so
 linux:/usr/lib # /sbin/ldconfig
 linux:/usr/lib #

The program should now compile and you should get a response similar to the one shown earlier.

Let's go through the program step by step. All programs will of course use gtk which declares the variables, functions, structures, etc. that will be used in your GTK application.

First we declare our one global variable which is a pointer to a GtkWidget - our window.

 VAR window : pGtkWidget;

We're ready to begin the main program. Its first line is:

 gtk_init(@argc, @argv);

This calls the C 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 colour 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:

--gtk-module--g-fatal-warnings--gtk-debug--gtk-no-debug
--gdk-debug--gdk-no-debug--display--sync
--no-xshm--name--class 

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); { Create a new window }
 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.

[Previous] [Contents] [Next]

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 hello_world;

 USES gtk, gdk, glib;

 { ---------------------------------hello-------------------------------- }

 PROCEDURE hello( widget : pGtkWidget ; data : gpointer ); cdecl;
 { This is a callback function. The data arguments are ignored in this example. More on callbacks later }
 BEGIN writeln('Hello World!');  END; { ------------------------------hello------------------------------ }

 { ----------------------------delete_event------------------------ }
 FUNCTION delete_event( widget : pGtkWidget ; event : pGdkEvent ; data : gpointer ) :
      gint; 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');
{ Returning FALSE from the function, as we do here, causes the --destroy-- signal to be emitted. }
delete_event := 0; { i.e. FALSE }
 END; { -----------------------------delete_event-------------------------- }

 { -----------------------------destroy---------------------------- }
 PROCEDURE destroy( widget : pGtkWidget ; data : gpointer ); cdecl;
 { Another callback - this one will kill the app }
 BEGIN gtk_main_quit();  END; { -------------------------------destroy------------------------------- }

 { -----------------------Global Variables------------------------ }
 VAR window, button : pGtkWidget; { GtkWidget is the storage type for widgets }  { --------------------------Main Program-------------------------- }
 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);

window := gtk_window_new(GTK_WINDOW_TOPLEVEL); { Create a new window }

{ 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. }

gtk_signal_connect(GTK_OBJECT(window), 'delete_event',
      GTK_SIGNAL_FUNC(@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. }

gtk_signal_connect(GTK_OBJECT(window), 'destroy',
      GTK_SIGNAL_FUNC(@destroy), NIL);

{ 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() -defined above- passing it NIL as its argument. }
gtk_signal_connect(GTK_OBJECT(button), 'clicked',
      GTK_SIGNAL_FUNC(@hello), NIL);

{ 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. { ----------------------------Main Program---------------------------- }

 

[Previous] [Contents] [Next]

2.2 Compiling Hello World

Hopefully you've already compiled the first example and have a working setup - if not you should refer back should this compilation cause you problems. Assuming the file is called hellogtk.pas use the following to compile it:

 fpc hellogtk.pas

The libraries that are usually linked in are:

[Previous] [Contents] [Next]

2.3 Theory of Signals and Callbacks

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 toggled 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 gtk_signal_connect( object : pGtkObject ; name : pgchar ;
      func : GtkSignalFunc ; func_data : gpointer ) : gint;

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, and should generally be of the form

 PROCEDURE callback_proc( widget : pGtkWidget ; callback_data : gpointer );

where the first argument will be a pointer to the widget that emitted the signal, and the second 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. For example, the CList select_row signal provides both row and column parameters.

[Previous] [Contents] [Next]

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:

eventbutton_press_eventbutton_release_eventmotion_notify_event
delete_eventdestroy_eventexpose_eventkey_press_event
key_release_evententer_notify_eventleave_notify_eventconfigure_event
focus_in_eventfocus_out_eventmap_eventunmap_event
property_notify_eventselection_clear_eventselection_request_eventselection_notify_event
proximity_in_eventproximity_out_eventdrag_begin_eventdrag_request_event
drag_end_eventdrop_enter_eventdrop_leave_eventdrop_data_available_event
other_event   

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:

 PROCEDURE callback_proc( widget : pGtkWidget ; event : pGdkEvent ;
      callback_data : gpointer );

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_NOTHINGGDK_DELETEGDK_DESTROYGDK_EXPOSE
GDK_MOTION_NOTIFYGDK_BUTTON_PRESSGDK_2BUTTON_PRESSGDK_3BUTTON_PRESS
GDK_BUTTON_RELEASEGDK_KEY_PRESSGDK_KEY_RELEASEGDK_ENTER_NOTIFY
GDK_LEAVE_NOTIFYGDK_FOCUS_CHANGEGDK_CONFIGUREGDK_MAP
GDK_UNMAPGDK_PROPERTY_NOTIFYGDK_SELECTION_CLEARGDK_SELECTION_REQUEST
GDK_SELECTION_NOTIFYGDK_PROXIMITY_INGDK_PROXIMITY_OUTGDK_DRAG_BEGIN
GDK_DRAG_REQUESTGDK_DROP_ENTERGDK_DROP_LEAVEGDK_DROP_DATA_AVAIL
GDK_CLIENT_EVENTGDK_VISIBILITY_NOTIFYGDK_NO_EXPOSEGDK_OTHER_EVENT*

     * Deprecated, use filters instead

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

 gtk_signal_connect(GTK_OBJECT(button), 'button_press_event',
       GTK_SIGNAL_FUNC(@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 ) : gint; cdecl;

Note that we can declare the second argument as type GdkEventButton 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.

[Previous] [Contents] [Next]

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. Our callback procedure is called by a C function: 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 most likely compile, but then 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 the signal handler we called destroy(). It's not obligatory to call it destroy(), but it aids readability.

 FUNCTION delete_event( widget : pGtkWidget ; event : pGdkEvent ; data : gpointer ) :
      gint; cdecl;
 BEGIN writeln('delete event occurred');
delete_event := 0; { i.e. FALSE }
 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;

Next we declare our global variables in the same way we would for any other Pascal program. These pointers to GtkWidget types will be used later to create a window and a button.

 VAR window, button : pGtkWidget;

Again, like any other Pascal program, having defined our functions/procedures we begin the main program. Just like in the base.pas example, we start by calling gtk_init(). 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 the command-line argument arrays (argc and argv) to make it look like they never existed, allowing your application to parse the remaining arguments.

 BEGIN gtk_init(@argc, @argv);

Next we create a new window. This is fairly straightforward. Memory is allocated for the GtkWidget pointer we named window so it 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, or when we use the gtk_widget_destroy() call passing in the window widget as the object to destroy. The second is emitted when, in the delete_event handler, we return FALSE.

GTK_OBJECT and GTK_SIGNAL_FUNC are macros that perform 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 GTK_SIGNAL_FUNC macro. If you take a look at GTK programs written in C you will only see the name of the function to be called.

 gtk_signal_connect(GTK_OBJECT(window), 'delete_event',
       GTK_SIGNAL_FUNC(@delete_event), NIL);
 gtk_signal_connect(GTK_OBJECT(window), 'destroy',
       GTK_SIGNAL_FUNC(@destroy), NIL);

Next we create a new button. This call 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.

 gtk_signal_connect(GTK_OBJECT(button), 'clicked',
      GTK_SIGNAL_FUNC(@hello), NIL);

Next we have 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_quit() is called 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. The hello() procedure uses the standard writeln() to display 'Hello World!' in the console window.

If the user clicks the X in the top right-hand corner of the window the delete_event is emitted. This will call our delete_event() handler. If we return 1 (TRUE) here, 'delete event ocurred' will be displayed in the console, but the window will be left as is. Returning 0 (FALSE) will cause GTK to emit the destroy signal, which of course calls the destroy() callback, exiting GTK.

[Previous] [Contents] [Next]