Tutorial: Simple Gnome Application Using libglade and C/GTK+

Gnome Application 3

This tutorial is intended for those people interested in getting started developing Gnome applications using GTK+. Specifically, it uses the Glade Interface Designer to build a the user interface and C/GTK+ to write the code for a Gnome application. The application will simply be a window with a typical toolbar and status bar. This application will be used in later tutorials to demonstrate various other aspects of Gnome programming. The complete project can be downloaded here: gnome3-0.1.tar.gz This tutorial is based on the older version of Glade (glade2). For an up-to-date tutorial on using Glade 3, see this post: GTK+ and Glade3 GUI Programming Tutorial - Part 1 You should have a basic understanding of the C programming language. If you don't, I recomend the old tried and true C Programming Language 2nd Ed. by Brian W. Kernighan and Dennis M. Ritchie. Just about any C programmer you meet will own or has learned from this book. Although you don't need to be an expert as you can learn a lot as you go, you should know the basic structure of c programs and header files, compiler directives, pointers, casting, arrays, etc. Don't be intimidated by the immense "this is how you do it" attitude out there. Your first programs may be a mess and done all "wrong". That's how we learn. First make it work, and then learn how to make it work the right way. You may also want to pick up The Official GNOME 2 Developer's Guide. You should also have a very basic and conceptual understanding of what GTK+is. Basically, if you are reading this tutorial, you should know that you want to develop Gnome applications using GTK+. It couldn't hurt to go through a basic GTK+ hello world example such as the one at GTK+ 2.0 Tutorial but isn't required as I'll discuss the code in detail.

Required Software

At the time of writing, I'm running Fedora Core 4 Linux. When installing Fedora, you can optionally install a variety of development libraries and tools. Alternately, Fedora is an RPM based distribution and you can install development packages using yum. It is beyond the scope of this tutorial to go into the details of installing all the required packages to develop Gnome applications. For the RPMs, the development libraries typically have a '-devel' suffix (ie: gtk2-devel-2.6.10-2.fc4.4). To work through this tutorial, you should have the following installed on your system:
  • GTK+ - The graphic interface toolkit on which our applications are built.
  • Glade - Gnome utility to visually (and quickly) build a GTK+ based interface for your application.
  • libglade - A library we'll use in our application which allows us to dynamically generate an interface from an XML file created by Glade.
  • Devhelp (optional) - Contains the API documentation for all of your development libraries in a convenient, searchable application. All the documentation is also accessible and downloadable at the Gnome Development Site.
  • Anjuta (optional) - A C/C++ Integrated Development Environment (IDE) with specific Gnome and GTK integration. The CVS (and future) versions support Devhelp and Glade3 plugins, making it your all-in-one tool. All the bells and whistles asside, both the 1.2.x and the 2.x versions make a great editor.
If you are unclear on what you need to have installed to develop Gnome applications or how to do it, search google or ask for help from one of the gnome/gtk mailing lists, the Gnome Support Forums, or my GTK+ Forum.

Didn't I Already Write This Tutorial?

I previously wrote 2 tutorials on getting started with Gnome programming using the Anjuta IDE and the Glade Interface Designer. With this tutorial, I'm going to repeat what was done in both of those tutorials, that is, going to walk you through building a very basic Gnome application from which to build upon. However, I will be taking a different approach this time. The previous tutorials used Glade 2.6.0 and Anjuta 1.2.2. At the time of writing, the current stable version of Glade is 2.10.0 and Anjuta 1.2.3. However, Anjuta 2.x is just around the corner and many people (myself included) are now using "unstable" versions of Anjuta 2.x from CVS. Glade3 is also on the near horizon. There are significant changes in Anjuta 2.x and I don't want to write a tutorial on a version of a program that is likely to change dramatically by the time it is realeased. Additionally, Anjuta 1.2.x is likely to generate/support deprecated function calls and autotools methods. If you are using Anjuta 1.2.x and are looking for something geared specifically to that, you may want to check out those older tutorials: Gnome Programming Tutorial 1 - Getting Started with Anjuta/Glade Gnome Programming Tutorial 2 - Using LibGlade with Anjuta/Glade

