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,>KDialogBox::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,>KDialogBox::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,>KDialogBox::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.