Journal Articles

CVu Journal Vol 17, #1 - Feb 2005 + Programming Topics
Browse in : All > Journals > CVu > 171 (9)
All > Topics > Programming (877)
Any of these categories - All of these categories

Note: when you create a new publication type, the articles module will automatically use the templates user-display-[publicationtype].xt and user-summary-[publicationtype].xt. If those templates do not exist when you try to preview or display a new article, you'll get this warning :-) Please place your own templates in themes/yourtheme/modules/articles . The templates will get the extension .xt there.

Title: An Introduction to Programming with GTK+ and Glade in ISO C and ISO C++ - Part 4

Author: Administrator

Date: 03 February 2005 13:16:10 +00:00 or Thu, 03 February 2005 13:16:10 +00:00

Summary: 

Body: 

GTK+ and C++

In the previous article, it was shown that Glade and GObject could make programs much simpler, and hence increase their long-term maintainability. However, some problems remain:

  • Much type checking is done at run-time. This might mean errors only show up when the code is in production use.

  • Although object-oriented, using objects in C is a bit clunky. In addition, it is very difficult (although not impossible) to derive new widgets from existing ones using GObject, or override a class method or signal. Most programmers do not bother, or just use "compound widgets", which are just a container containing more widgets.

  • Signal handlers are not type safe. This could result in undefined behaviour, or a crash, if a signal handler does not have a signature compatible with the signal it is connected to.

  • Signal handlers are functions, and there is often a need to resort to using global variables and casting structures to type gpointer to pass complex information to a callback though its data argument. If Glade or GObject are used, this can be avoided, however.

gtkmm offers solutions to most of these problems. Firstly, all of the GTK+ objects are available as native C++ classes. The object accessor functions are now normal C++ class methods, which prevents some of the abuse of objects that could be accomplished in C. The advantage is less typing, and there is no need to manually cast between an object's types to use the methods for different classes in the inheritance hierarchy.

The gtkmm classes may be used just like any other C++ class, and this includes deriving new objects from them through inheritance. This also enables all the type checking to be performed by the compiler, which results in more robust code, since object type checking is not deferred until run-time.

Signal handling is also more reliable. gtkmm uses the libsigc++ library, which provides a templated signal/slot mechanism for type-safe signal handling. The slot objects allow signal handlers with a different signature than the signal requires to be bound, which gives greater flexibility than the C signals allow. Perhaps the most notable feature is that signal handlers may be class methods, which are recommended over global functions. This results in further encapsulation of complexity, and allows the signal handlers to access the member data of their class. Unlike the Qt library, gtkmm does not require any source preprocessing, allowing plain ISO C++ to be used without extensions.

libglademm is a C++ wrapper around libglade, and may be used to dynamically load user interfaces as in the previous section. It provides similar functionality, the exception being that signals must be connected manually. This is because the libsigc++ signals, connecting to the methods of individual objects, cannot be connected automatically.

C++/glade/ogcalc, shown in Figure 1, is identical to the previous examples, both in appearance and functionality. However, internally there are some major differences.

C++/glade/ogcalc in action.

Figure 1. C++/glade/ogcalc in action.

Firstly, the main() function no longer knows anything about the user interface. It merely instantiates an instance of the ogcalc class, similar to C/gobject/ogcalc.

The ogcalc class is derived from the Gtk::Window class, and so contains all of the functionality of a Gtk::Window, plus its own additional functions and data. ogcalc contains methods called on_button_clicked_calculate() and on_button_clicked_reset() These are the equivalents of the functions on_button_clicked_calculate() and on_button_clicked_reset() used in the previous examples. Because these functions are class methods, they have access to the class member data, and as a result are somewhat simpler than previously.

Two versions are provided, one using the basic C++ classes and methods to construct the interface, the other using libglademm to load and construct the interface as for the previous examples using Glade. Only the latter is discussed here. There are a great many similarities between the C and C++ versions not using Glade, and the C Gobject version and the C++ Glade version. It is left as an exercise to the reader to compare and contrast them.

Code Listings

