Journal Articles
Browse in : |
All
> Journals
> CVu
> 164
(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++
Author: Administrator
Date: 03 August 2004 13:16:06 +01:00 or Tue, 03 August 2004 13:16:06 +01:00
Summary:
This short tutorial is intended as a simple introduction to writing GTK+ applications in C and C++, using the current 2.0/2.2 version of libgtk. It also covers the use of the Glade user interface designer for rapid application development (RAD).
Body:
GTK+ is a toolkit used for writing graphical applications. Originally written for the X11 windowing system, it has now been ported to other systems, such as Microsoft Windows and the Apple Macintosh, and so may be used for cross-platform software development. GTK+ was written as a part of the GNU Image Manipulation Program (GIMP), but has long been a separate project, used by many other free software projects, one of the most notable being the GNU Network Object Model Environment (GNOME) Project.
GTK+ is written in C and, because of the ubiquity of the C language, bindings have been written to allow the development of GTK+ applications in many other languages. This short tutorial is intended as a simple introduction to writing GTK+ applications in C and C++, using the current 2.0/2.2 version of libgtk. It also covers the use of the Glade user interface designer for rapid application development (RAD).
It is assumed that the reader is familiar with C and C++ programming, and it would be helpful to work through the "Getting Started" chapter of the GTK+ tutorial before reading further. The GTK+, Glib, libglade, Gtkmm and libglademm API references will be useful while working through the examples.
I hope you find this tutorial informative.
Several working, commented examples accompany the tutorial. They are also available from http://people.debian.org/~rleigh/gtk/ogcalc/. To build them, type:
./configure make
This will check for the required libraries and build the example code. Each program may then be run from within its subdirectory.
I have been asked on various occasions to write a tutorial to explain how the GNU autotools work. While this is not the aim of this tutorial, I have converted the build to use the autotools as a simple example of their use.
This tutorial document, the source code and compiled binaries, and all other files distributed in the source package are copyright © 2003 - 2004 Roger Leigh. These files and binary programs are free software; you can redistribute them and/or modify them under the terms of the GNU General Public Licence as published by the Free Software Foundation; either version 2 of the Licence, or (at your option) any later version.
A copy of the GNU General Public Licence version 2 is provided in the file COPYING in the source package this document was generated from.
GTK+ is an object-oriented (OO) toolkit. I'm afraid that unless one is aware of the basic OO concepts (classes, class methods, inheritance, polymorphism), this tutorial (and GTK+ in general) will seem rather confusing. On my first attempt at learning GTK+, I didn't "get" it, but after I learnt C++, the concepts GTK+ is built on just "clicked", and I understood it quite quickly.
The C language does not natively support classes, and so GTK+ provides its own object/type system, GObject. GObject provides objects, inheritance, polymorphism, constructors, destructors and other facilities such as reference counting and signal emission and handling. Essentially, it provides C++ classes in C. The syntax differs a little from C++ though. As an example, the following C++
myclass c; c.add(2);
would be written like this using GObject:
myclass *c = myclass_new(); myclass_add(c, 2);
The difference is due to the lack of a this pointer in the C language (since objects do not exist). This means that class methods require the object pointer passing as their first argument. This happens automatically in C++, but it needs doing "manually" in C.
Another difference is seen when dealing with polymorphic objects. All GTK+ widgets (the controls, such as buttons, checkboxes, labels, etc.) are derived from GtkWidget. That is to say, a GtkButton is a GtkWidget, which is a GtkObject, which is a GObject. In C++, one can call member functions from both the class and the classes it is derived from. With GTK+, the object needs explicit casting to the required type. For example
GtkButton mybutton; mybutton.set_label("Cancel"); mybutton.show();
would be written as
GtkButton *mybutton = gtk_button_new(); gtk_button_set_label(mybutton, "Cancel"); gtk_widget_show(GTK_WIDGET(mybutton))
In this example, set_label() is a method of GtkButton, whilst show() is a method of GtkWidget, which requires an explicit cast. The GTK_WIDGET() cast is actually a form of run-time type identification (RTTI). This ensures that the objects are of the correct type when they are used.
Objects and C work well, but there are some issues, such as a lack of type-safety of callbacks and limited compile-time type checking. Using GObject, deriving new widgets is complex and error-prone. For these, and other, reasons, C++ may be a better language to use. libsigc++ provides type-safe signal handling, and all of the GTK+ (and GLib, Pango et. al.) objects are available as standard C++ classes. Callbacks may also be class methods, which makes for cleaner code, since the class can contain object data without having to resort to passing in data as a function argument. These potential problems will become clearer in the next sections.
A user interface consists of different objects with which the user can interact. These include buttons which can be pushed, text entry fields, tick boxes, labels and more complex things such as menus, lists, multiple selections, colour and font pickers. Some example widgets are shown in Figure 1.
Not all widgets are interactive. For example, the user cannot usually interact with a label, or a framebox. Some widgets, such as containers, boxes and event boxes are not even visible to the user (there is more about this in the next section).
Different types of widget have their own unique properties. For example, a label widget contains the text it displays, and there are functions to get and set the label text. A checkbox may be ticked or not, and there are functions to get and set its state. An options menu has functions to set the valid options, and get the option the user has chosen.
The top-level of every GTK+ interface is the window. A window is what one might expect it to be: it has a title bar, borders (which may allow resizing), and it contains the rest of the interface.
In GTK+, a GtkWindow is a GtkContainer. In English, this means that the window is a widget that can contain another widget. More precisely, a GtkContainer can contain exactly one widget. This is usually quite confusing compared with the behaviour of other graphics toolkits, which allow one to place the controls on some sort of "form".
The fact that a GtkWidget can only contain one widget initially seems quite useless. After all, user interfaces usually consist of more than a single button. In GTK+, there are other kinds of GtkContainer. The most commonly used are horizontal boxes, vertical boxes, and tables. The structure of these containers is shown in Figure 2.
Figure 2 shows the containers as having equal size, but in a real interface, the containers resize themselves to fit the widgets they contain.
In other cases, widgets may be expanded or shrunk to fit the space allotted to them. There are several ways to control this behaviour, to give fine control over the appearance of the interface.
In addition to the containers discussed above, there are more complex containers available, such are horizontal and vertical panes, tabbed notebooks, and viewports and scrolled windows. These are out of the scope of this tutorial, however.
Newcomers to GTK+ may find the concept of containers quite strange. Users of Microsoft Visual Basic or Visual C++ may be used to the free-form placement of controls. The placement of controls at fixed positions on a form has no advantages over automatic positioning and sizing. All decent modern toolkits use automatic positioning. This fixes several issues with fixed layouts:
-
The hours spent laying out forms, particularly when maintaining existing code.
-
Windows that are too big for the screen.
-
Windows that are too small for the form they contain.
-
Issues with spacing when accommodating translated text.
-
Bad things happen when changing the font size from the default.
The nesting of containers results in a widget tree, which has many useful properties, some of which will be used use later. One important advantage is that they can dynamically resize and accommodate different lengths of text, important for internationalisation, when translations in different languages may vary widely in their size.
The Glade user interface designer can be very instructive when exploring how containers and widget packing work. It allows easy manipulation of the interface, and all of the standard GTK+ widgets are available. Modifying an existing interface is trivial, even when doing major reworking. Whole branches of the widget tree may be cut, copied and pasted at will, and a widget's properties may be manipulated using the "Properties" dialogue. While studying the code examples, Glade may be used to interactively build and manipulate the interface, to visually follow how the code is working. More detail about Glade is provided in a later section, where libglade is used to dynamically load a user interface.
Most graphical toolkits are event-driven, and GTK+ is no exception. Traditional console applications tend not to be event-driven; these programs follow a fixed path of execution. A typical program might do something along these lines:
-
Prompt the user for some input
-
Do some work
-
Print the results
This type of program does not give the user any freedom to do things in a different order. Each of the above steps might be a single function (each of which might be split into helper functions, and so on).
GTK+ applications differ from this model. The programs must react to events, such as the user clicking on a button, or pressing Enter in an text entry field. These widgets emit signals in response to user actions. For each signal of interest, a function defined by the programmer is called. In these functions, the programmer can do whatever needed. For example, in the ogcalc program, when the "Calculate" button is pressed, a function is called to read the data from entry fields, do some calculations, and then display the results.
Each event causes a signal to be emitted from the widget handling the event. The signals are sent to signal handlers. A signal handler is a function which is called when the signal is emitted. The signal handler is connected to the signal. In C, these functions are known as callbacks. The process is illustrated graphically in Figure 3.
A signal may have zero, one or many signal handlers connected (registered) with it. If there is more than one signal handler, they are called in the order they were connected in.
Without signals, the user interface would display on the screen, but would not actually do anything. By associating signal handlers with signals one is interested in, events triggered by the user interacting with the widgets will cause things to happen.
GTK+ is comprised of several separate libraries:
atk | Accessibility Toolkit, to enable use by disabled people. |
gdk | GIMP Drawing Kit (XLib abstraction layer - windowing system dependent part). |
gdk-pixbuf | Image loading and display. |
glib | Basic datatypes and common algorithms. |
gmodule | Dynamic module loader (libdl portability wrapper). |
gobject | Object/type system. |
gtk | GIMP Tool Kit (windowing system independent part). |
pango | Typeface layout and rendering. |
When using libglade another library is required:
glade | User Interface description loader/constructor. |
Lastly, when using C++, some additional C++ libraries are also needed:
atkmm | C++ ATK wrapper. |
gdkmm | C++ GDK wrapper. |
gtkmm | C++ GTK+ wrapper. |
glademm | C++ Glade wrapper. |
pangomm | C++ Pango wrapper. |
sigc++ | Advanced C++ signal/slot event handling (wraps GObject signals). |
This looks quite intimidating! However, there is no need to worry, since compiling and linking programs is quite easy. Since the libraries are released together as a set, there are few library interdependency issues.
Before starting to code, it is necessary to plan ahead by thinking about what the program will do, and how it should do it. When designing a graphical interface, one should pay attention to how the user will interact with it, to ensure that it is easy to understand, and efficient to use.
When designing a GTK+ application, it is useful to sketch the interface on paper, before constructing it. Interface designers such as Glade are helpful here, but a pen and paper are best for the initial design.
As part of the production (and quality control) processes in the brewing industry, it is necessary to determine the alcohol content of each batch at several stages during the brewing process. This is calculated using the density (gravity) in g/cm3 and the refractive index. A correction factor is used to align the calculated value with that determined by distillation, which is the standard required by HM Customs & Excise. Because alcoholic beverages are only slightly denser than water, the PG value is (density-1) x 100. That is, 1.0052 would be entered as 52.
Original gravity is the density during fermentation. As alcohol is produced during fermentation, the density falls. Traditionally, this would be similar to the PG, but with modern high-gravity brewing (at a higher concentration) it tends to be higher. It is just as important that the OG is within the set limits of the specification for the product as the ABV.
The ogcalc program performs the following calculation:
O = (R x 2.597) - (P x 1.644) - 34.4165 + C
If O is less than 60, then
A = (O - P) x 0.130
otherwise
A = (O - P) x 0.134
The symbols have the following meanings:
A | Percentage Alcohol By Volume |
C | Correction Factor |
O | Original Gravity |
P | Present Gravity |
R | Refractive Index |
The program needs to ask the user for the values of C, P, and R. It must then display the results, A and O. A simple sketch of the interface is shown in Figure 4.
Due to the need to build up an interface from the bottom up, due to the containers being nested, the interface is constructed starting with the window, then the containers that fit in it. The widgets the user will use go in last. This is illustrated in Figure 5.
Once a widget has been created, signal handlers may be connected to its signals. After this is completed, the interface can be displayed, and the main event loop may be entered. The event loop receives events from the keyboard, mouse and other sources, and causes the widgets to emit signals. To end the program, the event loop must first be left.
Many GTK+ applications are written in C alone. This section demonstrates the C/plain/ogcalc program discussed in the previous section. Figure 6 is a screenshot of the finished application.
This program consists of just three functions:
on_button_clicked_reset() - Reset the interface to its default state
on_button_clicked_calculate - Get the values the user has entered, do a calculation, then display the results.
main() - Initialise GTK+, construct the interface, connect the signal handlers, then enter the GTK+ event loop.
The program code is listed on pages 23-26. The source code is extensively commented, to explain what is going on.
To build the source, do the following:
cd C/plain cc 'pkg-config -cflags gtk+-2.0' -c ogcalc.c cc 'pkg-config -libs gtk+-2.0' -o ogcalc ogcalc.o
Roger Leigh
[I would recommend entering the code, it would be a valuable exercise in learning how a GTK application is built up. It may take a little while, but it certainly helped me to understand what is going on. - Ed]
// C/plain/ogcalc.c #include <gtk/gtk.h> GtkWidget *create_spin_entry( const gchar *label_text, const gchar *tooltip_text, GtkWidget **spinbutton_pointer, GtkAdjustment *adjustment, guint digits); GtkWidget *create_result_label( const gchar *label_text, const gchar *tooltip_text, GtkWidget **result_label_pointer); void on_button_clicked_reset( GtkWidget *widget, gpointer data); void on_button_clicked_calculate( GtkWidget *widget, gpointer data); /* This structure holds all of the widgets needed to get all the values for the calculation. */ struct calculation_widgets { GtkWidget *pg_val; // PG entry widget GtkWidget *ri_val; // RI entry widget GtkWidget *cf_val; // CF entry widget GtkWidget *og_result; // OG result label GtkWidget *abv_result; // ABV% result label }; int main(int argc, char *argv[]) { /* These are pointers to widgets used in constructing the interface, and later used by signal handlers. */ GtkWidget *window; GtkWidget *vbox1, *vbox2, *hbox1, *hbox2, *button1, *button2; GtkObject *adjustment, *hsep; struct calculation_widgets cb_widgets; gtk_init(&argc, &argv); // Initialise GTK+. /* Create a new top-level window. */ window = gtk_window_new(GTK_WINDOW_TOPLEVEL); /* Set the window title. */ gtk_window_set_title(GTK_WINDOW(window), "OG & ABV Calculator"); /* Disable window resizing */ gtk_window_set_resizable(GTK_WINDOW(window), FALSE); /* Connect the window close button ("destroy" event) to gtk_main_quit(). */ g_signal_connect(G_OBJECT(window), "destroy", gtk_main_quit, NULL); /* Create a GtkVBox to hold the other widgets. This contains other widgets, which are packed in to it vertically. */ vbox1 = gtk_vbox_new(FALSE, 0); /* Add the VBox to the Window. A GtkWindow /is a/ GtkContainer which /is a/ GtkWidget. GTK_CONTAINER casts the GtkWidget to a GtkContainer, like a C++ dynamic_cast. */ gtk_container_add(GTK_CONTAINER(window), vbox1); /* Display the Vbox. At this point, the Window has not yet been displayed, so the window isn't yet visible. */ gtk_widget_show(vbox1); /* Create a second GtkVBox. Unlike the previous VBox, the widgets it will contain will be of uniform size and separated by a 5 pixel gap. */ vbox2 = gtk_vbox_new(TRUE, 5); /* Set a 10 pixel border */ gtk_container_set_border_width( GTK_CONTAINER(vbox2), 10); /* Add this VBox to our first VBox. */ gtk_box_pack_start(GTK_BOX(vbox1), vbox2, FALSE, FALSE, 0); gtk_widget_show(vbox2); /* Create a GtkHBox. This is identical to a GtkVBox except that the widgets pack horizontally, instead of vertically. */ hbox1 = gtk_hbox_new(FALSE, 10); /* Add to vbox2. The function's other arguments mean to expand into any extra space alloted to it, to fill the extra space and to add 0 pixels of padding between it and its neighbour. */ gtk_box_pack_start(GTK_BOX(vbox2), hbox1, TRUE, TRUE, 0); gtk_widget_show (hbox1); /* A GtkAdjustment is used to hold a numeric value: the initial value, minimum and maximum values, "step" and "page" increments and the "page size". It's used by spin buttons, scrollbars, sliders etc. */ adjustment = gtk_adjustment_new(0.0, 0.0, 10000.0, 0.01, 1.0, 0); /* Call a helper function to create a GtkSpinButton entry together with a label and a tooltip. The spin button is stored in the cb_widgets.pg_val pointer for later use. */ hbox2 = create_spin_entry("PG:", "Present Gravity (density)", &cb_widgets.pg_val, GTK_ADJUSTMENT(adjustment), 2); /* Pack the returned GtkHBox into the interface. */ gtk_box_pack_start(GTK_BOX(hbox1), hbox2, TRUE, TRUE, 0); gtk_widget_show(hbox2); /* Repeat the above for the next spin button. */ adjustment = gtk_adjustment_new (0.0, 0.0, 10000.0, 0.01, 1.0, 0); hbox2 = create_spin_entry("RI:", "Refractive Index", &cb_widgets.ri_val, GTK_ADJUSTMENT(adjustment), 2); gtk_box_pack_start(GTK_BOX(hbox1), hbox2, TRUE, TRUE, 0); gtk_widget_show(hbox2); /* Repeat again for the last spin button. */ adjustment = gtk_adjustment_new (0.0, -50.0, 50.0, 0.1, 1.0, 0); hbox2 = create_spin_entry("CF:", "Correction Factor", &cb_widgets.cf_val, GTK_ADJUSTMENT(adjustment), 1); gtk_box_pack_start(GTK_BOX(hbox1), hbox2, TRUE, TRUE, 0); gtk_widget_show(hbox2); /* Now we move to the second "row" of the interface, for displaying the results. */ /* Firstly, a new GtkHBox to pack the labels into. */ hbox1 = gtk_hbox_new (TRUE, 10); gtk_box_pack_start(GTK_BOX(vbox2), hbox1, TRUE, TRUE, 0); gtk_widget_show (hbox1); /* Create the OG result label, then pack and display. */ hbox2 = create_result_label("OG:", "Original Gravity (density)", &cb_widgets.og_result); gtk_box_pack_start(GTK_BOX(hbox1), hbox2, TRUE, TRUE, 0); gtk_widget_show(hbox2); /* Repeat as above for the second result value. */ hbox2 = create_result_label("ABV %:", "Percent Alcohol By Volume", &cb_widgets.abv_result); gtk_box_pack_start(GTK_BOX(hbox1), hbox2, TRUE, TRUE, 0); gtk_widget_show(hbox2); /* Create a horizontal separator (GtkHSeparator) and add it to the VBox.*/ hsep = gtk_hseparator_new(); gtk_box_pack_start(GTK_BOX(vbox1), hsep, FALSE, FALSE, 0); gtk_widget_show(hsep); /* Create a GtkHBox to hold the bottom row of buttons. */ hbox1 = gtk_hbox_new(TRUE, 5); gtk_container_set_border_width( GTK_CONTAINER(hbox1), 10); gtk_box_pack_start(GTK_BOX(vbox1), hbox1, TRUE, TRUE, 0); gtk_widget_show(hbox1); /* Create the "Quit" button. We use a "stock" button-commonly-used buttons that have a set title and icon. */ button1 = gtk_button_new_from_stock(GTK_STOCK_QUIT); /* We connect the "clicked" signal to the gtk_main_quit() callback which will end the program. */ g_signal_connect(G_OBJECT(button1), "clicked", gtk_main_quit, NULL); gtk_box_pack_start(GTK_BOX(hbox1), button1, TRUE, TRUE, 0); gtk_widget_show(button1); /* This button resets the interface. */ button1 = gtk_button_new_with_mnemonic("_Reset"); /* The "clicked" signal is connected to the on_button_clicked_reset() callback above, and our "cb_widgets" widget list is passed as the second argument, cast to a gpointer (void). */ g_signal_connect(G_OBJECT(button1), "clicked", G_CALLBACK(on_button_clicked_reset), (gpointer)&cb_widgets); /* g_signal_connect_swapped is used to connect a signal from one widget to the handler of another. The last argument is the widget that will be passed as the first argument of the callback. This causes gtk_widget_grab_focus to switch the focus to the PG entry. */ g_signal_connect_swapped(G_OBJECT(button1), "clicked", G_CALLBACK(gtk_widget_grab_focus), (gpointer)GTK_WIDGET(cb_widgets.pg_val)); /* This lets the default action (Enter) activate this widget even when the focus is elsewhere. This doesn't set the default, it just makes it possible to set.*/ GTK_WIDGET_SET_FLAGS(button1, GTK_CAN_DEFAULT); gtk_box_pack_start(GTK_BOX(hbox1), button1, TRUE, TRUE, 0); gtk_widget_show(button1); /* The final button is the Calculate button.*/ button2 = gtk_button_new_with_mnemonic("_Calculate"); /* When the button is clicked, call the on_button_clicked_calculate() function. This is the same as for the Reset button.*/ g_signal_connect(G_OBJECT(button2), "clicked", G_CALLBACK(on_button_clicked_calculate), (gpointer)&cb_widgets); /* Switch the focus to the Reset button when the button is clicked. */ g_signal_connect_swapped(G_OBJECT(button2), "clicked", G_CALLBACK(gtk_widget_grab_focus), (gpointer)GTK_WIDGET(button1)); /* As before, the button can be the default.*/ GTK_WIDGET_SET_FLAGS(button2, GTK_CAN_DEFAULT); gtk_box_pack_start(GTK_BOX(hbox1), button2, TRUE, TRUE, 0); /* Make this button the default. Note the thicker border in the interface-this button is activated if you press enter in the CF entry field. */ gtk_widget_grab_default(button2); gtk_widget_show(button2); /* Set up data entry focus movement. This makes the interface work correctly with the keyboard, so that you can touch-type through the interface with no mouse usage or tabbing between the fields. */ /* When Enter is pressed in the PG entry box, focus is transferred to the RI entry. */ g_signal_connect_swapped( G_OBJECT(cb_widgets.pg_val), "activate", G_CALLBACK(gtk_widget_grab_focus), (gpointer)GTK_WIDGET(cb_widgets.ri_val)); /* RI -> CF. */ g_signal_connect_swapped( G_OBJECT(cb_widgets.ri_val), "activate", G_CALLBACK(gtk_widget_grab_focus), (gpointer)GTK_WIDGET(cb_widgets.cf_val)); /* When Enter is pressed in the RI field, it activates the Calculate button. */ g_signal_connect_swapped( G_OBJECT(cb_widgets.cf_val), "activate", G_CALLBACK(gtk_window_activate_default), (gpointer)GTK_WIDGET(window)); /* The interface is complete, so finally we show the top-level window. This is done last or else the user might see the interface drawing itself during the short time it takes to construct. It's nicer this way. */ 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; } /* A utility function for UI construction. It constructs part of the widget tree, then returns its root. */ GtkWidget *create_spin_entry( const gchar *label_text, const gchar *tooltip_text, GtkWidget **spinbutton_pointer, GtkAdjustment *adjustment, guint digits) { GtkWidget *hbox, *eventbox, *spinbutton, *label; GtkTooltips *tooltip; /* A GtkHBox to pack the entry child widgets into. */ hbox = gtk_hbox_new(FALSE, 5); /* An eventbox. This widget is just a container for widgets (like labels) that don't have an associated X window, and so can't receive X events. This is just used to we can add tooltips to each label. */ eventbox = gtk_event_box_new(); gtk_widget_show(eventbox); gtk_box_pack_start(GTK_BOX(hbox), eventbox,FALSE, FALSE, 0); /* Create a label. */ label = gtk_label_new(label_text); /* Add the label to the eventbox */ gtk_container_add(GTK_CONTAINER(eventbox), label); gtk_widget_show(label); /* Create a GtkSpinButton and associate it with the adjustment. It adds/substracts 0.5 when the spin buttons are used, and has digits accuracy. */ spinbutton = gtk_spin_button_new( adjustment, 0.5, digits); /* Only numbers can be entered. */ gtk_spin_button_set_numeric( GTK_SPIN_BUTTON(spinbutton), TRUE); gtk_box_pack_start(GTK_BOX(hbox), spinbutton, TRUE, TRUE, 0); gtk_widget_show(spinbutton); /* Create a tooltip and add it to the EventBox previously created. */ tooltip = gtk_tooltips_new(); gtk_tooltips_set_tip(tooltip, eventbox, tooltip_text, NULL); *spinbutton_pointer = spinbutton; return hbox; } /* A utility function for UI construction. It constructs part of the widget tree, then returns its root. */ GtkWidget *create_result_label( const gchar *label_text, const gchar *tooltip_text, GtkWidget **result_label_pointer) { GtkWidget *hbox, *eventbox, *result_label, *result_value; GtkTooltips *tooltip; /*A GtkHBox to pack entry child widgets into*/ hbox = gtk_hbox_new(FALSE, 5); /* As before, a label in an event box with a tooltip. */ eventbox = gtk_event_box_new(); gtk_widget_show(eventbox); gtk_box_pack_start(GTK_BOX(hbox), eventbox, FALSE, FALSE, 0); result_label = gtk_label_new(label_text); gtk_container_add(GTK_CONTAINER(eventbox), result_label); gtk_widget_show(result_label); /* This is a label, used to display the OG result. */ result_value = gtk_label_new (NULL); /* Because it's a result, it is set "selectable", to allow copy/paste of the result, but it's not modifiable. */ gtk_label_set_selectable( GTK_LABEL(result_value), TRUE); gtk_box_pack_start(GTK_BOX(hbox), result_value, TRUE, TRUE, 0); gtk_widget_show(result_value); /* Add the tooltip to the event box. */ tooltip = gtk_tooltips_new(); gtk_tooltips_set_tip(tooltip, eventbox, tooltip_text, NULL); *result_label_pointer = result_value; return hbox; } /* This is a callback function. It resets the values of the entry widgets, and clears the results. "data" is the calculation_widgets structure, which needs casting back to its correct type from a gpointer (void) type. */ void on_button_clicked_reset( GtkWidget *widget, gpointer data ) { /* Widgets to manipulate. */ struct calculation_widgets *w; w = (struct calculation_widgets *) data; gtk_spin_button_set_value( GTK_SPIN_BUTTON(w->pg_val), 0.0); gtk_spin_button_set_value( GTK_SPIN_BUTTON(w->ri_val), 0.0); gtk_spin_button_set_value( GTK_SPIN_BUTTON(w->cf_val), 0.0); gtk_label_set_text( GTK_LABEL(w->og_result), ""); gtk_label_set_text( GTK_LABEL(w->abv_result), ""); } /* This callback does the actual calculation. Its arguments are the same as for on_button_clicked_reset(). */ void on_button_clicked_calculate( GtkWidget *widget, gpointer data ) { gdouble pg, ri, cf, og, abv; gchar *og_string, *abv_string; struct calculation_widgets *w; w = (struct calculation_widgets *) data; /* Get the numerical values from the entry widgets. */ pg = gtk_spin_button_get_value( GTK_SPIN_BUTTON(w->pg_val)); ri = gtk_spin_button_get_value( GTK_SPIN_BUTTON(w->ri_val)); cf = gtk_spin_button_get_value( GTK_SPIN_BUTTON(w->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(w->og_result), og_string); gtk_label_set_markup( GTK_LABEL(w->abv_result), abv_string); g_free(og_string); g_free(abv_string); }
Notes:
More fields may be available via dynamicdata ..