Journal Articles
Browse in : |
All
> Journals
> CVu
> 166
(12)
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 3
Author: Administrator
Date: 03 December 2004 13:16:08 +00:00 or Fri, 03 December 2004 13:16:08 +00:00
Summary:
Body:
In the previous sections, the user interface was constructed entirely by hand, or automatically using libglade. The callback functions called in response to signals were simple C functions. While this mechanism is simple, understandable and works well, as a project gets larger the source will become more difficult to understand and manage. A better way of organising the source is required.
One very common way of reducing this complexity is object-orientation. The GTK+ library is already made up of many different objects. By using the same object mechanism (Gobject), the ogcalc code can be made more understandable and maintainable.
The ogcalc program consists of a GtkWindow which contains a number of other GtkWidgets and some signal handler functions. If our program was a class (Ogcalc) which derived from GtkWindow, the widgets the window contains would be member variables and the signal handlers would be member functions (methods). The user of the class wouldn't be required to have knowledge of these details, they just create a new Ogcalc object and show it. By using objects one also gains reusability. Previously only one instance of the object at a time was possible, and main() had explicit knowledge of the creation and workings of the interface.
This example bears many similarities with the C++ Glade example (next edition). Some of the features offered by C++ may be taken advantage of using plain C and GObject.
The listings for the code are given at the end of the article (next two pages).
To build the source, do the following:
cd C/gobject cc 'pkg-config -cflags libglade-2.0' -c ogcalc.c cc 'pkg-config -cflags libglade-2.0' -c ogcalc-main.c cc 'pkg-config -libs libglade-2.0' -o ogcalc ogcalc.o ogcalc-main.o
The bulk of the code is the same as in previous sections, and so describing what the code does will not be repeated here. The Ogcalc class is defined in C/gobject/ogcalc.h. This header declares the object and class structures and some macros common to all GObject-based objects and classes. The macros and internals of GObject are out of the scope of this document, but suffice it to say that this boilerplate is required, and is identical for all GObject classes bar the class and object names.
The object structure (_Ogcalc) has the object it derives from as the first member. This is very important, since it allows casting between types in the inheritance hierarchy, since all of the object structures start at an offset of 0 from the start address of the object. The other members may be in any order. In this case it contains the Glade XML interface object and the widgets required to be manipulated after object and interface construction. The class structure (_OgcalcClass) is identical to that of the derived class (GtkWindowClass). For more complex classes, this might contain virtual function pointers. It has many similarities to a C++ vtable. Finally, the header defines the public member functions of the class.
The implementation of this class is found in C/gobject/ogcalc.c. The major difference to previous examples is the class registration and the extra functions for object construction, initialisation and notification of destruction. The body of the methods to reset and calculate are identical to previous examples.
ogcalc_get_type() is used to get the the typeid (GType) of the class. As a side effect, it also triggers registration of the class with the GType type system. Remember, Gtype is a dynamic type system. Unlike languages like C++, where the types of all classes are known at compile-time, the majority of all the types used with GTK+ are registered on demand, except for the primitive data types and the base class GObject which are registered as fundamental types. As a result, in addition to being able to specify constructors and destructors for the object (or initialisers and finalisers in Gtype parlance), it is also possible to have initialisation and finalisation functions for both the class and base. For example, the class initialiser could be used to fix up the vtable for overriding virtual functions in derived classes. In addition, there is also an instance_init function, which is used in this example to initialise the class. It's similar to the constructor, but is called after object construction.
All these functions are specified in a GTypeInfo structure which is passed to g_type_register_static() to register the new type.
ogcalc_class_init() is the class initialisation function. This has no C++ equivalent, since this is taken care of by the compiler. In this case it is used to override the finalize() virtual function in the GObjectClass base class. This is used to specify a virtual destructor (it's not specified in the GTypeInfo because the destructor cannot be run until after an instance is created, and so has no place in object construction). With C++, the vtable would be fixed up automatically; here, it must be done manually. Pure virtual functions and default implementations are also possible, as with C++.
ogcalc_init() is the object initialisation function (C++ constructor). This does a similar job to the main() function in previous examples, namely contructing the interface (using Glade) and setting up the few object properties and signal handlers that could not be done automatically with Glade. In this example, a second argument is passed to glade_xml_new(); in this case, there is no need to create the window, since our Ogcalc object is a window, and so only the interface rooted from ogcalc_main_vbox is loaded.
ogcalc_finalize() is the object finalisation function (C++ destructor). It's used to free resources allocated by the object, in this case the GladeXML interface description.
g_object_unref() is used to decrease the reference count on a GObject. When the reference count reaches zero, the destructor is run and then the object is destroyed. There is also a dispose() function called prior to finalize(), which may be called multiple times. Its purpose is to safely free resources when there are cyclic references between objects, but this is not required in this simple case.
An important difference with earlier examples is that instead of connecting the window destroy signal to gtk_main_quit() to end the application by ending the GTK+ main loop, the delete signal is connected to ogcalc_on_delete_event() instead. This is because the default action of the delete event is to trigger a destroy event. The object should not be destroyed, so by handling the delete signal and returning TRUE, destruction is prevented. Both the "Quit" button and the delete event end up calling gtk_widget_hide() to hide the widget rather than gtk_main_quit() as before.
Lastly, C/gobject/ogcalc-main.c defines a minimal main(). The sole purpose of this function is to create an instance of Ogcalc, show it, and then destroy it. Notice how simple and understandable this has become now that building the UI is where it belongs - in the object construction process. The users of Ogcalc need no knowledge of its internal workings, which is the advantage of encapsulating complexity in classes.
By connecting the hide signal of the Ogcalc object to gtk_main_quit() the GTK+ event loop is ended when the user presses "Quit" or closes the window. By not doing this directly in the class it is possible to have as many instances of it as one likes in the same program, and control over termination is entirely in the hands of the user of the class - where it should be.
Listing 1: C/gobject/ogcalc.h
#include <gtk/gtk.h> #include <glade/glade.h> /* The following macros are GObject boilerplate. */ /* Return the GType of the Ogcalc class. */ #define OGCALC_TYPE (ogcalc_get_type()) /* Cast an object to type Ogcalc. The object must be of type Ogcalc, or derived from Ogcalc for this to work. This is similar to a C++ dynamic_cast<>. */ #define OGCALC(obj) \ (G_TYPE_CHECK_INSTANCE_CAST ((obj), \ OGCALC_TYPE, Ogcalc)) /* Cast a derived class to an OgcalcClass. */ #define OGCALC_CLASS(klass) \ (G_TYPE_CHECK_CLASS_CAST ((klass), \ OGCALC_TYPE, OgcalcClass)) /* Check if an object is an Ogcalc. */ #define IS_OGCALC(obj) \ (G_TYPE_CHECK_TYPE ((obj), OGCALC_TYPE)) /* Check if a class is an OgcalcClass. */ #define IS_OGCALC_CLASS(klass) \ (G_TYPE_CHECK_CLASS_TYPE ((klass), \ OGCALC_TYPE)) /* Get the OgcalcClass class. */ #define OGCALC_GET_CLASS(obj) \ (G_TYPE_INSTANCE_GET_CLASS ((obj), \ OGCALC_TYPE, OgcalcClass)) /* The Ogcalc object instance type. */ typedef struct _Ogcalc Ogcalc; /* The Ogcalc class type. */ typedef struct _OgcalcClass OgcalcClass; /* The definition of Ogcalc. */ struct _Ogcalc { GtkWindow parent; /* The object derives from GtkWindow. */ GladeXML *xml; /* The XML interface. */ /* Widgets contained within the window. */ GtkSpinButton *pg_val; GtkSpinButton *ri_val; GtkSpinButton *cf_val; GtkLabel *og_result; GtkLabel *abv_result; GtkButton* quit_button; GtkButton* reset_button; GtkButton* calculate_button; }; struct _OgcalcClass { /* The class derives from GtkWindowClass. */ GtkWindowClass parent; /* No other class properties are required (e.g. virtual functions). */ }; /* The following functions are described in ogcalc.c */ GType ogcalc_get_type(void); Ogcalc * ogcalc_new(void); gboolean ogcalc_on_delete_event(Ogcalc *ogcalc, GdkEvent *event, gpointer data); void ogcalc_reset(Ogcalc *ogcalc, gpointer data); void ogcalc_calculate(Ogcalc *ogcalc, gpointer data);
Listing 2: C/gobject/ogcalc.c
#include "ogcalc.h" static void ogcalc_class_init(OgcalcClass *klass); static void ogcalc_init(GTypeInstance *instance, gpointer g_class); static void ogcalc_finalize(Ogcalc *self); /* Get the GType of Ogcalc. This has the side effect of registering Ogcalc as a new GType if it has not already been registered. */ GType ogcalc_get_type(void) { static GType type = 0; if(type == 0) { /* GTypeInfo describes a GType. In this case, we only specify the size of the class and object instance types, along with an initialisation function. We could have also specified both class and object constructors and destructors here as well. */ static const GTypeInfo info = { sizeof (OgcalcClass), NULL, NULL, (GClassInitFunc) ogcalc_class_init, NULL, NULL, sizeof(Ogcalc), 0, (GInstanceInitFunc) ogcalc_init }; /* Actually register the type using the above type information. We specify the type we are deriving from, the class name and type information. */ type = g_type_register_static(GTK_TYPE_WINDOW, "Ogcalc", &info, (GTypeFlags) 0); } return type; } /* This is the class initialisation function. It has no comparable C++ equivalent, since this is done by the compiler. */ static void ogcalc_class_init(OgcalcClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); /* Override the virtual finalize method in the GObject class vtable (which is contained in OgcalcClass). */ gobject_class->finalize = (GObjectFinalizeFunc) ogcalc_finalize; } /* This is the object initialisation function. It is comparable to a C++ constructor. Note the similarity between "self" and the C++ "this" pointer. */ static void ogcalc_init(GTypeInstance *instance, gpointer g_class) { Ogcalc *self = (Ogcalc *) instance; /* Set the window title */ gtk_window_set_title(GTK_WINDOW (self), "OG & ABV Calculator"); /* Don't permit resizing */ gtk_window_set_resizable(GTK_WINDOW (self), FALSE); /* Connect the window close button ("destroy- event") to a callback. */ g_signal_connect(G_OBJECT (self), "delete-event", G_CALLBACK (ogcalc_on_delete_event), NULL); /* Load the interface description. */ self->xml = glade_xml_new("ogcalc.glade", "ogcalc_main_vbox", NULL); /* Get the widgets. */ self->pg_val = GTK_SPIN_BUTTON (glade_xml_get_widget (self->xml, "pg_entry")); self->ri_val = GTK_SPIN_BUTTON (glade_xml_get_widget (self->xml, "ri_entry")); self->cf_val = GTK_SPIN_BUTTON (glade_xml_get_widget (self->xml, "cf_entry")); self->og_result = GTK_LABEL (glade_xml_get_widget (self->xml, "og_result")); self->abv_result = GTK_LABEL (glade_xml_get_widget (self->xml, "abv_result")); self->quit_button = GTK_BUTTON (glade_xml_get_widget (self->xml, "quit_button")); self->reset_button = GTK_BUTTON (glade_xml_get_widget (self->xml, "reset_button")); self->calculate_button = GTK_BUTTON (glade_xml_get_widget (self->xml, "calculate_button")); /* Set up the signal handlers. */ glade_xml_signal_autoconnect(self->xml); g_signal_connect_swapped (G_OBJECT (self->cf_val), "activate", G_CALLBACK (gtk_window_activate_default), (gpointer) self); g_signal_connect_swapped (G_OBJECT (self->calculate_button), "clicked", G_CALLBACK (ogcalc_calculate), (gpointer) self); g_signal_connect_swapped (G_OBJECT (self->reset_button), "clicked", G_CALLBACK (ogcalc_reset), (gpointer) self); g_signal_connect_swapped (G_OBJECT (self->quit_button), "clicked", G_CALLBACK (gtk_widget_hide), (gpointer) self); /* Get the interface root and pack it into our window. */ gtk_container_add (GTK_CONTAINER (self), glade_xml_get_widget( self->xml, "ogcalc_main_vbox")); /* Ensure calculate is the default. The Glade default was lost since it wasn't in a window when the default was set. */ gtk_widget_grab_default (GTK_WIDGET (self->calculate_button)); } /* This is the object initialisation function. It is comparable to a C++ destructor. Note the similarity between "self" and the C++ "this" pointer. */ static void ogcalc_finalize(Ogcalc *self) { /* Free the Glade XML interface description. */ g_object_unref(G_OBJECT(self->xml)); } /* Create a new instance of the Ogcalc class (i.e. an object) and pass it back by reference. */ Ogcalc * ogcalc_new(void) { return (Ogcalc *) g_object_new(OGCALC_TYPE, NULL); } /* This function is called when the window is about to be destroyed (e.g. if the close button on the window was clicked). It is not a destructor. */ gboolean ogcalc_on_delete_event(Ogcalc *ogcalc, GdkEvent *event, gpointer user_data) { gtk_widget_hide(GTK_WIDGET (ogcalc)); /* We return true because the object should not be automatically destroyed. */ return TRUE; } /* Reset the interface. */ void ogcalc_reset(Ogcalc *ogcalc, gpointer data) { gtk_spin_button_set_value(ogcalc->pg_val, 0.0); gtk_spin_button_set_value(ogcalc->ri_val, 0.0); gtk_spin_button_set_value(ogcalc->cf_val, 0.0); gtk_label_set_text(ogcalc->og_result, ""); gtk_label_set_text(ogcalc->abv_result, ""); } /* Perform the calculation. */ void ogcalc_calculate(Ogcalc *ogcalc, gpointer data) { gdouble pg, ri, cf, og, abv; gchar *og_string; gchar *abv_string; pg = gtk_spin_button_get_value (ogcalc->pg_val); ri = gtk_spin_button_get_value (ogcalc->ri_val); cf = gtk_spin_button_get_value (ogcalc->cf_val); og = (ri * 2.597) - (pg * 1.644) - 34.4165 + cf; /* Do the sums. */ if (og < 60) abv = (og - pg) * 0.130; else abv = (og - pg) * 0.134; /* Display the results. Note the <b></b> GMarkup tags to make it display in Bold. */ og_string = g_strdup_printf("<b>%0.2f</b>", og); abv_string = g_strdup_printf("<b>%0.2f</b>", abv); gtk_label_set_markup(ogcalc->og_result, og_string); gtk_label_set_markup(ogcalc->abv_result, abv_string); g_free(og_string); g_free(abv_string); }
Listing 3: C/gobject/ogcalc-main.c
#include <gtk/gtk.h> #include <glade/glade.h> #include "ogcalc.h" /* This main function merely instantiates the ogcalc class and displays its main window. */ int main(int argc, char *argv[]) { /* Initialise GTK+. */ gtk_init(&argc, &argv); /* Create an Ogcalc object. */ Ogcalc *ogcalc = ogcalc_new(); /* When the widget is hidden, quit the GTK+ main loop. */ g_signal_connect(G_OBJECT (ogcalc), "hide", G_CALLBACK (gtk_main_quit), NULL); /* Show the object. */ gtk_widget_show(GTK_WIDGET (ogcalc)); /* Enter the GTK Event Loop. This is where all the events are caught and handled. It is exited with gtk_main_quit(). */ gtk_main(); /* Clean up. */ gtk_widget_destroy(GTK_WIDGET (ogcalc)); return 0; }
Notes:
More fields may be available via dynamicdata ..