// C++/glade/ogcalc.h
#include <gtkmm.h>
#include <libglademm.h>
class ogcalc : public Gtk::Window {
public:
  ogcalc();
  virtual ~ogcalc();
protected:
  // Calculation signal handler.
  virtual void on_button_clicked_calculate();
  // Reset signal handler.
  virtual void on_button_clicked_reset();
  // The widgets that are manipulated.
  Gtk::SpinButton* pg_entry;
  Gtk::SpinButton* ri_entry;
  Gtk::SpinButton* cf_entry;
  Gtk::Label* og_result;
  Gtk::Label* abv_result;
  Gtk::Button* quit_button;
  Gtk::Button* reset_button;
  Gtk::Button* calculate_button;
  // Glade interface description.
  Glib::RefPtr<Gnome::Glade::Xml> xml_interface;
};
// C++/glade/ogcalc.cc
#include <iomanip>
#include <sstream>
#include <sigc++/retype_return.h>
#include "ogcalc.h"
ogcalc::ogcalc() {
  // Set the window title.
  set_title("OG & ABV Calculator");
  // Don't permit resizing.
  set_resizable(false);
  // Get the Glade UI and add it to this window.
  xml_interface = Gnome::Glade::Xml::create(
                "ogcalc.glade", "ogcalc_main_vbox");
  Gtk::VBox *main_vbox;
  xml_interface->get_widget("ogcalc_main_vbox",
                            main_vbox);
  add(*main_vbox);
  // Pull all of the widgets from the Glade interface
  xml_interface->get_widget("pg_entry", pg_entry);
  xml_interface->get_widget("ri_entry", ri_entry);
  xml_interface->get_widget("cf_entry", cf_entry);
  xml_interface->get_widget("og_result",
                            og_result);
  xml_interface->get_widget("abv_result",
                            abv_result);
  xml_interface->get_widget("quit_button",
                            quit_button);

  xml_interface->get_widget("reset_button",
                            reset_button);
  xml_interface->get_widget("calculate_button",
                            calculate_button);
  // Set up signal handers for buttons.
  quit_button->signal_clicked().connect(
         SigC::slot(*this, &ogcalc::hide));
  reset_button->signal_clicked().connect(
         SigC::slot(*this,
             &ogcalc::on_button_clicked_reset));
  reset_button->signal_clicked().connect(
         SigC::slot(*pg_entry,
             &Gtk::Widget::grab_focus));
  calculate_button->signal_clicked().connect(
         SigC::slot(*this,
             &ogcalc::on_button_clicked_calculate));
  calculate_button->signal_clicked().connect(
         SigC::slot(*reset_button,
             &Gtk::Widget::grab_focus));
  // Set up signal handlers for numeric entries.
  pg_entry->signal_activate().connect(
         SigC::slot(*ri_entry,
             &Gtk::Widget::grab_focus));
  ri_entry->signal_activate().connect(
         SigC::slot(*cf_entry,
             &Gtk::Widget::grab_focus));
  cf_entry->signal_activate().connect(
         SigC::hide_return(SigC::slot(*this,
             &Gtk::Window::activate_default)));
  // Ensure calculate is the default.  The Glade
  // default was lost since it was not packed in
  // a window when set.
  calculate_button->grab_default();
}

ogcalc::~ogcalc() {}

void ogcalc::on_button_clicked_calculate() {
  // PG, RI, and CF values.
  double pg = pg_entry->get_value();
  double ri = ri_entry->get_value();
  double cf = cf_entry->get_value();
  // Calculate OG.
  double og = (ri*2.597)  -(pg*1.644) - 34.4165 + cf;
  // Calculate ABV.
  double abv;
  if (og < 60)
    abv = (og - pg) * 0.130;
  else
    abv = (og - pg) * 0.134;
  std::ostringstream output;
  // Use the user's locale for this stream.
  output.imbue(std::locale(""));
  output << "<b>" << std::fixed
         << std::setprecision(2)
         << og << "</b>";
  og_result->set_markup(
               Glib::locale_to_utf8(output.str()));
  output.str("");
  output << "<b>" << std::fixed
         << std::setprecision(2)
         << abv << "</b>";
  abv_result->set_markup(
               Glib::locale_to_utf8(output.str()));
}

