by Jason King, <pizza@netspace.net.au>
GTK+ started life as the replacement research project of two young guys named Peter Mattis and Spencer Kimball. Work on their original project stopped when the LISP code wouldn't allocate enough memory to parse a grammer, and they decided to write a graphics program in C called the GIMP (Gnu Image Manipulation Program). The graphics widgets for GIMP were done in motif, but this wasn't good for other people wanting to help with development, as motif was pretty expensive. To overcome this problem, they created their own widget library, GTK, and rewrote GIMP to use it.
A further rewrite was done to allow an improvement to GTK, called GTK+. One of its main features was that you could convert its widgets into other types. (See the Gimp User Manual, or GUM, for more details.)
GTK consists of three main parts: GTK+; GDK; and Glib.
This is a picture which shows the relationship between the separate parts of GTK, the application that is written using it and the underlying GUI.
To port GTK to another platform, all that is required is to port GDK to that platform (apparently glib might need some work too). There is a port being written for Microsoft Windows.
Glib contains all of the functions and structures which GTK+ uses for implementation of the GTK Objects. It provides:
Using these in your GTK+ programs will mean that under new conditions, like a new OS or 64 bit machine, that it will still compile and run.
Go to the gtk.org download page, and get the latest versions of GTK and glib.
Simply unwrap the tarball, and install it according to the instructions in the INSTALL file.
Source code.
This code generates a simple window and places it on the screen.
#include <stdio.h> #include <gtk/gtk.h> int main(int argc, char *argv[]) { GtkWidget *window; gtk_init(&argc, &argv); window = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_window_set_title(GTK_WINDOW(window), "test window"); gtk_widget_show(window); gtk_main(); return 0; }
#include <gtk/gtk.h>
You need to include gtk.h.
GtkWidget *window;
All GTK objects, called "widgets" from now on, need to be defined as a pointer to a GtkWidget structure. The pointer will be allocated some memory by the use of a gtk_WIDGET_new() function.
gtk_init(&argc, &argv);
The first GTK function call that should be made when using GTK+ is gtk_init(). This function takes as parameters &argc and &argv. gtk_init() will strip off the debug switches that it wants, and leave the rest for the program. If you don't call gtk_init() first, and you do anything using GTK+, then you get about 6 pages of failed assertions, So obviously it's doing a lot of other stuff too. Read the code for details.
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
The window pointer is allocated some space by the gtk_window_new() function. Note that the window was defined as a GtkWidget, not a GtkWindow or something like that. The reason for this is demonstrated below. GTK_WINDOW_TOPLEVEL is just a constant that means you are generating a window that isn't a temporary one. The other two possible settings are GTK_WINDOW_DIALOG, and GTK_WINDOW_POPUP.
gtk_window_set_title(GTK_WINDOW(window), "test window");
The window title is set by gtk_window_set_title(). It requires two parameters, one is the title to set it to, naturally, but the other is the pointer to a window. Now all you have at the moment is a pointer to a GtkWidget, so It must be forced to the correct type through the use of a macro, like GTK_WINDOW() . If the pointer is not what it should be, then an assertion will fail. (This is good for debugging. Either that or the makers of GTK+ are real bastards.)
gtk_widget_show(window);
The window is then displayed by calling gtk_widget_show(). It's not necessary to cast it to a different type. If the parent of the widget you are showing isn't shown itelf yet, then you don't see anything just yet. When the parent finally is shown, the child widgets already shown will appear as well.
gtk_main();
Once you have things how you want them, gtk_main() is called, which takes over control of the program. Once gtk_main() returns, your program can quit gracefully. gtk_main only returns when gtk_main_quit() is called. It isn't in this example, so you will notice how closing the window doesn't give you the prompt back. You need to do the two fingured salute, CTRL-C, to get it back.
Source code.
GTK+ provides a script called gtk-config which will supply the needed library files and C flags to build GTK+ apps.
# # Makefile contents # LIBS = `/usr/bin/gtk-config --libs` CFLAGS = `/usr/bin/gtk-config --cflags` C_OPTIONS = -g -Wall all: gcc $(C_OPTIONS) main.c -o runthis $(LIBS) $(CFLAGS) # ^ make sure this is a TAB
The next example places a button widget on the window. It fills the entire window, as there is no facility for a widget to only fill half of a container. The widget containing the child must be cast to GTK_CONTAINER using the GTK_CONTAINER() macro.
There are only two exceptions to this, which are covered later.
When you do something to a widget, like push a button or click on a listbox, GTK+ wil generate an event. In order to be able to react to that event, a signal is attached to that event.
From the GTK+ docs, the following are the events that are generated by the Gtk_Button Widget:
All of these events can be linked to signals that look mostly like this:
void signal_function (GtkButton* Button, gpointer* data)
The delete_event for a window widget wants a gint return, TRUE means don't kill the window, FALSE means kill it. (Make sure you read the docs for each event.)
The signal is attached to the event using the gtk_signal_connect() function, used like this:
gtk_signal_connect(GTK_OBJECT(button), "clicked", GTK_SIGNAL_FUNC(ButtonPressed), NULL);
The parameters are the object whose event is being connected, the event itself, the signal converted to the proper type using the GTK_SIGNAL_FUNC() macro, and a pointer to some information you may want to pass to the signal.
Widgets inherit properties from widgets higher up in the widget tree. A widget can be converted to an object higher in the tree using macros (i.e., GTK_CONTAINER(button)). Necessary for functions that require a container, not necessarily a table or window.
Below is a very simplified widget tree for GTK.
GTK_OBJECT | +-GTK_WIDGET | +-GTK_CONTAINER | +-GTK_BUTTON | +-GTK_WINDOW
Source code.
#include <stdio.h> #include <gtk/gtk.h> #define COLROW 5 /* * After it's connected to the right event, this will run when * the window is closed */ gint Delete(GtkWidget* widget, gpointer* data) { g_print("Screw you guys, I'm going home!\n"); gtk_main_quit(); /* stop gtk_main */ return FALSE; /* Kill the window */ } /* * After it's connected to the right event, this will run when * the button is pressed */ gint ButtonPressed(GtkWidget* widget, gpointer data) { g_print("You pressed the button.\n"); return FALSE; } int main(int argc, char* argv[]) { GtkWidget* window; GtkWidget* button; gtk_init(&argc, &argv); window = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_window_set_title(GTK_WINDOW(window), "test window"); button = gtk_button_new_with_label("hello world"); /* * The above line is equivalent to * GtkWidget* label; * * button = gtk_button_new(); * label = gtk_label_new("hello world"); * gtk_container_add(GTK_CONTAINER(button), label); * gtk_widget_show(label); */ gtk_container_add(GTK_CONTAINER(window), button); gtk_signal_connect(GTK_OBJECT(window), "delete_event", GTK_SIGNAL_FUNC(Delete), NULL); gtk_signal_connect(GTK_OBJECT(button), "clicked", GTK_SIGNAL_FUNC(ButtonPressed), NULL); gtk_widget_show(button); gtk_widget_show(window); /* do this last */ gtk_main(); return 0; }
Note that gtk_widget_show(window) is called after the showing of the button. You can choose not to show the window last if you wish, but you will see the windows child widgets being progressivly placed on the window. It may not be a pretty thing to see. Making sure the window itself is shown last stops this from happening.
The above example placed a button on a window (cast as a container). Now while this is all well and good, if you want to place more than one widget on a container, you have a problem.
Luckily, there are two exceptions to the rule that a widget can only have one child widget, these are the PACKING BOX and the PACKING TABLE.
A packing box is used as follows:
GtkWidget* box; box = gtk_vbox_new(homogenous, nSpacing); /* or gtk_hbox_new */ /* * homogenous = TRUE if all cells have the same spacing * FALSE if not * spacing = the number of pixels in from the borders */ gtk_box_pack_start(box, widget, expand, fill, padding); /* * box = the packing box to stick things in. (you still need to cast it) * widget = the widget you want to pack. * expand = TRUE if the box should be expanded to fill any remaining space. * fill = TRUE if the widget should grow in size to fit the box * padding = number of padding pixels. */
Simply call gtk_box_pack_start repeatedly with the widgets you want to put in it. There is also a function, gtk_box_pack_end, which will pack widgets starting from the end, not the beginning.
Packing tables work differently to boxes.
The code to create and add widgets to packing tables looks like this:
GtkWidget* table; table = gtk_table_new(NoOfColumns, NoOfRows, homogeneous); /* * homogenous = TRUE if each table cell should be the size of the largest * widget * FALSE if each table row and column should be the size * of the largest widget in each row and column */ gtk_table_attach(table, Child widget, left cell line to attach to, right cell line to attach to, Top Cell line to attach to, Bottom Cell line..., Xoptions, (defaults to GTK_FILL | GTK|EXPAND), Yoptions, Xpadding, YPadding); /* * Or, use this function. * * gtk_table_attach_defaults(table, Child widget, * left cell line to attach to, * right cell line to attach to, * Top Cell line to attach to, * Bottom Cell line, ...); */
This program example expands on the last one, and shows how to use a packing table.
Source code.
#include <stdio.h> #include <gtk/gtk.h> #define COLROW 5 gint Delete(GtkWidget* widget, gpointer* data) { g_print("screw you guys, I'm going home\n"); gtk_main_quit(); return FALSE; } gint ButtonPressed(GtkWidget* widget, gpointer* data) { g_print("you pressed the button.\n"); return FALSE; } int main(int argc, char* argv[]) { GtkWidget* window; GtkWidget* table; GtkWidget* button; gtk_init(&argc, &argv); window = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_window_set_title(GTK_WINDOW(window), "test window"); table = gtk_table_new(COLROW, COLROW, TRUE); gtk_container_add(GTK_CONTAINER(window), table); button = gtk_button_new_with_label("hello world"); gtk_table_attach_defaults(GTK_CONTAINER(table), button, 2, 4, 2, 4); gtk_signal_connect(GTK_OBJECT(window), "delete_event", GTK_SIGNAL_FUNC(Delete), NULL); gtk_signal_connect(GTK_OBJECT(button), "clicked", GTK_SIGNAL_FUNC(ButtonPressed), NULL); gtk_widget_show(button); gtk_widget_show(table); gtk_widget_show(window); /*do this last*/ gtk_main(); return 0; }
Notice in how the previous example, that the button on the window enlarged when the window was enlarged? Well, the same sort of thing happens with packing baxes and tables. Go ahead and change the width and height of this program. The Button on it will change its relative width and height to match.
Developing Linux Applications (using GTK and GDK), by Eric Harlow.
GTK+/Gnome Application Development, by Pennington.
Beginning Linux Programming, Second Ed., by Richard Stones and Neil Matthew
Various parts of this talk were blatantly copied from "Developing Linux Applications". Other parts probably look pretty similar to every other GTK+ talk in existance. Sorry about this, but I guess that is to be expected. This talk was only meant to be a simple intro.