About a decade ago I wrote a graphical application for a scientific organisation, in C++, using the then new GTK+ 1.2, via the GTK-- 1.2 C++ wrapper. Recently I've been asked to update this to work in a modern Linux environment, primarily ensuring that it compiles with a modern C++ compiler (which may be the subject of a separate post) and update it to the current version of the relevant graphics libraries. A lot has changed in the interveening time, including the release of GTK+ 2.x, and a much updated gtkmm (including a new name for GTK--, gtkmm to make it easier to search for -- various Linux distributions had already chosen to do this in their package names).

In gtkmm 2.x there are quite a few changes from GTK-- 1.2 that impact a lot of code, so partly for my benefit here are some of the ones I found by starting to update a simple "dialog box" wrapper class:

Headers

In GTK-- 1.2 the default "everything" header file was:

#include "gtk--.h"

but in gtkmm 2.0 it has a new name:

#include "gtkmm.h"

However that pulls in about 1MB of source code to parse which adds to the compile times (at least on compilers that don't have precompiled headers), so it's recommended to include the individual headers required directly, eg:

#include "gtkmm/window.h"

(which of course requires chasing down the ones that are actually needed, and ironically will probably work worse when the compiler supports precompiled headers, since that is normally applied only to the first header file).

(NOTE: Those headers would normally be included in angle brackets, but the angle brackets conflict with HTML tags and it appears impossible to get the right combination of escaping to make both the blog view and the RSS/ATOM feeds work.)

Signal/Slots

GTK-- 1.2 (and gtkmm) uses libsigc for signal/slot handling, as a way of being notified of events (eg, "button clicked"). Over the past decade this usage has changed in several ways that make old code incompatible. The most obvious is that the namespace has changed from SigC to sigc (all lower case now) which requires source code changes since C++ is a case sensitive language.

The means of connecting to these signals has changed too. In GTK-- 1.2 one would declare a void myFunction(void) member function, and then bind an object pointer (eg this) and the member function into a SigC::slot and connect that to the signal -- accessing the signal as a public property of the relevant object. For instance:

Gtk::Button *close = new Gtk::Button("Close");
close->clicked.connect(SigC::slot(this,&GTKDialogBox::dismissDialog));

In gtkmm 2.x, the approach is similar but different: there are two different ways of making slots, sigc::ptr_fun which takes just a function pointer with no object, and sigc::mem_fun which takes both an object pointer and a member function. In addition the signals to connect to are hidden behind accessor functions, rather than being public properties, so one uses signal_clicked() and similar to get at them, rather than just clicked. The new code looks like this:

Gtk::Button *close = new Gtk::Button("Close");
close->signal_clicked().connect(sigc::mem_fun(this,&GTKDialogBox::dismissDialog));

There's on additional wrinkle. In GTK-- 1.2, windows had a destroy signal, which one could connect to in order to take some default action when the window was closed using the buttons in the window title bar. And it expected to call the same sort of function as the clicked signal, which made for some useful code reuse. In gtkmm 2.x, that signal has been renamed and generalised so that the it expects to call a signal handler that receives an event type and indicates whether or not the window can be closed that way (so, eg, one could prompt "save before quitting" and if the user chooses "don't quit" then one stops the close action). This means a new target function is required, although for something simple like a dialog box (where we don't need to anything special) it can just be a wrapper around the same action. Eg,

bool GTKDialogBox::deleteDialog(GdkEventAny *event)
{
  dismissDialog();
  return true;
}
signal_delete_event().connect(
       sigc::mem_fun(this,&GTKDialogBox::deleteDialog));

Signal/Slots with parameters

In a number of places in the code it needed to receive a callback with a parameter to indicate which thing had been selected (such as an item selected out of a menu). In GTK-- 1.2 the best way I could find at the time of doing this was to create a proxy object that would store the necessary parameter, and have that be called first and then relay the call into the right function. Something like:

class myProxy : public SigC::Object
{
public:
  myProxy(Gtk::MenuItem *source, param_t param, mainClass *parent) :
          d_param(param), d_observer(parent)
  { (*source).activate.connect(SigC::slot(this, &myProxy::itemSelected)); }

  void itemSelected()
  { (*d_observer).realHandler(d_param); }

private: 
  param_t    d_param;
  mainClass *d_observer;
};

and declaring myProxy to be a friend of the main class, which is invoked something like:

d_myProxies.push_back(new myProxy(item, *pos, this));

in the constructor of the main class (where d_myProxies is a way of managing the memory of these proxies).

In gtkmm 2.x there isn't an easily identifiable direct equivilent of SigC::Object which can be substituted, but fortunately none of this is necessary as gtkmm 2.x allows the use of sigc::bind template to add a parameter into the call back. The new code looks like this, just in the constructor of the main class:

item->signal_activate().connect(sigc::bind(sigc::mem_fun(this, &mainClass::realHandler), pos));

and the library takes care of everything (including the memory of any proxies). sigc::bind can be used to bind multiple parameters, up to 7 of them, which should handle most cases where we had to invent our own proxies to do this. (The Inkscape Wiki page on libsigc++ signals has a useful guide to the flexibility of this approach.)

(NOTE: sigc::bind can often derive the argument types needed for the template from the arguments given, but if note they can be specified in the normal C++ template style, inside angle brackets; unfortunately for the same HTML/RSS/ATOM issues mentioned above it's difficult to show an example here.)

Button Spacing

With the above issues sorted out, there was only one additional issue with the simple dialog box wrapper class, to do with setting the layout and spacing of multiple buttons.

In GTK-- 1.2 there was a combined function that would set both the layout and spacing together:

Gtk::HButtonBox *buttons = new Gtk::HButtonBox();
buttons->set_layout_spacing(GTK_BUTTONBOX_START, 2);

but in gtkmm 2.x there are only two separate classes, and rather than using the GTK+ enumeration directly there is a C++ version of the enumeration. The replacement code is:

buttons->set_layout(Gtk::BUTTONBOX_START);
buttons->set_spacing(2);

(where Gtk::BUTTONBOX_START is declared in gtkmm/enums.h).

Gtk::Text

Gtk::Text in GTK-- 1.2 has been replaced by Gtk::TextView and Gtk::TextBuffer in gtkmm 2.x. The two classes allow for a MVC (Model-View-Controller), design where the same piece of text can be displayed in multiple views, or a view can be changed between different groups of text. The tutorial on TextView illustrates some of these options.

The new interface is actually much easier to deal with than the old interface, particularly around getting text out of the edited buffer which used to require low-level GTK+ calls to read each character out, and now just requires something like:

Gtk::TextView view;
Gtk::TextBuffer buffer = view.get_buffer();
std::string text = buffer.get_text();

And replacing the buffer is as simple as a set_text() call, which is useful when something is loaded from a file.

ETA 2010-05-14: Added description of Gtk::Text to Gtk::TextView changes.