Getting started with GTK programming


Summary
GIMP Toolkit (GTK) widget ArcGIS controls have been provided for C++ developers. To use them, you must understand some basics of GTK programming. This is not by any means a complete resource; the variety of GTK widgets and their resources, which you have available to you as a programmer, are not discussed here. However, this should give you a place to start figuring out the GTK-specific bits of the C++ API and samples.

Seven steps of GTK programming

When writing a GTK program, there are seven steps that need to be done.
  1. Initialize GTK
  2. Create the widgets
  3. Place the widgets
  4. Implement event listening and callback functions for widgets
  5. Show the widgets
  6. Begin the event handling loop
  7. Shut down the application
To illustrate each of these steps, you will create a simple GTK application.

GTK program: Simple PushButton

Thie application will be a single button that displays which button on the mouse was used to click on the button widget. Start a new file that will be your program. Here that file will be called pbExample.cpp.
To use GTK you will need to include the GTK header file: gtk/gtk.h. For the display of the number of the button clicked, you will need to inlcude iostream. You will also need a main function. Set these up in your new file, so that it looks like this:
[GTK C++]
#include <iostream>
#include <gtk/gtk.h>
int main(int argc, char *argv[])
{
  return 0;
}

Initialize GTK

Initialize GTK with a call to gtk_init(&argc, &argv);. This call does a few things: sets up resources, initializes everything needed to work with GTK, and parses some command line options.
In ArcGIS Engine applications, you must use AoInitialize as well, placing the call before any ArcObjects usage.
[GTK C++]
int main(int argc, char *argv[])
{
  gtk_init(&argc, &argv);
  return 0;
}

Create the widgets

With the toolkit initialized, you can create the single widget you are using in this application. First you will need to create the window in which you will place the button. Then you will create the button itself with gtk_button_new_with_label, since you want a labeled button in this example.
[GTK C++]
int main(int argc, char *argv[])
{
  gtk_init(&argc, &argv);
  GtkWidget *window,  *button;
  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  button = gtk_button_new_with_label("Push the button.");
  return 0;
}

Place the widgets

For the widgets to show up, they must be added to the main window as follows:
[GTK C++]
button = gtk_button_new_with_label("Push the button.");
gtk_container_add(GTK_CONTAINER(window), button);
To use multiple widgets in an application they must be packed. This is done with horizonal or vertical boxes or panes. Some of the calls related to packing include gtk_box_pack_start(), gtk_hbox_new(), gtk_vpaned_new(), and gtk_paned_add1(). Since this example only uses a single widget, this step is not needed. For additional information on it, see either the reference at the end or the samples.

Implement event listening and callback functions for widgets

You now have a button, but for that to be useful you must hook it to some functionality. Widgets are attached to behavior at certain events through special callback functions. You can add callbacks to a widget after it is created.
[GTK C++]
button = gtk_button_new_with_label("Push the button.");
g_signal_connect(G_OBJECT(button), "button_press_event", G_CALLBACK
  (ClickCallback), NULL);
These parameters have the following roles:
  • button—the widget that will give off the signal you are watching for.
  • "button_press_event"—the signal to respond to. Here you will listen for the button being pressed. For other options, see the GTK reference at the end of this topic.
  • ClickCallback—the function to call when the signal is received.
  • NULL—data to pass into the callback function. Here there is no data the function will need, so NULL is passed.
Callback functions
For the callback to work, you must implement the function that is being called on the event. Callbacks will generally follow the following functions signature:
[GTK C++]
void callback_func(GtkWidget *widget, gpointer callback_data);
However, as in this case, you will sometimes want additional information about the event. To get that information, the following function signature can be used:
[GTK C++]
void ClickCallback(GtkWidget *widget, GdkEventButton *event, gpointer
  callback_data);
The parameters are:
  • widget--the widget that gave the signal
  • event--tells what button press or release event triggered the function
  • callback_data--any data passed into the function, as indicated in the last parameter of g_signal_connect
callback_data scope: Make sure that the data passed to callback_data will be in scope later when the callback routine is executed.
For this example, place this function after main in pbExample.cpp, and have it print to cerr which mouse button was used to click (for example, "button: 1"). Remember to also place a forward declaration of it before main.
To get the button that was clicked, you must use the information in the GdkEventButton struct.
[GTK C++]
void ClickCallback(GtkWidget *widget, GdkEventButton *event, gpointer
  callback_data)
{
  // show which button was clicked
  std::cerr << "button pressed: " << event->button << std::endl;
}

Show the widgets

Your application's look and functionality are now written, but to create the actual window for your widget you need to call show_all_ right before you start the event loop, as you will do in the next step. You can either call gtk_widget_show on each widget, or you can call gtk_widget_show_all on the top-level widget, which will then recursively realize the rest of the widgets.
[GTK C++]
g_signal_connect(G_OBJECT(button), "button_press_event", G_CALLBACK
  (ClickCallback), NULL);
gtk_widget_show_all(window);

Begin the event handling loop

The next step for this application is to idle until a user generates an event.
[GTK C++]
gtk_widget_show_all(window);
gtk_main();

Sh ut down the application

As mentioned above, this application will run indefinitely unless it receives an event that tells it to do otherwise. To allow proper shutdown of the application, you will handle the signals that tell you the window is going to be closed.
After the widgets are shown and before the event loop is started by gtk_main, you will listen for those signals:
[GTK C++]
gtk_widget_show_all(window);
g_signal_connect(G_OBJECT(window), "delete_event", G_CALLBACK(delete_event),
  NULL);
g_signal_connect(G_OBJECT(window), "destroy", G_CALLBACK(destroy_event), NULL);
You will also provide the callbacks DeleteEvent and DestroyEvent that have the application close when the signals are recieved. As for the other callback, make sure you place a forward declaration before main.
[GTK C++]
static void destroy_event(GtkWidget *widget, gpointer data)
{
  gtk_main_quit();
}

static gboolean delete_event(GtkWidget *widget, GdkEvent *event, gpointer data)
{
  return FALSE; // must return false to trigger destroy event for window
}
For ArcGIS Engine C++ programming, you must call AoExit before returning. You must call AoUninitialize before shutting down the application with AoExit.

Trying it out

To compile your GTK program, you will need to link against libraries for GTK, Xt, and X11, in that order. If you are programming on Solaris, you will compile with the Sun Workshop (Forte), and if you are programming on Linux you will use GCC.
Solaris:
CC pbExample.cpp -o pbExample 'pkg-config gtk+-2.0 --cflags --libs'
Linux:
g++ pbExample.cpp -o pbExample 'pkg-config gtk+-2.0 --cflags --libs'
Run the program:
./pbExample
As you click the button, you will see the number of the button you pressed appear in your terminal window.
Now that you have a feeling for GTK programming, look at the C++ samples for GTK, and see these steps applied there.


See Also:

The GIMP Toolkit Web page
The GIMP Toolkit Tutorial
The GIMP Toolkit API reference