Let's Get Started

For the purposes of this tutorial, I will be building this project on my system at /home/mcarrick/Projects/gnome3/. All the screenshots and open/save locations will reflect that location. When discussing the path hierarchy, I will be talking about it relative to that path. For example, putting source code in the /src directory means that it is actually in the /home/mcarrick/Projects/gnome3/src directory. Got it? Good. So you should have made a 'gnome3' directory somewhere and a 'src' directory within that directory. The project we will be building is giong to be called 'gnome3', and is just a basic Gnome application. The '3' comes from the fact that I already have a gnome1 and a gnome2 from some older tutorials.

Building the Interface in Glade

The Glade Interface Designer, or just Glade, is a Gnome application which allows us to layout our widgets visually rather than through C code. What's a widget right? A widget is much like an "object" in object oriented vocabulary. Only most widgets in GTK+ are physically visible. In Windows programming, we might call these "controls". A button is a widget, the label on that button is another widget, the window is a widget, the scroll bars are widgets, etc. Widgets, like objects or classes, can be derived from other widgets. A window widget may be derived from a container base widget etc. This relationship is known as the widget or object hierarchy. So let's see what I'm talking about. Start Glade and select 'New' from the 'Project' menu, and then click the 'New GNOME Project' button. Now, before we go any further, let's get our windows visible. We'll learn what they all do as the tutorial progresses. For now, just make sure you have the Pallette, Property Editor, and Widget Tree windows open. You can do so from the 'View' menu in the main Glade window. We're going to add a Gnome Application Window to our project by clicking the icon GnomeApp Icon located in the Glade toolbar under 'Gnome'. A window should pop up. That is the Gnome Application Window and the main window for our application. The Gnome Application Window is actually a GnomeApp widget, part of libgnomeui. The API documentation states: "The GnomeApp widget and associated functions provides the easiest way to create an almost complete GNOME application in libgnomeui". It's very true. It's a very easy way to setup the base of a Gnome application. Now back to talking about widgets and object hierarchy, the object hierachy for the GnomeApp widget is shown below.
As we can see, the GnomeApp is derived from the GtkWindow, and so on up the tree. At the top of the tree is GObject which is the base object for all other objects. It is part of glib (the core, low-level functions and utilities at the base of Gnome and GTK). It's prefix is a 'G'. From now on, associate a single 'g' with glib. Objects that start with 'G' and functions that start with 'g_' are typically going to asociated with glib. Then, we have several objects with a prefix "Gtk". Those are part of GTK. Objects that begin with 'Gtk' and functions that begin with 'gtk_' are typically associated with GTK+. And finally, the GnomeApp has 'Gnome' as the prefix. It is part of the Gnome UI library. Objects that begin with 'Gnome' and functions that begin with 'gnome_' are associated with the Gnome libraries. This will become quite obvious as you begin writing code later on in the tutorial.Back to Glade, we are going to rename the GnomeApp widget to something more nice and give it a title, which shows up in the window's "titlebar". To rename the widget, click to select 'app1' in Glade's main window or the Widget Tree window, and change the 'Name:' property in Glade's Properties window to 'app_window'. Also change the 'Title' property in Glade's Properties window to 'Gnome Application 3'. Below is a screenshot from my system.

Glade Interface screenshot

Before we go any further, let's save the glade file (an XML file). Click the 'Save' icon. In the save dialog, we're going to save the file as src/gnome3.glade. Glade can generate C or C++ source code which compiles to build the application you design, however, that method is no longer the ideal way to use Glade. Instead, the Glade project file, a .glade file, is parsed by the application at run-time using libglade. This allows you to modify/update the UI without having to recompile the entire program and allows the program to be separate from the interface. So, when saving, the only thing we're actually concerned with is the name and location of the 'Project' file. We're not going to let Glade generate the code so the other parameters are not relevant for this tutorial. My screenshot is below.

Glade Interface screenshot

Glade Interface screenshot

