Journal Articles
Browse in : |
All
> Journals
> CVu
> 165
(13)
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 - Part 2
Author: Administrator
Date: 03 October 2004 13:16:07 +01:00 or Sun, 03 October 2004 13:16:07 +01:00
Summary:
In this part, I will be showing how to use the Glade application for rapid window creation.
Body:
As you will recall from the first part, I showed how to construct a GTK window with all of its associated parts in some detail, and it was clear that to do this for every application would not be a good idea. In this part, I will be showing how to use the Glade application for rapid window creation.
The main() function is responsible for constructing the user interface, connecting the signals to the signal handlers, and then entering the main event loop. The more complex aspects of the function are discussed here.
g_signal_connect(G_OBJECT(window), "destroy", gtk_main_quit, NULL);
This code connects the "destroy" signal to the gtk_main_quit() function. This signal is emitted by the window if is to be destroyed, for example when the "close" button on the titlebar is clicked). The result is that when the window is closed, the main event loop returns, and the program then exits.
vbox1 = gtk_vbox_new(FALSE, 0); gtk_container_add(GTK_CONTAINER(window), vbox1);
vbox1 is a GtkVBox. When constructed using gtk_vbox_new(), it is set to be non-homogenous (FALSE), which allows the widgets contained within the GtkVBox to be of different sizes, and has zero pixels padding space between the containers it contains. The homogeneity and padding space are different for the various GtkBoxes used, depending on the visual effect intended. gtk_container_add() packs vbox1 into the window (a GtkWindow object is a GtkContainer).
eventbox = gtk_event_box_new(); gtk_widget_show(eventbox); gtk_box_pack_start(GTK_BOX(hbox2), eventbox, FALSE, FALSE, 0);
Some widgets do not receive events from the windowing system, and hence cannot emit signals. Label widgets are one example of this. If this is required, for example in order to show a tooltip, they must be put into a GtkEventBox, which can receive the events. The signals emitted from the GtkEventBox may then be connected to the appropriate handler.
gtk_widget_show() displays a widget. Widgets are hidden by default when created, and so must be shown before they can be used.
It is typical to show the top-level window last, so that the user does not see the interface being drawn.
gtk_box_pack_start() packs a widget into a GtkBox, in a similar manner to gtk_container_add(). This packs eventbox into hbox2. The last three arguments control whether the child widget should expand into any extra space available, whether it should fill any extra space available (this has no effect if expand is FALSE), and extra space in pixels to put between its neighbours (or the edge of the box), respectively. Figure 1 shows how gtk_box_pack_start() works.
The create_spin_entry() function is a helper function to create a numeric entry (spin button) together with a label and tooltip. It is used to create all three entries.
label = gtk_label_new(label_text);
A new label is created displaying the text label_text.
spinbutton = gtk_spin_button_new(adjustment, 0.5, 2); gtk_spin_button_set_numeric( GTK_SPIN_BUTTON(spinbutton), TRUE);
A GtkSpinButton is a numeric entry field. It has up and down buttons to "spin" the numeric value up and down. It is associated with a GtkAdjustment, which controls the range allowed, default value, etc. gtk_adjustment_new() returns a new GtkAdjustment object. Its arguments are the default value, minimum value, maximum value, step increment, page increment and page size, respectively. This is straightforward, apart from the step and page increments and sizes. The step and page increments are the value that will be added or subtracted when the mouse button 1 or button 2 are clicked on the up or down buttons, respectively. The page size has no meaning in this context (GtkAdjustments are also used with scrollbars).
gtk_spin_button_new() creates a new GtkSpinButton, and associates it with adjustment. The second and third arguments set the "climb rate" (rate of change when the spin buttons are pressed) and the number of decimal places to display.
Finally, gtk_spin_button_set_numeric() is used to ensure that only numbers can be entered.
tooltip = gtk_tooltips_new(); gtk_tooltips_set_tip(tooltip, eventbox, tooltip_text, NULL);
A tooltip (pop-up help message) is created with gtk_tooltips_new(). gtk_tooltips_set_tip() is used to associate tooltip with the eventbox widget, also specifying the message it should contain. The fourth argument should typically be NULL.
The create_result_label() function is a helper function to create a result label together with a descriptive label and tooltip.
gtk_label_set_selectable( GTK_LABEL(result_value), TRUE);
Normally, labels simply display a text string. The above code allows the text to be selected and copied, to allow pasting of the text elsewhere. This is used for the result fields so the user can easily copy them.
button1 = gtk_button_new_from_stock(GTK_STOCK_QUIT);
This code creates a new button, using a stock widget. A stock widget contains a predefined icon and text. These are available for commonly used functions, such as "OK", "Cancel", "Print", etc..
button2 = gtk_button_new_with_mnemonic( "_Calculate"); g_signal_connect(G_OBJECT (button2), "clicked", G_CALLBACK(on_button_clicked_calculate), (gpointer) &cb_widgets); GTK_WIDGET_SET_FLAGS(button2, GTK_CAN_DEFAULT);
Here, a button is created, with the label "Calculate". The mnemonic is the "_C", which creates an accelerator. This means that when Alt-C is pressed, the button is activated (i.e. it is a keyboard shortcut). The shortcut is underlined, in common with other graphical toolkits.
The "clicked" signal (emitted when the button is pressed and released) is connected to the on_button_clicked_calculate() callback. The cb_widgets structure is passed as the argument to the callback.
Lastly, the GTK_CAN_DEFAULT attribute is set. This attribute allows the button to be the default widget in the window.
g_signal_connect_swapped (G_OBJECT(cb_widgets.pg_val), "activate", G_CALLBACK(gtk_widget_grab_focus), (gpointer)GTK_WIDGET(cb_widgets.ri_val));
This code connects signals in the same way as gtk_signal_connect(). The difference is the fourth argument, which is a GtkWidget pointer. This allows the signal emitted by one widget to be received by the signal handler for another. Basically, the widget argument of the signal handler is given cb_widgets.ri_val rather than cb_widgets.pg_val. This allows the focus (where keyboard input is sent) to be switched to the next entry field when Enter is pressed in the first.
g_signal_connect_swapped (G_OBJECT(cb_widgets.cf_val), "activate", G_CALLBACK(gtk_window_activate_default), (gpointer)GTK_WIDGET(window));
This is identical to the last example, but in this case the callback is the function gtk_window_activate_default() and the widget to give to the signal handler is window. When Enter is pressed in the CF entry field, the default "Calculate" button is activated.
gtk_main();
This is the GTK+ event loop. It runs until gtk_main_quit() is called.
The signal handlers are far simpler than building the interface. The function on_button_clicked_calculate() reads the user input, performs a calculation, then displays the result.
void on_button_clicked_calculate( GtkWidget *widget, gpointer data) { struct calculation_widgets *w; w = (struct calculation_widgets *) data;
Recall that a pointer to cb_widgets, of type struct calculation_widgets, was passed to the signal handler, cast to a gpointer. The reverse process is now applied, casting data to a pointer of type struct calculation_widgets.
gdouble pg; pg = gtk_spin_button_get_value( GTK_SPIN_BUTTON(w->pg_val));
This code gets the value from the GtkSpinButton.
gchar *og_string; og_string = g_strdup_printf("<b>%0.2f</b>", og); gtk_label_set_markup(GTK_LABEL(w->og_result), og_string); g_free(og_string);
Here the result og is printed to the string og_string. This is then set as the label text using gtk_label_set_markup(). This function sets the label text using the Pango Markup Format, which uses the <b> and </b> tags to embolden the text.
gtk_spin_button_set_value( GTK_SPIN_BUTTON(w->pg_val), 0.0); gtk_label_set_text( GTK_LABEL(w->og_result), "");
on_button_clicked_reset() resets the input fields to their default value, and blanks the result fields.
In the previous section, the user interface was constructed entirely "by hand". This might seem to be rather difficult to do, as well as being messy and time-consuming. In addition, it also makes for rather unmaintainable code, since changing the interface, for example to add a new feature, would be rather hard. As interfaces become more complex, constructing them entirely in code becomes less feasible.
The Glade user interface designer is an alternative to this. Glade allows one to design an interface visually, selecting the desired widgets from a palette and placing them on windows, or in containers, in a similar manner to other interface designers. Figures 3-7 (see next page) shows some screenshots of the various components of Glade.
The file C/glade/ogcalc.glade contains the same interface constructed in C/plain/ogcalc.c, but designed in Glade. This file can be opened in Glade, and changed as needed, without needing to touch any code.
Even signal connection is automated. Examine the "Signals" tab in the "Properties" dialog box.
The source code is listed below. This is the same as the previous listing, but with the following changes:
-
The main() function does not construct the interface. It merely loads the ogcalc.glade interface description, auto-connects the signals, and shows the main window.
-
The cb_widgets structure is no longer needed: the callbacks are now able to query the widget tree through the Glade XML object to locate the widgets they need. This allows for greater encapsulation of data, and signal handler connection is simpler.
-
The code saving is significant, and there is now separation between the interface and the callbacks.
The running C/glade/ogcalc application is shown in Figure 2. Notice that it is identical to C/plain/ogcalc, shown in the last article. (No, they are not the same screenshot!)
The most obvious difference between the code using Glade (see listing at end of article) and the previous code is the huge reduction in size. The main() function is reduced to just these lines:
GladeXML *xml; GtkWidget *window; xml = glade_xml_new("ogcalc.glade", NULL, NULL); glade_xml_signal_autoconnect(xml); window = glade_xml_get_widget(xml, "ogcalc_main_window"); gtk_widget_show(window);
glade_xml_new() reads the interface from the file ogcalc.glade. It returns the interface as a pointer to a GladeXML object, which will be used later. Next, the signal handlers are connected with glade_xml_signal_autoconnect(). Windows users may require special linker flags because signal autoconnection requires the executable to have a dynamic symbol table in order to dynamically find the required functions.
The signal handlers are identical to those in the previous section. The only difference is that struct calculation_widgets has been removed. No information needs to be passed to them through the data argument, since the widgets they need to use may now be found using the GladeXML interface description.
GtkWidget *pg_val; GladeXML *xml; xml = glade_get_widget_tree( GTK_WIDGET (widget)); pg_val = glade_xml_get_widget(xml, "pg_entry");
Firstly, the GladeXML interface is found, by finding the widget tree containing the widget passed as the first argument to the signal handler. Once xml has been set, glade_xml_get_widget() may be used to obtain pointers to the GtkWidgets stored in the widget tree.
Compared with the pure C GTK+ application, the code is far simpler, and the signal handlers no longer need to get their data as structures cast to gpointer, which was ugly. The code is far more understandable, cleaner and maintainable.
Listing: C/glade/ogcalc.c
#include <gtk/gtk.h> #include <glade/glade.h> void on_button_clicked_reset(GtkWidget *widget, gpointer data); void on_button_clicked_calculate(GtkWidget *widget, gpointer data); /* The bulk of the program. Since Glade and libglade are used, this is just 9 lines! */ int main(int argc, char *argv[]) { GladeXML *xml; GtkWidget *window; /* Initialise GTK+. */ gtk_init(&argc, &argv); /* Load the interface description. */ xml = glade_xml_new("ogcalc.glade", NULL, NULL); /* Set up the signal handlers. */ glade_xml_signal_autoconnect(xml); /* Find the main window and then show it. */ window = glade_xml_get_widget(xml, "ogcalc_main_window"); gtk_widget_show(window); /* Enter the GTK Event Loop. This is where all the events are caught and handled. It is exited with gtk_main_quit(). */ gtk_main(); return 0; } /* This is a callback. This resets the values of the entry widgets, and clears the results. */ void on_button_clicked_reset(GtkWidget *widget, gpointer data) { GtkWidget *pg_val; GtkWidget *ri_val; GtkWidget *cf_val; GtkWidget *og_result; GtkWidget *abv_result; GladeXML *xml; /* Find the Glade XML tree containing widget. */ xml = glade_get_widget_tree(GTK_WIDGET (widget)); /* Pull the other widgets out the the tree. */ pg_val = glade_xml_get_widget(xml, "pg_entry"); ri_val = glade_xml_get_widget(xml, "ri_entry"); cf_val = glade_xml_get_widget(xml, "cf_entry"); og_result = glade_xml_get_widget(xml, "og_result"); abv_result = glade_xml_get_widget(xml, "abv_result"); gtk_spin_button_set_value(GTK_SPIN_BUTTON(pg_val), 0.0); gtk_spin_button_set_value(GTK_SPIN_BUTTON(ri_val), 0.0); gtk_spin_button_set_value(GTK_SPIN_BUTTON(cf_val), 0.0); gtk_label_set_text(GTK_LABEL(og_result), ""); gtk_label_set_text(GTK_LABEL(abv_result), ""); } /* This callback does the actual calculation. */ void on_button_clicked_calculate(GtkWidget *widget, gpointer data) { GtkWidget *pg_val; GtkWidget *ri_val; GtkWidget *cf_val; GtkWidget *og_result; GtkWidget *abv_result; GladeXML *xml; gdouble pg, ri, cf, og, abv; gchar *og_string; gchar *abv_string; /* Find the Glade XML tree containing widget. */ xml = glade_get_widget_tree(GTK_WIDGET(widget)); /* Pull the other widgets out the the tree. */ pg_val = glade_xml_get_widget(xml, "pg_entry"); ri_val = glade_xml_get_widget(xml, "ri_entry"); cf_val = glade_xml_get_widget(xml, "cf_entry"); og_result = glade_xml_get_widget(xml, "og_result"); abv_result = glade_xml_get_widget(xml, "abv_result"); /* Get the numerical values from the entry widgets. */ pg = gtk_spin_button_get_value( GTK_SPIN_BUTTON(pg_val)); ri = gtk_spin_button_get_value( GTK_SPIN_BUTTON(ri_val)); cf = gtk_spin_button_get_value( GTK_SPIN_BUTTON(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(GTK_LABEL(og_result), og_string); gtk_label_set_markup(GTK_LABEL(abv_result), abv_string); g_free(og_string); g_free(abv_string); }
To build the source, do the following:
cd C/glade cc 'pkg-config --cflags libglade-2.0' -c ogcalc.c cc 'pkg-config --libs libglade-2.0' -o ogcalc ogcalc.o
Notes:
More fields may be available via dynamicdata ..