void ogcalc::on_button_clicked_reset() {
  pg_entry->set_value(0.0);
  ri_entry->set_value(0.0);
  cf_entry->set_value(0.0);
  og_result->set_text("");
  abv_result->set_text("");
}
// C++/glade/ogcalc-main.cc
#include <gtk/gtk.h>
#include <glade/glade.h>
#include "ogcalc.h"

// This main function merely instantiates the ogcalc
// class and displays it.
int main (int argc, char *argv[]) {
  Gtk::Main kit(argc, argv); // Initialise GTK+.
  ogcalc window;   // Create an ogcalc object.
  kit.run(window);
            // Show window; return when it's closed.
  return 0;
}

To build the source, do the following:

cd C++/glade
c++ 'pkg-config -cflags libglademm-2.0' -c ogcalc.cc
c++ 'pkg-config -cflags libglademm-2.0'
                                   -c ogcalc-main.cc
c++ 'pkg-config -libs libglademm-2.0' -o ogcalc
                              ogcalc.o ogcalc-main.o

Similarly, for the plain C++ version, which is not discussed in the tutorial:

cd C++/plain
c++ 'pkg-config -cflags gtkmm-2.0' -c ogcalc.cc
c++ 'pkg-config -cflags gtkmm-2.0' -c ogcalc-main.cc
c++ 'pkg-config -libs gtkmm-2.0' -o ogcalc ogcalc.o
                              ogcalc-main.o

Analysis

ogcalc.h

The header file declares the ogcalc class.

class ogcalc : public Gtk::Window

ogcalc is derived from Gtk::Window

virtual void on_button_clicked_calculate();
virtual void on_button_clicked_reset();

on_button_clicked_calculate() and on_button_clicked_reset() are the signal handling functions, as previously. However, they are now class member functions, taking no arguments.

Gtk::SpinButton* pg_entry;
Glib::RefPtr<Gnome::Glade::Xml> xml_interface;

The class data members include pointers to the objects needed by the callbacks (which can access the class members like normal class member functions). Note that Gtk::SpinButton is a native C++ class. It also includes a pointer to the XML interface description. Glib::RefPtr is a templated, reference-counted, "smart pointer" class, which will take care of destroying the pointed-to object when ogcalc is destroyed.

ogcalc.cc

The constructor ogcalc::ogcalc() takes care of creating the interface when the class is instantiated.

set_title("OG & ABV Calculator");
set_resizable(false);

The above code uses member functions of the Gtk::Window class. The global functions gtk_window_set_title() and gtk_window_set_resizable() were used previously.

xml_interface = Gnome::Glade::Xml::create(
                "ogcalc.glade", "ogcalc_main_vbox");
Gtk::VBox *main_vbox;
xml_interface->get_widget("ogcalc_main_vbox",
                          main_vbox);
add(*main_vbox);

The Glade interface is loaded using Gnome::Glade::Xml::create(), in a similar manner to the GObject example, and then the main VBox is added to the ogcalc object.

xml_interface->get_widget("pg_entry", pg_entry);

Individual widgets may be obtained from the widget tree using the static member function Gnome::Glade::Xml::get_widget().

Because gtkmm uses libsigc++ for signal handling, which uses class member functions as signal handlers (normal functions may also be used, too), the signals cannot be connected automatically, as in the previous example.

quit_button->signal_clicked().connect(
                SigC::slot(*this, &ogcalc::hide));

This complex-looking code can be broken into several parts.

SigC::slot(*this, &ogcalc::hide)

creates a SigC::slot (function object) which points to the ogcalc::hide() member function of this object.

quit_button->signal_clicked()

returns a Glib::SignalProxy0 object (a signal taking no arguments). The connect() method of the signal proxy is used to connect ogcalc::hide() to the "clicked" signal of the Gtk::Button.

calculate_button->signal_clicked().connect(
         SigC::slot(*this,
             &ogcalc::on_button_clicked_calculate));