Let's see what widgets we have layed out (No, it's not just a GnomeApp). In the Widget Tree window of Glade, you can expand the GnomeApp widget to see what widgets it contains. This is shown in the image to the left. It contains a "bonobodock" and an "appbar". The bonobodock contains two "bonobodockitems". The first bonobodockitem contains a "menubar" and the second one contains a "toolbar". That's quite a bit for just a couple clicks of the mouse. Now, I'm a bit of a stickler about naming things and will go through and rename just about all of these widgets--or at least any widget I inted to access via code later. Change the widgets' names by selecting the widget in the Widget Tree window and then changing the 'Name' in the Properties window.
  • menubar1 => main_menubar
  • toolbar1 => main_toolbar
  • toolbutton1 => toolbutton_new
  • toolbutton2 => toolbutton_open
  • toolbutton3 => toolbutton_save
  • appbar1 => main_appbar
We'll leave the menu items alone for now, as we're going to edit those later using a menu editor. Your Widget Tree window should now look like the one show to the right.

Glade Interface screenshot

Now there's just one more thing to do in glade. We're going to attach a handler to the 'New' toolbar button's 'clicked' signal. A signal can be thought of as an event that occurs. Most objects have a variety of signals they "emit" for various events and they also inherit there base objects signals. You can find out what signals an object has by refering to the API documentation for that object. The handler for the signal, is a callback function, and is called when that signal is "emitted". Click on the 'toolbar_new' button to select it, and then switch to the 'Signals' tab in the Properties window of Glade. Then at the bottom of the window, there is a box labeled 'Signal:'. Click in the box to show the Select Signal dialog. You can select any of the signals associated with the GtkToolButton widget or the widgets it's derived from. Choose the 'clicked' signal and click the 'Ok' button. Now back in the Properties window,the 'Handler:' box has been filled in with "on_toolbutton_new_clicked". We're going to leave that as it is. Click the 'Add' button at the bottom of the window to apply these changes. You window should look like mine below. "on_toolbutton_new_clicked" is the name of the function we need to write that will be called when the 'New' toolbar button is clicked.

Glade Interface screenshot

Save the glade project and close glade.

Writing The Code

This is where the fun begins. Open up your text editor and create src/main.c. We're going to step through this code one section at a time starting from the top. You can view all of main.c here: main.c.
 1: /* the config.h file generated by the autotools */
 2: #ifdef HAVE_CONFIG_H
 3: #  include <config.h>
 4: #endif
This section has a few preprocessor directives to include a file called config.h. The config.h file is generated by a program called 'autoheader' which is part of the GNU 'autotools'. Although 'autotools' are an important part of Gnome application development, it is beyond the scope of this document.
 6: /* this will hold the path to the glade file after the "make install" */
 7: #define GLADE_FILE PACKAGE_DATA_DIR"/gnome3/gnome3.glade"
Here we're defining the path to our gnome3.glade XML file. PACKAGE_DATA_DIR is defined in the Makefile generated by the autotools and is platform dependent. On my Fedora system, PACKAGE_DATA_DIR ends up being /usr/local/share/ and thus, after compiling and installing this application on my system (using the make install command), the glade file for this application will be located at /usr/local/share/gnome3/gnome3.glade.
 9: #include <gnome.h>