calculate_button->signal_clicked().connect(
         SigC::slot(*reset_button,
             &Gtk::Widget::grab_focus));

Here two signal handlers are connected to the same signal. When the "Calculate" button is clicked, ogcalc::on_button_clicked_calculate() is called first, followed by

Gtk::Widget::grab_focus(). 
cf_entry->signal_activate().connect(
         SigC::hide_return(SigC::slot(*this,
             &Gtk::Window::activate_default)));

SigC::hide_return is a special SigC::slot used to mask the boolean value returned by activate_default(). The slot created is incompatible with with the slot type required by the signal, and this "glues" them together.

In the ogcalc::on_button_clicked_calculate() member function,

double pg = pg_entry->get_value();

the member function Gtk::SpinButton::get_value() was previously used as gtk_spin_button_get_value().

std::ostringstream output;
output.imbue(std::locale(""));
output << "<b>" << std::fixed
       << std::setprecision(2)
       << og << "</b>";
og_result->set_markup(
               Glib::locale_to_utf8(output.str()));

This code sets the result field text, using an output stringstream and Pango markup.

In the ogcalc::on_button_clicked_reset() member function,

pg_entry->set_value(0.0);
og_result->set_text("");
pg_entry->grab_focus();

class member functions are used to reset and clear the widgets as in previous examples.

ogcalc-main.cc

This file contains a very simple main() function.

Gtk::Main kit(argc, argv); // Initialise GTK+.
ogcalc window;
kit.run(window);

A Gtk::Main object is created, and then an ogcalc class, window, is instantiated. Finally, the interface is run, using kit.run(). This function will return when window is hidden, and then the program will exit.

Conclusion

Which method of programming one chooses is dependent on many different factors, such as:

  • The languages one is familiar with.

  • The size and nature of the program to be written.

  • The need for long-term maintainability.

  • The need for code reuse.

For simple programs, such as C/plain/ogcalc, there is no problem with writing in plain C, but as programs become more complex, Glade can greatly ease the effort needed to develop and maintain the code. The code reduction and de-uglification achieved through conversion to Glade/libglade is beneficial even for small programs, however, so I would recommend that Glade be used for all but the most trivial code.

The C++ code using gtkmm is slightly more complex than the code using Glade. However, the benefits of type and signal safety, encapsulation of complexity and the ability to re-use code through the derivation of new widgets make gtkmm and libglademm an even better choice. Although it is possible to write perfectly good code in C, gtkmm gives the programmer security through compiler type checking that plain GTK+ cannot offer. In addition, improved code organisation is possible, because inheritance allows encapsulation.

GObject provides similar facilities to C++ in terms of providing classes, objects, inheritance, constructors and destructors etc., and is certainly very capable (it is, after all, the basis of the whole of GTK+!). The code using GObject is very similar to the corresponding C++ code in terms of its structure. However, C++ still provides facilities such as RAII (Resource Acquisition is Initialisation) and automatic destruction when an object goes out of scope that C cannot provide.

There is no "best solution" for everyone. Choose based on your own preferences and capabilities. In addition, Glade is not the solution for every problem. The author typically uses a mixture of custom widgets and Glade interfaces (and your custom widgets can contain Glade interfaces!). Really dynamic interfaces must be coded by hand, since Glade interfaces are not sufficiently flexible. Use what is best for each situation.

Further Reading

The GTK+ Tutorial, and the GTK+ documentation are highly recommended. These are available from http://www.gtk.org/ The gtkmm documentation is available from http://www.gtkmm.org Unfortunately, some parts of these manuals are as yet incomplete. I hope that they will be fully documented in the future, since without good documentation, it will not be possible to write programs that take advantage of all the capabilities of GTK+ and gtkmm, without having to read the original source code. While there is nothing wrong with reading the source, having good documentation is essential for widespread adoption of GTK+.

Documentation and examples of GObject are scarce, but Mathieu Lacage has written an excellent tutorial which is available from http://le-hacker.org/papers/gobject/

Notes: 

More fields may be available via dynamicdata ..