10: #include <glade/glade.h>
These includes should look fairly obvious, however, I want to point one little thing out. The included header files will recursively call the libraries from which they are derived. In this case, gnome.h is going to call gtk.h which is going to call glib.h etc. So it is not necessary to include gtk/gtk.h for a Gnome application. The glade/glade.h include is for libglade which allows us to generate our user interface from the glade XML file.
12: /* callback function prototypes */
13: static gint delete_event_cb(GtkWidget* w, GdkEventAny* e, gpointer data);
14: static gint destroy_cb(GtkWidget* w, GdkEventAny* e, gpointer data);
These two lines declare "callback" functions, which is why I have added the '_cb' suffix to their names. These are going to be "connected" to a signal. I'm going to leave it at that for now, we'll see these again further down in the code.
17: int main (int argc, char *argv[])
18: {
19: 	GtkWidget *app_window;          /* main application window widget */
20: 	GladeXML *gxml;                 /* glade xml file */
When we layed out our interface in the previous section, I pointed out the naming conventions used with the widgets. We see that again here where we are declaring two variables. First, app_window is a pointer to a GtkWidget type. Notice the 'Gtk' prefix. The GtkWidget type is therefore part of the GTK libraries. The next line we declare gxml as pointer to a GladeXML type. This must be part of the Glade library, or libglade. At first you may be asking why we are declaring app_window as a GtkWidget and not a GnomeApp. Here's that object hierarchy again. Remember that GnomeApp was derived from several objects going back to a GtkWidget and eventually all the way back to a GObject. Thus, a GnomeApp widget is a GtkWidget, and for that matter, it is a GObject. A GtkWidget is the base object of all widgets, and it has functions that are common to all widgets. For example there is a function to show widgets called gtk_widget_show() which takes a pointer to a GtkWidget as a parameter. Therfore we typically declare any widget as their base object class, a GtkWidget. So we now have app_window which will reference our GnomeApp widget (but does not yet) and we have gxml which references a GladeXML object which we will use to build the interface from the Glade XML file.
22:        /* initialize libgnome */
23:        gnome_program_init (PACKAGE, VERSION, LIBGNOMEUI_MODULE,
24:                            argc, argv,
25:                            NULL);
All GTK+ and Gnome applications have to call a function to initialize the libraries before any GTK+ functions are used. In a pure GTK+ program, you will see gtk_init(). This function, gnome_program_init() is specificly for Gnome applications. A lot of the code you see, especially in tutorials, you will see gnome_init() instead-- which actually looks nicer because it doesn't have all those arguments being passed to it. However, gnome_init() is a deprecated function and should not be used. You should read about the gnome_program_init() in the API documentation. You're definetely going to need to know about this function, however, for now I'm just going to briefly describe it as it applies to this program. PACKAGE and VERSION are defined in the config.h file which is automatically created by the autotools. For this program, they are 'gnome3' and '0.1' respectively. LIBGNOMEUI_MODULE is defined in libgnomeui.h (which we've included indirectly by including gnome.h) and is a module containing the information to initialize the gnome libraries. argc and argv are the standard C/C++ application command line arguments we know and love. argc is the count of the command line arguments and argv is the string array of the command line arguments. Next, the function takes a series of attribute name/value pairs terminated by a NULL. To keep things simple and minimal for now, we're just passing the NULL termination. (An empty list of attribute name/value pairs). In many of the tutorials and code you see using libglade, you will find often find a call to glade_init() at this point to initialize the Glade libraries (libglade). This is no longer a necessary call as new versions of the Glade libraries will be initialized on demand.
27:        /* create GladeXML object and connect signals */
28:        gxml = glade_xml_new (GLADE_FILE, NULL, NULL);
29:        glade_xml_signal_autoconnect (gxml);
This is where we use libglade to build our interface from the Glade XML file we previously created. If you are familiar with OOP languages (classes) then you're familiar with having to instantiate an object often using a keyword such as "new" (obj = new SomeObject;). C is not technically an object-oriented language, however, GTK+ is object based. This is achived using functions. With most GTK+ objects there will be a series of functions to create a new one of those objects, much like class constructors. The call to glade_xml_new creates a new GladeXML object which gxml references, and it also creates all the widgets in the Glade file which we pass to the function as the first argument (we defined GLADE_FILE up at line 7). We're passing NULL as the other two parameters as we don't need to worry about them yet. One is used if we only want to build certain widgets in the glade file and the other is used for translation. The call to glade_xml_signal_autoconnect() is a variation on another function, glade_xml_signal_connecr(). As it's name implies, the "autoconnect" function automatically "connects" the signals defined in the glade file to thier callback functions (signal "handlers") in the C code. Remember when building our interface, we specified that the "clicked" signal of the toolbar button should be "handled" by the function on_toolbar_new_clicked(). This call reads that information from the glade file and "connects" that signal to it's callback funciton.
31:        /* get the app_window from the glade XML file */
32:        app_window = glade_xml_get_widget (gxml, "app_window");
Now, it's all fine and dandy that libglade can "build" our interface from that XML file, but how do we reference those widgets then? The answer is glade_xml_get_widget(), where the 2 parameters are the GladeXML object gxml and the name of the widget we want to "get" respectively. In this case, we're getting "app_window" which is our GnomeApp widget. After this call, the GtkWidget pointer app_window references the GnomeApp widget. We'll be using this a few lines later.
34:        /* Connect signals for termination of application */
35:        g_signal_connect(G_OBJECT(app_window), "delete_event",
36:             G_CALLBACK(delete_event_cb), NULL);
38:        g_signal_connect(G_OBJECT(app_window), "destroy",
39:             G_CALLBACK(destroy_cb), NULL);
These two lines are also "connecting" signals to functions which "handle" that event. We could have set these up in Glade just as we did with the "click" signal for the toolbutton and let the glade_xml_autoconnect() function connect the signals, however, I wanted to do it here in the code as a demonstration of how the signals are connected and show some casting macros. It's very important to understand the concept of signals and callback functions. There are 4 arguments being passed to the g_signal_connect() function calls. First, G_OBJECT(app_window), which is our GtkWidget app_window being cast into a GObject pointer. G_OBJECT is a macro which does the casting. You'll find these casting macros are available for all the casting you need to do. The g_signal_connect takes a GObject pointer as it's first argument, however, our app_window is defined as a GtkWidget pointer, which is why we must cast it when passing it as a parameter. Remember that the GtkWidget is derived from a GObject, which is why we can cast it into a GObject pointer. Another example, is if we have a function that requires a GtkWindow such as gtk_window_get_title(). We could cast app_window into a GtkWindow object pointer using the GTK_WINDOW macro. This is where that object hierarchy is so cool. We can use the functions for any of the classes from which a widget is derived by casting the widget poitner to the appropriate type. The second parameter in the g_signal_connect() function is the name of the signal which we are connecting a function to. As you can see, we have 2 signals that we are connecting functions to. The first is the "delete_event" signal which is emitted when we request that the window be closed. Clicking the little 'x' in the top of the window will invoke this signal. Next is the "destroy" signal which is emitted when the window is destroyed. We'll see why this is important later. The third parameter in the g_signal_connect() function is the function which we are connecting to the signal. It is the "callback" function. We have to first cast the function into a GCallback pointer using the G_CALLBACK macro. And finally we have the 4th parameter which is the data that we will pass to our callback functions, in this case, nothing (NULL). What this means, is that when the "delete_event" signal is emitted, our function named delete_event_cb() will be called. When the "destroy" signal is emitted, our function destroy_cb() will be called.
41:        /* show the main window */
42:        gtk_widget_show (app_window);
Now we're ready to actually show our window. The gtk_widget_show() function shows a widget. We simply pass it our GtkWidget pointer, app_window which references our GnomeApp widget--the main application window.
44:        /* begin main GTK loop */
45:        gtk_main ();
47:        return 0;
48: }
Now, the gtk_main() function is an "endless" loop. The loop will call our functions based on "events" occuring. A perfect example, is that "click" signal we setup for the toolbutton. When that button is clicked, the gtk_main() loop turns over control to our "handler" on_toolbutton_new_clicked() after which control resumes in the endless loop. This is how we achive the "event" based application. To break out of the loop, one of our functions has to call gtk_main_quit(), which is why we have a callback for the "destroy" signal as we will see in a moment.
50: static gint delete_event_cb(GtkWidget* w, GdkEventAny* e, gpointer data)
51: {   
52:         /* callback for "delete" signal */
53:         g_print("main.c:delete_event_cb()\n");
54:         return 0;
55: }
57: static gint destroy_cb(GtkWidget* w, GdkEventAny* e, gpointer data)
58: {
59:        /* callback for "destroy" signal */
60:        g_print("main.c:destroy_event_cb()\n");  
62:        /* quit application */
63:        gtk_main_quit();
64:        return 0;
65: }
We don't actually need to handle the "delete-event" for this application, however, I've added it to demonstrate when the events occur. In both the delete_event_cb() and the destroy_cb() I'm calling g_print() (which is very much like the printf function) to print out the name of the function. This is just so that we can see these events occur. Notice however, that the handler for "destroy" signal has a call to gtk_main_quit(). This is very important. Recall that the gtk_main() is an endless loop. If a user closes that window, it destroys the widget but the execution is still in that gtk_main() loop and the program hasn't actually terminated. It would "appear" to because the window is gone, but would still be running. By capturing the "destroy" signal we can break out of that loop when the user destroys (closes) the window. Aha, it's all making sense now. One more thing to note, is the "delete-event" signal can return a value if you want to prevent it from occuring. The "delete-event" occurs when the widget (the window) is requesting to be destroyed. We can stop this from occuring if, for example, we want to first prompt the user to be sure they want to quit. With a dialog box and conditional code, we could return 0 if they are sure they want to quit, and 1 if they do not actually want to quit.
67: void 
68: on_toolbutton_new_clicked(GtkWidget *w, GdkEventKey *e)
69: {
70: /* the 'New' toolbar was clicked */
71: g_print("main.c:on_toolbutton_new_clicked()");
72: }
Oh wow, we're almost done! This last function is the callback for the "click" signal we setup in Glade. I'm simply printing to the screen again so we can see that the signal was emitted and our callback function called. That's it, let's run it!

Build and Run the Application

As I mentioned earlier, the autotools are an important part of Gnome development. However, these tools are very detailed and beyond the scope of this document. Therefore, I'm going to provide you with a tarball containing the project files which were generated by the Anjuta IDE (version 2.x from CVS). Download gnome3-0.1.tar.gz You may then copy your main.c and gnome3.glade files to the src directory, or just use mine. To build our application, we make it just like any other tarball:
tar -xzf gnome3-0.1.tar.gz
cd gnome3-0.1
make install
And then we run the program by simply typing it's name gnome3. When we do so, you're going to see several warnings from libglade. This is because the GnomeApp widget has actually already specified callback functions for the "activate" signal on each of the menu items. However, we did not define the functions for them in the code, so the glade_xml_autoconnect function cannot connect the signals. I left this on purpose so you could see these errors and know what they mean.(gnome3:19796): libglade-WARNING **: could not find signal handler 'on_save1_activate'. (gnome3:19796): libglade-WARNING **: could not find signal handler 'on_preferences1_activate'. (gnome3:19796): libglade-WARNING **: could not find signal handler 'on_quit1_activate'. (gnome3:19796): libglade-WARNING **: could not find signal handler 'on_cut1_activate'. (gnome3:19796): libglade-WARNING **: could not find signal handler 'on_about1_activate'. (gnome3:19796): libglade-WARNING **: could not find signal handler 'on_open1_activate'. (gnome3:19796): libglade-WARNING **: could not find signal handler 'on_paste1_activate'. (gnome3:19796): libglade-WARNING **: could not find signal handler 'on_clear1_activate'. (gnome3:19796): libglade-WARNING **: could not find signal handler 'on_new1_activate'. (gnome3:19796): libglade-WARNING **: could not find signal handler 'on_save_as1_activate'. (gnome3:19796): libglade-WARNING **: could not find signal handler 'on_properties1_activate'. (gnome3:19796): libglade-WARNING **: could not find signal handler 'on_copy1_activate'. Once the window has popped up, you can click the "new" button and see our message print to the screen. Then close the window using the "x" in the corner of the title bar. You should then see the "delete-event" and "destroy" signal's callbacks print out to the screen as well. We know that the "destroy" even successfully called the gtk_main_quit() because we will get back to a prompt. If we had not done so, the gtk_main() loop would still be running and we'd have to hit CTRL+C to force the application to quit. main.c:on_toolbutton_new_clicked() main.c:delete_event_cb() main.c:destroy_cb()

Gnome Application 3

Please feel free to leave a comment below, I appreciate feedback.

Did you enjoy Tutorial: Simple Gnome Application Using libglade and C/GTK+? If you would like to help support my work, A donation of a buck or two would be very much appreciated.
blog comments powered by Disqus
Linux Servers on the Cloud IN MINUTES