Programming GTK+ GUIs using Glade

Introduction to GTK+

The first stable release of GTK+ came out in 1998, so it has been around for nearly 20 years (as of 2017). GTK+ was initially developed as the widget toolkit for the Gnu Image Manipulation Program (the GIMP). I chose to use GTK+ over KDE many years ago, when KDE had a rather "childish" appearance with lots of bright colours and animated icons than moved when you clicked them.

Through these tutorials and examples I hope to show how to use GTK+ to quickly and easily produce simple user interfaces that look good. I'm not going to attempt to cover all of GTK+, but there is plenty of documentation available to help you continue to explore GTK+ beyond the sub-set that I've covered.

GTK+ 2 vs GTK+ 3

There are two current releases of GTK+, Versions 2 and 3. Originally it was planned that GTK+ 2 would be depreciated by now, but some unfavourable reactions to the initial releases of GNOME 3/GTK+ 3 by application developers caused many to delay moving to GTK+ 3 until it had matured. Consequently old and new applications have continued to be developed using GTK+ 2.

Personally I have only recently started to move to using GTK+ 3 for new application development and I'm starting the process of porting some of my existing GTK+ 2 code to GTK+3 .

Glade

Glade is a GTK+ interface design tool, and it was also first released in 1998. Current versions exist for both GTK+ 2 and GTK+ 3.

Early versions of Glade produced C code snippets that could be incorporated into applications to create and display the desired windows and widgets. Code production was superseded by producing a description of the interface using XML, and the use of "libglade" which read the xml file and created the widgets at run time.

libglade has in turn been replaced by the GtkBuilder class, and now Glade produces xml files for use with GtkBuilders.

Tested Platforms

All of the examples and tutorials have been tested on X86-64 PCs running MINT-17.3, MINT-18 and on a Raspberry Pi 3B running PIXEL.

C Programming

All the examples are written in C but note that this is NOT a C programming tutorial, it assumes you have at least a basic understanding of C and that you understand how pointers work.

Typography

Commands to be typed and programme output are shown like this:

petero@HP173 ~/GtkClock $ ls -lrt
total 92
-rw-r--r-- 1 petero petero  2935 Mar 21 21:39 Clock.glade
-rw-r--r-- 1 petero petero  8400 Mar 21 21:39 Clock.c
-rw-r--r-- 1 petero petero   364 Mar 21 21:40 CMakeLists.txt
-rw-r--r-- 1 petero petero   726 Mar 22 07:58 README
-rw-r--r-- 1 petero petero 14472 Mar 22 08:05 CMakeCache.txt
-rw-r--r-- 1 petero petero  4542 Mar 22 08:05 Makefile
-rw-r--r-- 1 petero petero  1573 Mar 22 08:05 cmake_install.cmake
-rwxr-xr-x 1 petero petero 33508 Mar 22 08:05 Clock
drwxr-xr-x 5 petero petero  4096 Mar 22 08:05 CMakeFiles
petero@HP173 ~/GtkClock $ 

Getting Started

All of the libraries needed to run GTK+ 3 applications are installed by default on modern Linux distributions, but to develop GKT+ 3 applications additional files need to be installed. Note that there are a lot of files needed because GTK+ 3 depends on a number of other libraries (such as pango for drawing text). Use the following command to install the required development packages. (Note the output has been "snipped" to make it shorter)


pi@raspberrypi:~ $ sudo apt-get install libgtk-3-dev
Reading package lists... Done
Building dependency tree       
Reading state information... Done
The following packages were automatically installed and are no longer required:
  fonts-lyx libjs-jquery-ui python-dateutil python-imaging python-matplotlib-data python-mock
  python-pyparsing python-tz
Use 'apt-get autoremove' to remove them.
The following extra packages will be installed:
  autoconf automake autopoint autotools-dev debhelper dh-autoreconf dh-strip-nondeterminism
  gettext gir1.2-atspi-2.0 intltool-debian libarchive-zip-perl libasprintf-dev
  libatk-bridge2.0-dev libatk1.0-dev libatspi2.0-dev libcairo-gobject2
  libcairo-script-interpreter2 libcairo2 libcairo2-dev libdbus-1-3 libdbus-1-dev
  libdbus-glib-1-dev libfile-stripnondeterminism-perl libfontconfig1-dev libgdk-pixbuf2.0-dev
  libgettextpo-dev libgettextpo0 libglib2.0-dev libharfbuzz-dev libharfbuzz-gobject0 libice-dev
  libltdl-dev libmail-sendmail-perl libpango1.0-dev libpcre3-dev libpcrecpp0 libpixman-1-dev
  libpthread-stubs0-dev libsigsegv2 libsm-dev libsys-hostname-long-perl libtool libunistring0
  libwayland-bin libwayland-client0 libwayland-cursor0 libwayland-dev libwayland-server0
  libx11-dev libx11-doc libxau-dev libxcb-render0-dev libxcb-shm0-dev libxcb1-dev
  libxcomposite-dev libxcursor-dev libxdamage-dev libxdmcp-dev libxext-dev libxfixes-dev
  libxft-dev libxi-dev libxinerama-dev libxkbcommon-dev libxrandr-dev libxrender-dev libxtst-dev
  m4 po-debconf x11proto-composite-dev x11proto-core-dev x11proto-damage-dev x11proto-fixes-dev
  x11proto-input-dev x11proto-kb-dev x11proto-randr-dev x11proto-record-dev x11proto-render-dev
  x11proto-xext-dev x11proto-xinerama-dev xorg-sgml-doctools xtrans-dev
Suggested packages:
  autoconf-archive gnu-standards autoconf-doc dh-make gettext-doc libcairo2-doc libglib2.0-doc
  libgtk-3-doc libice-doc libtool-doc imagemagick libpango1.0-doc libsm-doc automaken gfortran
  fortran95-compiler gcj-jdk libxcb-doc libxext-doc libmail-box-perl
The following NEW packages will be installed:
  autoconf automake autopoint autotools-dev debhelper dh-autoreconf dh-strip-nondeterminism
  gettext gir1.2-atspi-2.0 intltool-debian libarchive-zip-perl libasprintf-dev
  libatk-bridge2.0-dev libatk1.0-dev libatspi2.0-dev libcairo-script-interpreter2 libcairo2-dev
  libdbus-1-dev libdbus-glib-1-dev libfile-stripnondeterminism-perl libfontconfig1-dev
  libgdk-pixbuf2.0-dev libgettextpo-dev libgettextpo0 libglib2.0-dev libgtk-3-dev
  libharfbuzz-dev libharfbuzz-gobject0 libice-dev libltdl-dev libmail-sendmail-perl
  libpango1.0-dev libpcre3-dev libpcrecpp0 libpixman-1-dev libpthread-stubs0-dev libsigsegv2
  libsm-dev libsys-hostname-long-perl libtool libunistring0 libwayland-bin libwayland-dev
  libx11-dev libx11-doc libxau-dev libxcb-render0-dev libxcb-shm0-dev libxcb1-dev
  libxcomposite-dev libxcursor-dev libxdamage-dev libxdmcp-dev libxext-dev libxfixes-dev
  libxft-dev libxi-dev libxinerama-dev libxkbcommon-dev libxrandr-dev libxrender-dev libxtst-dev
  m4 po-debconf x11proto-composite-dev x11proto-core-dev x11proto-damage-dev x11proto-fixes-dev
  x11proto-input-dev x11proto-kb-dev x11proto-randr-dev x11proto-record-dev x11proto-render-dev
  x11proto-xext-dev x11proto-xinerama-dev xorg-sgml-doctools xtrans-dev
The following packages will be upgraded:
  libcairo-gobject2 libcairo2 libdbus-1-3 libwayland-client0 libwayland-cursor0
  libwayland-server0
6 upgraded, 77 newly installed, 0 to remove and 153 not upgraded.
Need to get 19.9 MB of archives.
After this operation, 68.8 MB of additional disk space will be used.
Do you want to continue? [Y/n] y
Get:1 http://archive.raspberrypi.org/debian/ jessie/main dh-autoreconf all 12~bpo8+1 [16.0 kB]
Get:2 http://archive.raspberrypi.org/debian/ jessie/main debhelper all 10.2.2~bpo8+1 [837 kB]
Get:3 http://mirrordirector.raspbian.org/raspbian/ jessie/main libdbus-1-3 armhf 1.8.22-0+deb8u1 [150 kB]
.................................................
Get:80 http://mirrordirector.raspbian.org/raspbian/ jessie/main x11proto-composite-dev all 1:0.4.2-2 [15.3 kB]
Get:81 http://mirrordirector.raspbian.org/raspbian/ jessie/main libsys-hostname-long-perl all 1.4-3 [11.6 kB]
Get:82 http://mirrordirector.raspbian.org/raspbian/ jessie/main libx11-doc all 2:1.6.2-3 [2,026 kB]
Get:83 http://mirrordirector.raspbian.org/raspbian/ jessie/main libmail-sendmail-perl all 0.79.16-1 [26.6 kB]
Fetched 19.9 MB in 16s (1,193 kB/s)                                                   
Reading changelogs... Done
Extracting templates from packages: 100%
(Reading database ... 125766 files and directories currently installed.)
Preparing to unpack .../libdbus-1-3_1.8.22-0+deb8u1_armhf.deb ...
Unpacking libdbus-1-3:armhf (1.8.22-0+deb8u1) over (1.8.20-0+deb8u1) ...
Selecting previously unselected package libunistring0:armhf.
Preparing to unpack .../libunistring0_0.9.3-5.2_armhf.deb ...
Unpacking libunistring0:armhf (0.9.3-5.2) ...
...............................................
Unpacking libsys-hostname-long-perl (1.4-3) ...
Selecting previously unselected package libmail-sendmail-perl.
Preparing to unpack .../libmail-sendmail-perl_0.79.16-1_all.deb ...
Unpacking libmail-sendmail-perl (0.79.16-1) ...
Selecting previously unselected package libx11-doc.
Preparing to unpack .../libx11-doc_2%3a1.6.2-3_all.deb ...
Unpacking libx11-doc (2:1.6.2-3) ...
Processing triggers for install-info (5.2.0.dfsg.1-6) ...
Processing triggers for man-db (2.7.0.2-5) ...
Processing triggers for libglib2.0-0:armhf (2.42.1-1) ...
Processing triggers for libc-bin (2.19-18+deb8u6) ...
Setting up libdbus-1-3:armhf (1.8.22-0+deb8u1) ...
Setting up libunistring0:armhf (0.9.3-5.2) ...
Setting up libgettextpo0:armhf (0.19.3-2) ...
Setting up libharfbuzz-gobject0:armhf (0.9.35-2) ...
............................................
Setting up libx11-doc (2:1.6.2-3) ...
Setting up dh-autoreconf (12~bpo8+1) ...
Setting up debhelper (10.2.2~bpo8+1) ...
Setting up dh-strip-nondeterminism (0.003-1) ...
Processing triggers for libc-bin (2.19-18+deb8u6) ...
pi@raspberrypi:~ $ 

Thanks to Alex Eames (Mr raspi.tv) for installing GTK+ 3 on his PI as a test run.

The source files for all the examples are contained in a tar ball on my website so there is no need to "cut and paste" code from this web page. If you know how to use CMake it also contains a CMakeLists.txt that will build all the examples.
If you don't know how to extract a tarball look here.

A note on compiling and running the examples

Compiling GTK+ code requires many options to be passed to the C compiler. These are mostly used to specify the directories where the GTK+ header files are located (e.g. -I/usr/include/gtk-3.0) and which libraries to link (e.g. -lgtk-3).

Here is the full compile command for the first example:

gcc -Wall -Wextra gcc -o Ex1 Ex1.c -pthread -I/usr/include/gtk-3.0 -I/usr/include/atk-1.0 \
-I/usr/include/at-spi2-atk/2.0 -I/usr/include/pango-1.0 -I/usr/include/gio-unix-2.0/ -I/usr/include/cairo \
-I/usr/include/gdk-pixbuf-2.0 -I/usr/include/glib-2.0 -I/usr/lib/x86_64-linux-gnu/glib-2.0/include \
-I/usr/include/harfbuzz -I/usr/include/freetype2 -I/usr/include/pixman-1 -I/usr/include/libpng12  \
-lgtk-3 -lgdk-3 -latk-1.0 -lgio-2.0 -lpangocairo-1.0 -lgdk_pixbuf-2.0 -lcairo-gobject \
-lpango-1.0 -lcairo -lgobject-2.0 -lglib-2.0

Creating these commands is greatly simplified by using the pkg-config utility. pkg-config gtk+-3.0 --cflags --libs outputs all the required options to stdout, so command substitution can be used to place them into a command line for the compiler.

gcc -Wall -Wextra o Ex1 Ex1.c $(pkg-config gtk+-3.0 --cflags --libs)

Note the use of the modern "$()" syntax for command substitution. For and explanation of "command substitution" see The Advanced Bash-Scripting Guide:

Note also the use of "-Wall" and "-Wextra" switches which cause the compiler to be more "fussy" about the code it is compiling and to produce more warning messages about your code !

If you have Cmake istalled on your machine you can use the included CMakeLists.txt to create a makefile.

~/GTK3 $ cmake .
-- The C compiler identification is GNU 4.8.4
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/petero/GTK3
~/GTK3 $ make
Scanning dependencies of target Ex1
[ 16%] Building C object CMakeFiles/Ex1.dir/Ex1.c.o
Linking C executable Ex1
[ 16%] Built target Ex1
Scanning dependencies of target Ex2
[ 33%] Building C object CMakeFiles/Ex2.dir/Ex2.c.o
Linking C executable Ex2
[ 33%] Built target Ex2
Scanning dependencies of target Ex3
[ 50%] Building C object CMakeFiles/Ex3.dir/Ex3.c.o
Linking C executable Ex3
[ 50%] Built target Ex3
Scanning dependencies of target Ex4
[ 66%] Building C object CMakeFiles/Ex4.dir/Ex4.c.o
Linking C executable Ex4
[ 66%] Built target Ex4
Scanning dependencies of target Ex5
[ 83%] Building C object CMakeFiles/Ex5.dir/Ex5.c.o
Linking C executable Ex5
[ 83%] Built target Ex5
Scanning dependencies of target Ex6
[100%] Building C object CMakeFiles/Ex6.dir/Ex6.c.o
Linking C executable Ex6
[100%] Built target Ex6

If you don't have Cmake installed, it can be installed like this:


pi@raspberrypi:~ $ sudo apt-get install cmake
Reading package lists... Done
Building dependency tree       
Reading state information... Done
The following packages were automatically installed and are no longer required:
  fonts-lyx libjs-jquery-ui python-dateutil python-imaging python-matplotlib-data python-mock
  python-pyparsing python-tz
Use 'apt-get autoremove' to remove them.
The following extra packages will be installed:
  cmake-data libjsoncpp0
Suggested packages:
  codeblocks eclipse ninja-build
The following NEW packages will be installed:
  cmake cmake-data libjsoncpp0
0 upgraded, 3 newly installed, 0 to remove and 159 not upgraded.
Need to get 3,608 kB of archives.
After this operation, 18.5 MB of additional disk space will be used.
Do you want to continue? [Y/n] y
Get:1 http://archive.raspberrypi.org/debian/ jessie/main cmake-data all 3.6.2-2~bpo8+1 [1,168 kB]
Get:2 http://mirrordirector.raspbian.org/raspbian/ jessie/main libjsoncpp0 armhf 0.6.0~rc2-3.1 [61.1 kB]
Get:3 http://archive.raspberrypi.org/debian/ jessie/main cmake armhf 3.6.2-2~bpo8+1 [2,379 kB]
Fetched 3,608 kB in 1s (2,061 kB/s)
Selecting previously unselected package cmake-data.
(Reading database ... 123691 files and directories currently installed.)
Preparing to unpack .../cmake-data_3.6.2-2~bpo8+1_all.deb ...
Unpacking cmake-data (3.6.2-2~bpo8+1) ...
Selecting previously unselected package libjsoncpp0.
Preparing to unpack .../libjsoncpp0_0.6.0~rc2-3.1_armhf.deb ...
Unpacking libjsoncpp0 (0.6.0~rc2-3.1) ...
Selecting previously unselected package cmake.
Preparing to unpack .../cmake_3.6.2-2~bpo8+1_armhf.deb ...
Unpacking cmake (3.6.2-2~bpo8+1) ...
Processing triggers for man-db (2.7.0.2-5) ...
Setting up cmake-data (3.6.2-2~bpo8+1) ...
Setting up libjsoncpp0 (0.6.0~rc2-3.1) ...
Setting up cmake (3.6.2-2~bpo8+1) ...
Processing triggers for libc-bin (2.19-18+deb8u6) ...
pi@raspberrypi:~ $ 

Note that you will not get exactly the same output as shown above. All that is important is that lines in bold are present.

Once compiled the examples are run by simply typing their name preceded by "./" which tells the shell to look for the named executable file in the current working directory.

Programming GTK+ 3 GUIs without using Glade

In order to best demonstrate how using Glade and GtkBuilders simplifies application development, first we are going to look at producing a very simple GUI without using them. This means our application code needs to explicitly create the various widgets, and to place them within each other to produce the desired layout. It will also serve as a good introduction to some of the basic concepts of Gtk+ 3.

GTK objects

Although GTK+ is written in C (Not C++) it uses an object oriented framework provided by the glib GObject library.

All widget types in GTK+ are ultimately sub classed from the GObject class. So for example the type hierarchy of the various button widget types is shown below.

    GObject
    ╰── GInitiallyUnowned
        ╰── GtkWidget
            ╰── GtkContainer
                ╰── GtkBin
                    ╰── GtkButton
                        ├── GtkToggleButton
                        ├── GtkColorButton
                        ├── GtkFontButton
                        ├── GtkLinkButton
                        ├── GtkLockButton
                        ╰── GtkScaleButton

Windows and Widgets

Wikipedia defines a Widget as "A control element (sometimes called a control or widget) in a graphical user interface is an element of interaction, such as a button or a scroll bar. " In GTK+ the GtkWidget class is the staring point for all of the available controls.

A widget that can hold other widgets is called a "container" and is of type GtkContainer. The most obvious example of a container is an application's window. If a GtkWindow is not contained in any other GtkWidget it is called a "top level window". Top level windows interact with the desktop window manager which controls where the window is placed on the desk top and then adds window decorations such as a "Title Bar" which typically contains buttons to hide,maximise and close the window. A window with no child widgets is just a blank rectangle (as shown below).

An Empty Top Level Window
Ex1 :An Empty GtkWindow with Window Manager decorations.

Here is the code to produce such an empty window.

01: // gcc -Wall -Wextra -o Ex1 Ex1.c $(pkg-config gtk+-3.0 --cflags --libs)
02: #include <gtk/gtk.h>
03: 
04: int main(int argc,char **argv) 
05: {
06:     GtkWidget *window;
07: 
08:     gtk_init(&argc , &argv);
09:     window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
10:     gtk_widget_show_all(window);
11:     gtk_main();
12:     return 0;
13: }
14: 

If you have run cmake in the examples directory you can build this code by executing make Ex1 otherwise use the command below.

gcc -Wall -Wextra -o Ex1 Ex1.c $(pkg-config gtk+-3.0 --cflags --libs)

When you run this example there are two things to note:

  1. The window has a default size, and can be resized using the mouse.
  2. Closing the window with the "Close Window" button or with the keyboard short cut (Alt+F4) does not terminate the application (i.e. the application has to be killed by pressing Ctrl-C)

If we put a single widget into a window, then the window resizes to fit around the contained widget.


Ex2: A Window containing a single button.

Here is the code for Ex2

01: // gcc  -Wall -Wextra -o Ex2 Ex2.c $(pkg-config gtk+-3.0 --cflags --libs)
02: #include <gtk/gtk.h>
03: 
04: int main ( int argc, char **argv) {
05:     GtkWidget *window;
06:     GtkWidget *quitButton;
07:     gtk_init (&argc , &argv);
08: 
09:     window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
10:     
11:     quitButton = gtk_button_new_with_label("QUIT");
12:     gtk_container_add(GTK_CONTAINER (window), quitButton);
13: 
14:     gtk_widget_show_all (window);
15:     gtk_main ();
16:     return 0;
17: }
18: 

Line 12 deserves a bit more explanation. The first parameter to gtk_container_add has to be a pointer to a GtkContainer, but the window variable is a pointer to a GtkWidget so it cannot be used directly. Without some form of casting, the following compiler error is produced:

Ex2.c: In function ‘main’:
Ex2.c:14:5: warning: passing argument 1 of ‘gtk_container_add’ from incompatible pointer type [enabled by default]
     gtk_container_add(window, quitButton);
     ^
In file included from /usr/include/gtk-3.0/gtk/gtkbin.h:33:0,
                 from /usr/include/gtk-3.0/gtk/gtkwindow.h:35,
                 from /usr/include/gtk-3.0/gtk/gtkdialog.h:33,
                 from /usr/include/gtk-3.0/gtk/gtkaboutdialog.h:30,
                 from /usr/include/gtk-3.0/gtk/gtk.h:31,
                 from Ex2.c:2:
/usr/include/gtk-3.0/gtk/gtkcontainer.h:116:9: note: expected ‘struct GtkContainer *’ but argument is of type ‘struct GtkWidget *’
 void    gtk_container_add   (GtkContainer    *container,

While it is possible to use a traditional C cast like this

gtk_container_add((GtkContainer *) window, quitButton);
it is better to use GTK's type conversion macros that preform additional compile time checking. For example
GTK_CONTAINER(window)
checks that the window variable is a pointer to a type that is a subclass of GtkContainer and therefore can be used as the first parameter to gtk_container_add.

When you run this example there are three things to note:

  1. Clicking the "QUIT" button does not close the application. A little more work is need to make that happen.
  2. The window defaults to the size of the button, and when it is resized the button grows and shrinks to fill the window. This is probably not the behaviour you want !
  3. As before, closing the window with the "Close Window" button or with the keyboard short cut (Alt+F4) does not terminate the application (i.e. the application has to be killed by pressing Ctrl-C)


The button grows to fill an expanded window.

Signals and Handlers

As noted above, creating a button called "quitButton" and containing the word "QUIT" does not by itself mean that clicking the button will cause the application to quit !

GTK uses "signals" to link on screen user actions such as clicking a button with pieces of code (called callbacks or signal handlers) in our application. GtkButtons can emit several signals, but "clicked" is the one most commonly used. Ex3.c below includes a signal handler to be executed when the quit button is clicked, and code to link the clicking of the button to the signal handler.

01: // gcc  -Wall -Wextra -o Ex3 Ex3.c $(pkg-config gtk+-3.0 --cflags --libs)
02: #include <gtk/gtk.h>
03: 
04: // Handle user clicking the QUIT button
05: static void quitButtonClicked(__attribute__((unused)) GtkWidget *widget, 
06:                               __attribute__((unused)) gpointer   data)
07: {
08:     g_print("%s called.\n",__FUNCTION__);
09:     gtk_main_quit();
10: }
11: 
12: // Handle the user trying to close the window 
13: gboolean windowDelete(__attribute__((unused)) GtkWidget *widget, 
14:                       __attribute__((unused)) GdkEvent  *event,
15:                       __attribute__((unused)) gpointer   data)
16: {
17:     g_print("%s called./n",__FUNCTION__);
18:     return TRUE;    // Returning TRUE stops the window being deleted.
19:                     // Returning FALSE allows deletion.   
20: }
21: 
22: 
23: 
24: int main ( int argc, char **argv) {
25:     GtkWidget *window;
26:     GtkWidget *quitButton;
27:     GtkWidget *testButton;
28: 
29:     gtk_init(&argc , &argv);
30: 
31:     window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
32:     
33:     quitButton = gtk_button_new_with_label("QUIT");
34:     gtk_container_add(GTK_CONTAINER(window), quitButton);
35: 
36:     testButton = gtk_button_new_with_label("TEST");
37:     gtk_container_add(GTK_CONTAINER(window), testButton);
38: 
39: 
40:     g_signal_connect(quitButton, "clicked", G_CALLBACK(quitButtonClicked), NULL);
41:     g_signal_connect(window, "delete_event", G_CALLBACK(windowDelete), NULL);
42: 
43:     gtk_widget_show_all(window);
44:     gtk_main();
45:     return 0;
46: }
47: 

When you run this example there are two things to note:

  1. Clicking the "QUIT" now closes the window and terminates the application.
  2. Trying to close the window with the "Close Window" button or with the keyboard short cut (Alt+F4) no longer closes the window.

What's all this "__attribute__((unused))" stuff about ?

As mentioned earlier I normally run the C compiler with additional warnings enabled (-Wall and -Wextra). One consequence of this is the production of warning messages when a function is defined with a parameter that is not actually used in the body of the function. For example without the including __attribute__((unused)) in the declaration of quitButtonClicked the following warnings are produced.

home/petero/GTK3/Ex6.c: In function ‘quitButtonClicked’:
/home/petero/GTK3/Ex6.c:70:35: warning: unused parameter ‘widget’ [-Wunused-parameter]
 void quitButtonClicked(GtkWidget *widget, 
                                   ^
/home/petero/GTK3/Ex6.c:71:21: warning: unused parameter ‘data’ [-Wunused-parameter]
          gpointer   data)
                     ^

"__attribute__((unused))" tells the compiler "Yes I know I've not used this parameter, but I had to include it to make the function signature correct".

We will see later that signal handlers have to conform to the correct signature if you do want to avoid other warnings and if you do need to use the parameters. I find it best to use the correct signature and suffer the slight overhead of having to type "__attribute__((unused))" where needed.

Containers

The examples so far have used a GtkWindow as a container to hold a single widget, a button. But GtkWindows can only hold one child widget because they are a subclass of GtkBin. If our code tries to add further buttons to the window GTK produces a runtime error message :

(Ex3:11865): Gtk-WARNING **: Attempting to add a widget with type GtkButton to a GtkWindow, but as a GtkBin subclass a GtkWindow can only contain one widget at a time; it already contains a widget of type GtkButton

The correct way to add extra widgets is to put a more flexible container into the window, and place the rest of your applications widgets into this second container. One of the simplest containers is the GtkBox. A GtkBox places all of its child widgets into a single row or column.

01: // gcc  -Wall -Wextra -o Ex4 Ex4.c $(pkg-config gtk+-3.0 --cflags --libs)
02: #include <gtk/gtk.h>
03: 
04: // Handle user clicking the QUIT button
05: static void quitButtonClicked(__attribute__((unused)) GtkWidget *widget, 
06:                               __attribute__((unused)) gpointer   data)
07: {
08:     g_print("%s called.\n",__FUNCTION__);
09:     gtk_main_quit();
10: }
11: 
12: // Handle the user trying to close the window 
13: gboolean windowDelete(__attribute__((unused)) GtkWidget *widget, 
14:                       __attribute__((unused)) GdkEvent  *event,
15:                       __attribute__((unused)) gpointer   data)
16: {
17:     g_print("%s called.\n",__FUNCTION__);
18:     return TRUE;    // Returning TRUE stops the window being deleted.
19:                     // Returning FALSE allows deletion.   
20: }
21: 
22: int main ( int argc, char **argv) {
23:     GtkWidget *window;
24:     GtkWidget *buttonBox;
25:     GtkWidget *quitButton;
26:     
27: 
28:     gtk_init(&argc , &argv);
29: 
30:     window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
31:     
32:     buttonBox = gtk_button_box_new(GTK_ORIENTATION_VERTICAL);
33:     gtk_container_add(GTK_CONTAINER(window), buttonBox);
34: 
35:     quitButton = gtk_button_new_with_label("QUIT");
36:     gtk_container_add(GTK_CONTAINER(buttonBox), quitButton);
37: 
38:     g_signal_connect(quitButton, "clicked", G_CALLBACK(quitButtonClicked), NULL);
39:     g_signal_connect(window, "delete_event", G_CALLBACK(windowDelete), NULL);
40: 
41:     gtk_widget_show_all(window);
42:     gtk_main();
43:     return 0;
44: }
45: 

When you run this example there is one thing to note:

  1. When the window is expanded, the GtkBox expands to fill the space in the window, but the button's size is unchanged. The GtkBox also keeps the button positioned at its centre


A GtkBox expands to fill the window.

Of course only placing one widget in the GtkBox doesn't demonstrate its packing strategy, so the next example inserts five widgets into the container. It also introduces the GtkEntry widget and time-outs.

001: // gcc  -Wall -Wextra -o Ex5 Ex5.c $(pkg-config gtk+-3.0 --cflags --libs)
002: #include <gtk/gtk.h>
003: 
004: int seconds = 0;
005: guint timerId = 0;
006: GtkWidget *timer;
007: 
008: gboolean timerTick(__attribute__((unused)) gpointer userData)
009: {
010:     static GString *time = NULL;
011: 
012:     if(time == NULL)
013:     {
014:         time = g_string_sized_new(10);
015:     }
016: 
017:     seconds += 1;
018:     printf("Timer Tick %d \n",seconds);
019:    
020:     g_string_printf(time,"%02d:%02d",seconds/60,seconds%60);
021:     gtk_entry_set_text (GTK_ENTRY(timer),time->str);
022:     
023:     return TRUE;
024: }
025: 
026: void stopButtonClicked(__attribute__((unused)) GtkWidget *widget, 
027:                        __attribute__((unused)) gpointer   data)
028: {
029:     g_print("Stop Clicked\n");
030:     if(timerId != 0)
031:     {
032:         g_source_remove(timerId);
033:         timerId = 0;
034:     }
035:     else
036:         g_print("Not Running\n");
037: }
038: 
039: void startButtonClicked(__attribute__((unused)) GtkWidget *widget, 
040:                         __attribute__((unused)) gpointer   data)
041: {
042:     g_print("Start Clicked\n");
043:     if(timerId == 0)
044:         timerId = g_timeout_add(1000,timerTick,NULL);
045:     else
046:         g_print("Already Running\n");
047: }
048: 
049: void clearButtonClicked(__attribute__((unused)) GtkWidget *widget, 
050:                         __attribute__((unused)) gpointer   data)
051: {
052:     g_print("Clear Clicked\n");
053:     if(timerId == 0)
054:     {
055:         seconds = 0;
056:         gtk_entry_set_text (GTK_ENTRY(timer),"00:00");
057:     }
058:     else
059:         g_print("Ignored, Running\n");
060: }
061: 
062: 
063: void quitButtonClicked(__attribute__((unused)) GtkWidget *widget, 
064:                        __attribute__((unused)) gpointer   data)
065: {
066:     g_print("Quit Clicked\n");
067:     gtk_main_quit();
068: }
069: 
070: 
071: // Handle the user trying to close the window 
072: gboolean windowDelete(__attribute__((unused)) GtkWidget *widget, 
073:                       __attribute__((unused)) GdkEvent  *event,
074:                       __attribute__((unused)) gpointer   data)
075: {
076:     g_print("%s called.\n",__FUNCTION__);
077:     return TRUE;    // Returning TRUE stops the window being deleted.
078:                     // Returning FALSE allows deletion.   
079: }
080: 
081: int main ( int argc, char **argv) {
082:     GtkWidget *window;
083:     GtkWidget *box;
084:     
085:     GtkWidget *startButton;
086:     GtkWidget *stopButton;
087:     GtkWidget *clearButton;
088:     GtkWidget *quitButton;
089: 
090: 
091:     gtk_init(&argc , &argv);
092: 
093:     window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
094:     
095:     box = gtk_button_box_new(GTK_ORIENTATION_VERTICAL);
096:     gtk_container_add(GTK_CONTAINER (window), box);
097: 
098:     timer = gtk_entry_new();
099:     gtk_entry_set_text(GTK_ENTRY(timer),"00:00");
100: 
101:     gtk_container_add(GTK_CONTAINER (box), timer);
102: 
103: 
104:     startButton = gtk_button_new_with_label("START");
105:     gtk_container_add(GTK_CONTAINER (box), startButton);
106:  
107:     g_signal_connect(startButton, "clicked", G_CALLBACK (startButtonClicked), NULL);
108: 
109:     stopButton = gtk_button_new_with_label("STOP");
110:     gtk_container_add(GTK_CONTAINER (box), stopButton);
111: 
112:     g_signal_connect(stopButton, "clicked", G_CALLBACK (stopButtonClicked), NULL);
113: 
114:     clearButton = gtk_button_new_with_label("CLEAR");
115:     gtk_container_add(GTK_CONTAINER (box), clearButton);
116: 
117:     g_signal_connect(clearButton, "clicked", G_CALLBACK (clearButtonClicked), NULL);
118: 
119:     quitButton = gtk_button_new_with_label("QUIT");
120:     gtk_container_add(GTK_CONTAINER (box), quitButton);
121: 
122:     g_signal_connect(quitButton, "clicked", G_CALLBACK (quitButtonClicked), NULL);
123:     g_signal_connect(window, "delete_event", G_CALLBACK(windowDelete), NULL);
124: 
125:     gtk_widget_show_all (window);
126:     gtk_main ();
127:     return 0;
128: }
129: 

Ex5.c and Ex6.c only differ in one respect, Ex5.c uses a GtkButtonBox container where as Ex6.c uses a GtkBox container. The pictures below show how these two containers have different behaviour with respect to the sizing of their child widgets, and also how they position the widgets differently if the top level window is enlarged. We will see later how these differences in behaviour can be controlled when using GtkBuilder and Glade to create the widgets and layout.


A simple Glade example

According to the Glade web site "Glade is a RAD tool to enable quick & easy development of user interfaces for the GTK+ toolkit and the GNOME desktop environment."

Getting started with the Glade GUI

On Raspbian, glade can be installed with
sudo apt-get install glade
On other platforms (such as Mint) you may need to make sure you are installing a version for GTK+3 not GTK+2

There is a menu item for glade

When Glade starts you should see a large window that looks like this (but without the coloured backgrounds which I added to aid with describing the various windows areas)

The Glade User Interface

To get started follow these steps to create the same GUI as Ex4.c above

  1. In the widget pallet, under Toplevels, find "Window"
  2. Drag and Drop a Window into the layout area. As well as a window filled with a grey cross-hatch pattern appearing in the layout area, note that an entry for "window1" also appears in the widget inspector and the widget properties are also populated with information about window1
  3. In the widget pallet, under Containers, find "Box"
  4. Drag and Drop a Box into the empty window in the layout area.
  5. In the "Create a GtkBox" pop-up reduce the number of items to one and then press "Create"
  6. Nothing obvious happens in the layout area but the window is now filled with an empty Box container. In the widget inspector box1 now appears as a child of window1 and the widget properties change to reflect box1
  7. In the widget pallet, under Control and Display, find "Button"
  8. Drag and Drop a Button into the empty window in the layout area.
  9. In the layout area the button appears at the top of the Box, in the widget inspector button1 appears as a child of box1 and the widget properties change to reflect button1.
  10. In the widget inspector try selecting each of the widgets and note how the layout area view changes and how different widget types have different property types.
  11. In the widget inspector select window1 then in the widget properties select "Signals".
  12. Expand "GtkWidget" , scroll down and select the "delete-event" row, Click on "Type Here" on the selected row and type "windowDelete" into the text box and press Return.
  13. In the widget inspector select button1 then in the widget properties select "Signals" if it is not already selected.
  14. Under "GtkButton" select the "clicked" row, Click on "Type Here" on the selected row and type "quitButtonClicked into the text box and press Return.
  15. In the menu bar select "FIle" and "Save". With the file selector navigate to the GtkExamples directory and save the file as "Ex7.glade"
01: // gcc  -Wall -Wextra -o Ex7 Ex7.c $(pkg-config gtk+-3.0 --cflags --libs)
02: #include <gtk/gtk.h>
03: 
04: 
05: 
06: void quitButtonClicked(__attribute__((unused)) GtkWidget *widget, 
07:                        __attribute__((unused)) gpointer   data)
08: {
09:     g_print("Quit Clicked\n");
10:     gtk_main_quit();
11: }
12: 
13: 
14: // Handle the user trying to close the window 
15: gboolean windowDelete(__attribute__((unused)) GtkWidget *widget, 
16:                       __attribute__((unused)) GdkEvent  *event,
17:                       __attribute__((unused)) gpointer   data)
18: {
19:     g_print("%s called.\n",__FUNCTION__);
20:     return TRUE;    // Returning TRUE stops the window being deleted.
21:                     // Returning FALSE allows deletion.   
22: }
23: 
24: int main ( int argc, char **argv) {
25: 
26:     GtkWidget *window;
27:     GtkBuilder *builder = NULL;
28: 
29:     gtk_init (&argc , &argv);
30: 
31:     builder = gtk_builder_new();
32: 
33:     if( gtk_builder_add_from_file (builder,"Ex7.glade" , NULL) == 0)
34:     {
35:         printf("gtk_builder_add_from_file FAILED\n");
36:         return(0);
37:     }
38: 
39:     window  = GTK_WIDGET (gtk_builder_get_object (builder,"window1"));
40:   
41: 
42:     gtk_builder_connect_signals(builder,NULL);
43: 
44:     gtk_widget_show_all (window);
45:     gtk_main ();
46:     return 0;
47: }
48: 

While the code for Ex7.c is not much shorter than the code fpr Ex4.c it is clear that for a more complex interface the savings can be significant.

The use of "gtk_builder_connect_signals()" is worth some further explanation.

01: <?xml version="1.0" encoding="UTF-8"?>
02: <!-- Generated with glade 3.18.3 -->
03: <interface>
04:   <requires lib="gtk+" version="3.10"/>
05:   <object class="GtkWindow" id="window1">
06:     <property name="can_focus">False</property>
07:     <signal name="delete-event" handler="windowDelete" swapped="no"/>
08:     <child>
09:       <object class="GtkBox" id="box1">
10:         <property name="visible">True</property>
11:         <property name="can_focus">False</property>
12:         <property name="orientation">vertical</property>
13:         <child>
14:           <object class="GtkButton" id="button1">
15:             <property name="label" translatable="yes">button</property>
16:             <property name="visible">True</property>
17:             <property name="can_focus">True</property>
18:             <property name="receives_default">True</property>
19:             <signal name="clicked" handler="quitButtonClicked" swapped="no"/>
20:           </object>
21:           <packing>
22:             <property name="expand">False</property>
23:             <property name="fill">True</property>
24:             <property name="position">0</property>
25:           </packing>
26:         </child>
27:       </object>
28:     </child>
29:   </object>
30: </interface>
31: 

Lines 14 to 20 describe the button in the interface. Line 19 describes the link between the "clicked" signal and the function "quitButtonClicked" in the C code. But note that only the name (as a string) of the function is specified. It is the job of gtk_builder_connect_signals() to resolve this function name into a pointer to the actual code that implements quitButtonClicked at run time. Normally the linker that created "Ex7" (the executable) does not include information about quitButtonClicked in the file because it appears to be unused, at least as far as the code in Ex7.c is concerned. The linker does not know that quitButtonClicked will be referenced in Ex7.glade. In order to force the linker to include the necessary information about quitButtonClicked an extra option "-rdynamic" is added to the compiler command line. Without -rdynamic the following happens when Ex7 is executed.


~/GTK3 $ gcc  -Wall -Wextra -o Ex7 Ex7.c $(pkg-config gtk+-3.0 --cflags --libs)
~/GTK3 $ ./Ex7

(Ex7:5292): Gtk-WARNING **: Could not find signal handler 'windowDelete'.  Did you compile with -rdynamic?

(Ex7:5292): Gtk-WARNING **: Could not find signal handler 'quitButtonClicked'.  Did you compile with -rdynamic?
^C
~/GTK3 $

Adding -rdynamic (as suggested by the warning message) fixes the problem.

~/GTK3 $ gcc  -Wall -Wextra -o Ex7 Ex7.c $(pkg-config gtk+-3.0 --cflags --libs) -rdynamic 
~/GTK3 $ ./Ex7
Quit Clicked
~/GTK3 $ 

A more complex Glade example

Follow the steps below to produce a glade for Ex6

  1. As before create a top level window containing a GtKBox, but this time set the number of child items to five. You will see the Box is divided into five equal horizontal strips.
  2. Select "window1" in the widget inspector an in the widget properties select "General" and change the ID from "window1" to just "window".
  3. In the widget pallet, under Control and Display, find "TextEntry".
  4. Drag and Drop a TextEntry into the top strip of the Box. Note that the top strip shrinks to surround the TextEntry widget.
  5. In the widget properties (which should be showing entry1) select "General" and change the ID from "entry1" to "timer"
  6. Change the "Text" to "00:00" (You may have to scroll down the properties panel to see this item).
  7. Place a Button (from Control and Display in the widget pallet) into each of the remaining empty strips in the Box. Note that they get the default IDs "button1" .. "button4"
  8. From top to bottom, change the button IDs to "startButton", "stopButton", "clearButton" and "quitButton"
  9. From top to bottom, change the "Button Content" to "START", "STOP", "CLEAR" and "QUIT"
  10. Change the "clicked" signal handlers to "startButtonClicked","stopButtonClicked","clearButtonClicked" and "quitButtonClicked"
  11. In the menu bar select "File" and "Save". With the file selector navigate to the GtkExamples directory and save the file as "Ex8.glade"

Here are the main() functions from Ex8 and Ex6 for comparison. I've improved Ex8 by adding a better explanation message to the user when gtk_builder_add_from_file() fails. Also I've used EXIT_SUCCESS and EXIT_FAILURE from stdlib.h for better values than 1 and 0 to return from main.

Ex8 Main Function Ex6 Main Function
01: int main ( int argc, char **argv) {
02: 
03:     GtkWidget *window;
04:     GtkBuilder *builder = NULL;
05:     GError *error = NULL;
06: 
07:     gtk_init (&argc , &argv);
08: 
09:     builder = gtk_builder_new();
10: 
11:     if( gtk_builder_add_from_file (builder,"Ex88.glade" , &error) == 0)
12:     {
13:         g_print("gtk_builder_add_from_file FAILED\n");
14:         g_print("%s\n",error->message);
15:         return EXIT_FAILURE;
16:     }
17: 
18:     window  = GTK_WIDGET (gtk_builder_get_object (builder,"window"));
19:     timer  = GTK_WIDGET (gtk_builder_get_object (builder,"timer"));
20:     
21:     gtk_builder_connect_signals(builder,NULL);
22: 
23:     gtk_widget_show_all (window);
24:     gtk_main ();
25:     return EXIT_SUCCESS;
26: }
01: int main ( int argc, char **argv) {
02:     GtkWidget *window;
03:     GtkWidget *box;
04:     
05:     GtkWidget *startButton;
06:     GtkWidget *stopButton;
07:     GtkWidget *clearButton;
08:     GtkWidget *quitButton;
09: 
10: 
11:     gtk_init (&argc , &argv);
12: 
13:     window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
14:     
15:     box = gtk_box_new(GTK_ORIENTATION_VERTICAL,0);
16:     gtk_container_add(GTK_CONTAINER (window), box);
17: 
18:     timer = gtk_entry_new();
19:     gtk_editable_set_editable(GTK_EDITABLE(timer),FALSE);
20:     gtk_entry_set_text(GTK_ENTRY(timer),"00:00");
21: 
22:     gtk_container_add(GTK_CONTAINER (box), timer);
23: 
24: 
25:     startButton = gtk_button_new_with_label("START");
26:     gtk_container_add(GTK_CONTAINER (box), startButton);
27:  
28:     g_signal_connect(startButton, "clicked", G_CALLBACK (startButtonClicked), NULL);
29: 
30:     stopButton = gtk_button_new_with_label("STOP");
31:     gtk_container_add(GTK_CONTAINER (box), stopButton);
32: 
33:     g_signal_connect(stopButton, "clicked", G_CALLBACK (stopButtonClicked), NULL);
34: 
35:     clearButton = gtk_button_new_with_label("CLEAR");
36:     gtk_container_add(GTK_CONTAINER (box), clearButton);
37: 
38:     g_signal_connect(clearButton, "clicked", G_CALLBACK (clearButtonClicked), NULL);
39: 
40:     quitButton = gtk_button_new_with_label("QUIT");
41:     gtk_container_add(GTK_CONTAINER (box), quitButton);
42: 
43:     g_signal_connect(quitButton, "clicked", G_CALLBACK (quitButtonClicked), NULL);
44:     g_signal_connect(window, "delete_event", G_CALLBACK(windowDelete), NULL);
45: 
46:     gtk_widget_show_all(window);
47:     gtk_main ();
48:     return 0;
49: }

The glade gui design tool

Widget Properties

General

Packing

Common

Signals

Top Levels (Windows and Dialogues)

Containers

Buttons

Drawing Areas

How to use a ".glade" file

Creating a GtkBuilder from a .glade file Connecting event handlers Finding widgets by name

Using Cairo to draw in a drawing area

Cairo is a fully featured 2D drawing library which is integrated into GTK+ (as of GTK+2.8). It can be used in conjunction with a GtkDrawingArea to produce dynamic graphics that change depending on variables in the program.

Cairo Example 1 : RedAmberGreen

Use glade to examine the layout defined in RedAmberGreen.glade . It contains a GtkDrawingArea called "drawingarea" at the top of the window. In the widget properties select "Common" and look in the Widget Spacing section. You can see that Width Request and Height Request are enabled and set to produce a 100x300 pixel area. Also look to see that it has a signal handler defined for the "draw" signal.

Also look to see that the Red, Amber and Green buttons have handlers defined for the "toggled" signal and that the user data field is set to "drawingarea". So far all the button signal handlers have had the following signature because neither of the parameters were being used:

1: gint on_quitButton_clicked(__attribute__((unused)) GtkButton *widget,
2:                            __attribute__((unused)) gpointer data)
3: {
4: ....
5: }
6: 

The data parameter is a "gpointer" which is actually defined as typedef void* gpointer; It is used as an untyped pointer, gpointer looks better and is easier to use than void*. With the user data field set to "drawingarea" in the glade file, this pointer will be set to the address of the drawing area when the handlers are called. This removes the need for a global pointer to the drawing area.

These handlers are called when the button changes state from up to down and from down to up.

001: #include <stdio.h>
002: #include <stdlib.h>
003: #include <gtk/gtk.h>
004: 
005: gboolean redOn,amberOn,greenOn;
006: 
007: gboolean on_drawingarea_draw(GtkWidget *widget, cairo_t *cr, __attribute__((unused)) gpointer data)
008: {
009:     guint width, height;
010:     double diameter;
011: 
012:     // Get the size of the drawing area
013:     width = gtk_widget_get_allocated_width (widget);
014:     height = gtk_widget_get_allocated_height (widget);
015:     
016: 
017:     // Define points for drawing the regular trapezium for the white boarder
018:     int borderPoints[][2] = {
019:         {10,10},{0,0},
020:         {width,0},{width-10,10},
021:         {width-10,height-10},{width,height},
022:         {0,height},{10,height-10},
023:         {10,10},{0,0},
024:     };
025:     
026:     // Fill whole area to black
027:     cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 1.0);
028:     cairo_rectangle(cr,0,0,width,height);
029:     cairo_fill(cr);
030: 
031:     // Set fill colour to white
032:     cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 1.0);
033:     cairo_set_line_width(cr,1);
034:     
035:     // Draw the white boarder with four regular trapeziums
036:     for(int start=0; start<=6; start+=2)
037:     {
038:         for(int n=start; n<=start+3; n++)
039:         {
040:             cairo_line_to(cr,borderPoints[n][0],borderPoints[n][1]);
041:         }
042:         
043:         // Draw and fill the trapezium
044:         cairo_close_path(cr);
045:         cairo_stroke_preserve(cr);
046:         cairo_fill(cr);
047:     }
048: 
049:     // Draw Red circle
050:     diameter = (width < height/3) ? 0.35 * width : 0.35 * height / 3.0; 
051:     cairo_set_source_rgba (cr, redOn ? 1.0 : 0.5, 0.0, 0.0, 1.0);
052:     cairo_arc(cr, 0.5*width, 0.17*height, diameter, 0.0 ,2.0 * G_PI);
053:     cairo_fill(cr);
054: 
055:     // Draw Amber circle
056:     cairo_set_source_rgba (cr, amberOn ? 1.0 : 0.5, amberOn ? 1.0 : 0.5, 0.0, 1.0);
057:     cairo_arc(cr, 0.5*width, 0.5*height,diameter, 0.0, 2.0 * G_PI);
058:     cairo_fill(cr);
059: 
060:     // Draw Green circle
061:     cairo_set_source_rgba (cr, 0.0,greenOn ? 1.0 : 0.5, 0.0, 1.0);
062:     cairo_arc(cr, 0.5*width, 0.83*height, diameter, 0.0, 2.0 * G_PI);
063:     cairo_fill(cr);
064:   
065:     return FALSE;
066: }
067: 
068: 
069: gint on_greenButton_toggled(__attribute__((unused)) GtkButton *widget,
070:                             gpointer data)
071: {
072:     printf("%s called\n",__FUNCTION__);
073:     greenOn = !greenOn;
074:     gtk_widget_queue_draw(GTK_WIDGET(data));
075:     return TRUE;
076: }
077: 
078: gint on_amberButton_toggled(__attribute__((unused)) GtkButton *widget,
079:                             gpointer data)
080: {
081:     printf("%s called\n",__FUNCTION__);
082:     amberOn = !amberOn;
083:     gtk_widget_queue_draw(GTK_WIDGET(data));
084:     return TRUE;
085: }
086: 
087: gint on_redButton_toggled(__attribute__((unused)) GtkButton *widget,
088:                           gpointer data)
089: {
090:     printf("%s called\n",__FUNCTION__);
091:     redOn = !redOn;
092:     gtk_widget_queue_draw(GTK_WIDGET(data));
093:     return TRUE;
094: }
095: 
096: gint on_quitButton_clicked(__attribute__((unused)) GtkButton *widget,
097:                            __attribute__((unused)) gpointer data)
098: {
099:     gtk_main_quit();
100:     return TRUE;
101: }
102: 
103: int main(int argc, char **argv)
104: {
105:     GtkBuilder *builder;
106:     GError *error = NULL;
107: 
108:     gtk_init(&argc,&argv);
109: 
110:     builder = gtk_builder_new ();
111:     if( gtk_builder_add_from_file (builder,"RedAmberGreen.glade" ,&error) == 0)
112:     {
113:         printf("gtk_builder_add_from_file FAILED %s\n",error->message);
114:         return EXIT_FAILURE;
115:     }
116:     
117:     gtk_builder_connect_signals (builder, NULL);
118: 
119:     // Turn all the lights off
120:     redOn = amberOn = greenOn = FALSE;
121:     
122:     gtk_main();
123: 
124:     return EXIT_SUCCESS;
125: }
126: 

The "draw" event handler

This function is called to draw whatever is required in the drawing area. Cairo is a large topic by itself so only a brief description of it's use is given here. There are several extensive tutorials available on-line. zetcode has a particulalry good one. The most fundamental concept in cairo is that drawing is performed by transferring pixels from a source onto a surface. When using Cairo with GTK+3 the surface in most often the pixels in a drawing area.

The event handler is passed three parameters:

  1. A pointer to the GtkWidget which generated the signal. In this case the GtkDrawingArea called drawingarea.
  2. A pointer to a cairo context (cairo_t *). Using a cairo context is central to drawing with cairo. The context is automatically set up with the drawing area's pixels as its surface.
  3. A user data value which is unused.

The widget reference is used to find the dimentions of the drawing area (lines 12,13). Don't forget that a widget's size may change between calls to the draw handler if the top level window is resized for example.

cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 1.0); Sets the source to be fully opaque black. Any drawing performed after this will draw black pixels onto the surface.

cairo_rectangle(cr,0,0,width,height); Creates a rectangular path that covers the whole of the drawing area.

cairo_fill(cr); Fills the path with the current source. After this the path is cleared.

The code on lines 34 to 45 draws a white boarder ten pixels wide around the edge of the drawing area. The coordinates of the outer and inner corners of the boarder have already been pit into the boarderPoints array so that the doubly nested loops can draw four regular trapeziums. The calls to cairo_line_to() build paths around three sides of each trapeziums, the fourth side being added by calling cairo_close_path() which joins the first point in the path to the last point in the path.

cairo_stroke_preserve(cr); Draws the path but does not delete the path. Without drawing the boundary lines explicitly the diagonal joins between the trapeziums show up as the fills don't include the path boundaries.

cairo_fill(cr); Fills the current path from the current white source. The adjacent diagram shows how the four trapeziums fit together to draw the boundary.

The colours used to draw the lamps are set by cairo_set_source_rgba() with RGB values adjusted depending on the boolean lamp state variables.

cairo_arc() creates a circular paths sized and positioned depending on the relative width and height of the drawing area. These paths are finally filled to create the coloured disks for each light.


Cairo Example 2 : Using pictures/icons for custom widgets

It is easy to create custom widgets using pre-drawn graphics. In the code below (in Contactor.c) two images are used to represent the "ON" and "OFF" states of an electrical switch.

001: #include <stdlib.h>
002: #include <gtk/gtk.h>
003: 
004: GdkPixbuf *contactorPixbufOn;
005: GdkPixbuf *contactorPixbufOff;
006: 
007: gboolean contactorState = FALSE;
008: 
009: /* Show contactor On or Off depending on  contactorState */
010: gboolean drawingAreaDraw(__attribute__((unused)) GtkWidget *da,
011:                          cairo_t *cr,
012:                          __attribute__((unused)) gpointer data)
013: {
014:     GdkPixbuf *pixbufToDraw;
015:     
016:     pixbufToDraw = contactorState ? contactorPixbufOn : contactorPixbufOff;
017:     
018:     // Set the pixbuf as the Cairo source 
019:     gdk_cairo_set_source_pixbuf(cr, pixbufToDraw, 0, 0);
020:     cairo_paint(cr);
021: 
022:     return FALSE;
023: }
024: 
025: /* Handle and ignore the window close button */
026: gboolean window_delete(__attribute__((unused)) GtkWidget *window)
027: {
028:     return TRUE;  // Don't close
029: }
030: 
031: 
032: 
033: gboolean contactor_press(GtkWidget *drawingArea,
034:                                 __attribute__((unused)) GdkEventButton *event,
035:                                 __attribute__((unused)) gpointer data)
036: {
037:     // Toggle the state
038:     contactorState = ! contactorState;
039:     
040:     // Refresh the dispaly
041:     gtk_widget_queue_draw(drawingArea);
042: 
043:     // Do things depending on/off state, in this simple case quit when turned to OFF.
044:     if(contactorState == FALSE)  gtk_main_quit();;
045:     
046:     return TRUE;
047: }
048: 
049: 
050: int main(int argc, char **argv)
051: {
052:     GtkWidget *window;
053:     GError *error = NULL;
054:     GtkWidget *drawingArea;
055:     int width,height;
056: 
057:     
058:     gtk_init(&argc , &argv);
059: 
060: 
061:     // Load the two images into GdkPixbuf structures
062:     if((contactorPixbufOn = gdk_pixbuf_new_from_file("contactorON.xpm",&error)) == NULL)
063:     {
064:         g_print("Failed to load image from contactorON.xpm \n");
065:         g_print("%s\n",error->message);
066:         return EXIT_FAILURE;
067:     }
068:     if((contactorPixbufOff = gdk_pixbuf_new_from_file("contactorOFF.xpm",&error)) == NULL)
069:     {
070:         g_print("Failed to load image from contactorOFF.xpm \n");
071:         g_print("%s\n",error->message);
072:         return EXIT_FAILURE;
073: 
074:     }
075: 
076:     // Get image dimentions for use in setting the window size.
077:     width = gdk_pixbuf_get_width(contactorPixbufOn);
078:     height = gdk_pixbuf_get_height(contactorPixbufOn);
079: 
080:     // Build a simple GUI 
081:     window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
082: 
083:     g_signal_connect(window,"delete-event",(GCallback) window_delete,NULL);
084:     gtk_widget_set_size_request (window,width,height);
085:     
086:     // Use a drawing area to dispay the images.
087:     drawingArea = gtk_drawing_area_new();
088:     gtk_container_add (GTK_CONTAINER (window), drawingArea);
089:     g_signal_connect (drawingArea, "draw", (GCallback) drawingAreaDraw, NULL);
090: 
091:     // Add a button press signal handler.
092:     g_signal_connect(drawingArea, "button_press_event",(GCallback) contactor_press,NULL);
093: 
094:     gtk_widget_add_events(drawingArea, gtk_widget_get_events(drawingArea)
095:                        | GDK_BUTTON_PRESS_MASK);
096:     
097:     gtk_widget_show_all (window);
098: 
099:     // Since the drawing area is showing a fixed sized image, it make sense to 
100:     // provent the window from being resized.
101:     gtk_window_set_resizable(GTK_WINDOW(window),FALSE);
102: 
103:     gtk_main ();
104:     return EXIT_SUCCESS;
105: }
106: 
107: 
108: 
109: 

The drawing area is updated by setting the appropriate pixbuf as the Cairo source and simply painting the source into the destination (which is the drawing area). The two "states" of the widget are shown below.

Cairo Example 3 : Computed images

Here is an example that uses glade and cairo and pango to draw an analogue meter.

The GUI is quite straight forward, moving the slider updates the position of the meter pointer. Use glade to open Meter1.glade and note the following points:

  1. (Properties/General) The main window is not resizable.
  2. (Properties/General) The main window is not deletable so there is no need to have a handler to ignore the delete signal.
  3. (Properties/Common) The main window is set visible so no need to call "show all".
  4. (Properties/Common) The drawing area is set to be 400x200 pixels.
  5. (Properties/Signals) The drawing areas's "draw" handler is passed a pointer to the scale in the user_data parameter. This avoids the need for global scale variable.
  6. (Properties/General) When the scale was added to the box, it is necessary to created an adjustment by pressing the icon at the end of the Adjustment box.
  7. (Properties/Signals) The scale's "value-changed" handler is passed a pointer to the drawing area so it can use gtk_widget_queue_draw to redraw the drawing area.

The numbers are drawn using Pango which is a library for laying out and rendering of text, with an emphasis on internationalization. Pango can use Cairo as a back end for rendering text into a GtkDrawingArea. While Cairo has it's own features for text rendering, Pango is much more flexible and this is a good place to introduce its features.

001: // gcc  -Wall -Wextra -o Meter1 Meter1.c $(pkg-config gtk+-3.0 --cflags --libs) -lm
002: #include <gtk/gtk.h>
003: #include <stdlib.h>
004: #include <math.h>
005: 
006: 
007: 
008: void quitButtonClicked(__attribute__((unused)) GtkWidget *widget, 
009:                        __attribute__((unused)) gpointer   data)
010: {
011:     g_print("Quit Clicked\n");
012:     gtk_main_quit();
013: }
014: 
015: 
016: // Handle the user trying to close the window 
017: gboolean windowDelete(__attribute__((unused)) GtkWidget *widget, 
018:                       __attribute__((unused)) GdkEvent  *event,
019:                       __attribute__((unused)) gpointer   data)
020: {
021:     return TRUE;    // Returning TRUE stops the window being deleted.
022:                     // Returning FALSE allows deletion.   
023: }
024: 
025: // This just causes the drawing area to be redrawn when the slider is moved.
026: // user_data is a pointer to drawingarea1
027: void on_scale1_value_changed(__attribute__((unused)) GtkRange *range,
028:                              gpointer  user_data)
029: {
030:     
031:     gtk_widget_queue_draw(GTK_WIDGET(user_data));
032: }
033: 
034: 
035: // user_data is a pointer to slider1
036: gboolean on_drawingarea1_draw(__attribute__((unused)) GtkWidget *widget,
037:                                      cairo_t *cr, 
038:                                      gpointer user_data)
039: {
040:     gdouble marks;
041:     char text[10];
042:     
043:     static PangoLayout *layout = NULL;  // Retained between calls
044:     PangoFontDescription *desc;
045:     int width,height;
046: 
047:     gdouble value;
048: 
049:     // Do some pango setup on the first call.
050:     if(layout == NULL)
051:     {
052:         layout = pango_cairo_create_layout (cr);
053:         desc = pango_font_description_from_string ("Sans Bold 15");
054:         pango_layout_set_font_description (layout, desc);
055:         pango_font_description_free (desc);
056:     }
057: 
058:     cairo_set_source_rgb(cr, 0, 0, 0);
059:     cairo_set_line_width(cr, 1.0);
060: 
061:     // Set origin as middle of bottom edge of the drawing area.
062:     // Window is not resizable so "magic numbers" will work here. 
063:     cairo_translate(cr,200,190);
064: 
065: 
066:     // Draw the black radial scale marks and labels
067:     for(marks = 0.0; marks <= 100.0; marks += 10.0)
068:     {
069:         // Draw the radial marks
070:         cairo_move_to(cr, 150.0 * cos(M_PI * marks * 0.01),   -150.0 * sin(M_PI * marks * 0.01) );
071:         cairo_line_to(cr, 160.0 * cos(M_PI * marks * 0.01),   -160.0 * sin(M_PI * marks * 0.01) );
072: 
073:         // Set the text to print
074:         sprintf(text,"%2.0f", marks);
075:         pango_layout_set_text (layout, text, -1);
076: 
077:         // Redo layout for new text
078:         pango_cairo_update_layout (cr, layout);
079: 
080:         // Retrieve the size of the rendered text
081:         pango_layout_get_size (layout, &width, &height);
082: 
083:         // Position the text at the end of the radial marks
084:         cairo_move_to(cr, 
085:                       (-180.0 * cos(M_PI *marks * 0.01)) - ((double)width /PANGO_SCALE / 2.0),
086:                       (-180.0 * sin(M_PI *marks * 0.01)) - ((double)height/PANGO_SCALE / 2.0));
087:         // Render the text
088:         pango_cairo_show_layout (cr, layout);
089:     }
090:     cairo_stroke(cr);
091: 
092:     // Retrieve the new slider value
093:     value = gtk_range_get_value(GTK_RANGE(user_data));
094: 
095:     cairo_set_source_rgb(cr, 1.0, 0, 0);
096:     cairo_set_line_width(cr, 1.5);
097: 
098:     // Draw the meter pointer
099:     cairo_move_to(cr, 0.0,0.0);
100:     cairo_line_to(cr, -150.0 * cos(M_PI *value * 0.01),  -150.0 * sin(M_PI *value * 0.01) );
101:     cairo_stroke(cr);  
102: 
103:     return FALSE;
104: }
105: 
106: 
107: // main is a generic version for a glade project.
108: 
109: int main(int argc, char **argv) 
110: {
111:     GtkBuilder *builder = NULL;
112: 
113:     gtk_init (&argc , &argv);
114: 
115:     builder = gtk_builder_new();
116: 
117:     if( gtk_builder_add_from_file (builder,"Meter1.glade" , NULL) == 0)
118:     {
119:         printf("gtk_builder_add_from_file FAILED\n");
120:         return EXIT_FAILURE;
121:     }
122: 
123:     gtk_builder_connect_signals(builder,NULL);
124: 
125:     gtk_main ();
126:     return EXIT_SUCCESS;
127: }
128: 

Cairo Example 4 : Computed images

Here is another example where the image is preiodically re-computed. This example also shows how to use a couple of new signals : "size-allocate" and "map-event".

size-allocate signal

This signal is emitted by a widget when it's allocated size changes. In this example the top level window is configured to be resizeable in the glade file. When the window is resized the drawing area it contains is also resized which has on_drawingarea_size_allocate() set as the handler. This simply retrieves the drawing area's new dimensions and saves them in global variables.

map-event signal

This signal is emitted by a widget when it becomes visible. It is emitted before the first call to the draw handler. This handler simply retrieves the drawing area's new dimensions and saves them in global variables.

001: #include <stdio.h>
002: #include <stdlib.h>
003: #include <string.h>
004: #include <time.h>
005: #include <sys/time.h>
006: #include <gtk/gtk.h>
007: 
008: 
009: // Values for points used to draw the segments
010: #define LEFT  0
011: #define LEFT1  (DigitWidth * 1.0 / 8.0)
012: #define LEFT2  (DigitWidth * 2.0 / 8.0)
013: #define RIGHT  (DigitWidth * 8.0 / 8.0)
014: #define RIGHT1 (DigitWidth * 7.0 / 8.0)
015: #define RIGHT2 (DigitWidth * 6.0 / 8.0)
016: #define TOP  0
017: #define TOP1 (DigitHeight * 1.0 / 14.0)
018: #define TOP2 (DigitHeight * 2.0 / 14.0)
019: #define MIDT (DigitHeight * 6.0 / 14.0)
020: #define MID  (DigitHeight * 7.0 / 14.0)
021: #define MIDB (DigitHeight * 8.0 / 14.0)
022: #define BOTTOM  (DigitHeight * 14.0 / 14.0)
023: #define BOTTOM1 (DigitHeight * 13.0 / 14.0)
024: #define BOTTOM2 (DigitHeight * 12.0 / 14.0)
025: 
026: // On = black  Off = 45% grey background = 50% grey
027: #define SEG_ON 0.0
028: #define SEG_OFF 0.45
029: #define BACKGROUND 0.5
030: 
031: 
032: 
033: 
034: int allSegments[6];      // Bit maps for the 6 7 segment digits
035: 
036: int digitCodes[10] =
037: {0x77,0x24,0x5D,0x6D,0x2E,0x6B,0x7B,0x25,0x7F,0x6F };   // Map 0..9 into segments
038: 
039: double DaWidth,DaHeight;
040: 
041: // Get the drawing area's size whenever it changes.
042: void on_drawingarea_size_allocate (__attribute__((unused)) GtkWidget    *widget,
043:                                    GdkRectangle *allocation,
044:                                    __attribute__((unused)) gpointer      user_data)
045: {
046:     // Save dimentions in global variables.
047:     DaWidth = allocation->width;
048:     DaHeight = allocation->height;
049: }
050: 
051: 
052: // Get the drawing area's size when it is first displayed.
053: // Note this needs "Visibility Notify" Events enables for the drawing area in glade.
054: gboolean on_drawingarea_map_event(GtkWidget *widget,
055:                                  __attribute__((unused)) GdkEvent  *event,
056:                                  __attribute__((unused)) gpointer   user_data)
057: {
058:     
059:     GtkAllocation allocation; 
060:     gtk_widget_get_allocation(widget, &allocation); 
061:     
062:     // Save size in global variables.
063:     DaWidth = allocation.width;
064:     DaHeight = allocation.height;
065: 
066:     return FALSE;
067: }
068: 
069: 
070: 
071: // draw the clock 
072: gboolean on_drawingarea_draw( __attribute__((unused))GtkWidget *widget, 
073:                               cairo_t *cr, 
074:                               __attribute__((unused)) gpointer data)
075: {
076:     int segments;
077:     int xBase;
078:     int digit;
079:     
080:     double DigitWidth,DigitHeight;
081: 
082:     // Calculate size of each digit 
083:     DigitWidth = DaWidth / 6.1;   // 6.1 to give a gap between digits.
084:     DigitHeight = DaHeight;
085: 
086:     // Fill whole of drawing area to 50% grey
087:     cairo_set_source_rgba (cr, BACKGROUND,BACKGROUND,BACKGROUND,1.0);
088:     cairo_paint(cr);
089: 
090:     // Draw each digit in turn , starting from the left
091:     for(digit = 0; digit < 6; digit += 1)
092:     {
093:         segments = allSegments[digit];
094: 
095:         xBase = DaWidth * digit / 6.0;
096:         // Draw the 7 segments coloured either SEG_ON or SEG_OFF
097:         // TOP
098:         if(segments & 0x1) 
099:             cairo_set_source_rgba (cr, SEG_ON,SEG_ON,SEG_ON,1.0);
100:         else 
101:             cairo_set_source_rgba (cr, SEG_OFF,SEG_OFF,SEG_OFF,1.0);
102:         
103:         cairo_move_to(cr,xBase+LEFT1,TOP1);    cairo_line_to(cr,xBase+LEFT2,TOP);
104:         cairo_line_to(cr,xBase+RIGHT2,TOP);    cairo_line_to(cr,xBase+RIGHT1,TOP1);
105:         cairo_line_to(cr,xBase+RIGHT2,TOP2);   cairo_line_to(cr,xBase+LEFT2,TOP2);
106:         cairo_fill(cr);
107:         
108:         // TOP LEFT
109:         if(segments & 0x2) 
110:             cairo_set_source_rgba (cr, SEG_ON,SEG_ON,SEG_ON,1.0);
111:         else 
112:             cairo_set_source_rgba (cr, SEG_OFF,SEG_OFF,SEG_OFF,1.0);
113:     
114:         cairo_move_to(cr,xBase+LEFT1,TOP1);    cairo_line_to(cr,xBase+LEFT2,TOP2);
115:         cairo_line_to(cr,xBase+LEFT2,MIDT);    cairo_line_to(cr,xBase+LEFT1,MID);
116:         cairo_line_to(cr,xBase+LEFT,MIDT);     cairo_line_to(cr,xBase+LEFT,TOP2);
117:         cairo_fill(cr);
118: 
119:         // TOP RIGHT
120:         if(segments & 0x4) cairo_set_source_rgba (cr, SEG_ON,SEG_ON,SEG_ON,1.0);
121:         else cairo_set_source_rgba (cr, SEG_OFF,SEG_OFF,SEG_OFF,1.0);
122:         
123:         cairo_move_to(cr,xBase+RIGHT1,TOP1);   cairo_line_to(cr,xBase+RIGHT2,TOP2);
124:         cairo_line_to(cr,xBase+RIGHT2,MIDT);   cairo_line_to(cr,xBase+RIGHT1,MID);
125:         cairo_line_to(cr,xBase+RIGHT,MIDT);    cairo_line_to(cr,xBase+RIGHT,TOP2);
126:         cairo_fill(cr);
127:     
128:         // MIDDLE
129:         if(segments & 0x8)  cairo_set_source_rgba (cr, SEG_ON,SEG_ON,SEG_ON,1.0);
130:         else cairo_set_source_rgba (cr, SEG_OFF,SEG_OFF,SEG_OFF,1.0);
131:         
132:         cairo_move_to(cr,xBase+LEFT1,MID);     cairo_line_to(cr,xBase+LEFT2,MIDT);
133:         cairo_line_to(cr,xBase+RIGHT2,MIDT);   cairo_line_to(cr,xBase+RIGHT1,MID);
134:         cairo_line_to(cr,xBase+RIGHT2,MIDB);   cairo_line_to(cr,xBase+LEFT2,MIDB);
135:         cairo_fill(cr);
136: 
137:         // BOTTOM LEFT
138:         if(segments & 0x10)  cairo_set_source_rgba (cr, SEG_ON,SEG_ON,SEG_ON,1.0);
139:         else cairo_set_source_rgba (cr, SEG_OFF,SEG_OFF,SEG_OFF,1.0);
140:         
141:         cairo_move_to(cr,xBase+LEFT1,BOTTOM1); cairo_line_to(cr,xBase+LEFT2,BOTTOM2);
142:         cairo_line_to(cr,xBase+LEFT2,MIDB);    cairo_line_to(cr,xBase+LEFT1,MID);
143:         cairo_line_to(cr,xBase+LEFT,MIDB);     cairo_line_to(cr,xBase+LEFT,BOTTOM2);
144:         cairo_fill(cr);
145: 
146:         // BOTTOM RIGHT
147:         if(segments & 0x20) cairo_set_source_rgba (cr, SEG_ON,SEG_ON,SEG_ON,1.0);
148:         else cairo_set_source_rgba (cr, SEG_OFF,SEG_OFF,SEG_OFF,1.0);
149: 
150:         cairo_move_to(cr,xBase+RIGHT1,BOTTOM1);cairo_line_to(cr,xBase+RIGHT2,BOTTOM2);
151:         cairo_line_to(cr,xBase+RIGHT2,MIDB);   cairo_line_to(cr,xBase+RIGHT1,MID);
152:         cairo_line_to(cr,xBase+RIGHT,MIDB);    cairo_line_to(cr,xBase+RIGHT,BOTTOM2);
153:         cairo_fill(cr);
154:     
155:         // BOTTOM
156:         if(segments & 0x40) cairo_set_source_rgba (cr, SEG_ON,SEG_ON,SEG_ON,1.0);
157:         else cairo_set_source_rgba (cr, SEG_OFF,SEG_OFF,SEG_OFF,1.0);
158: 
159:         cairo_move_to(cr,xBase+LEFT1,BOTTOM1); cairo_line_to(cr,xBase+LEFT2,BOTTOM);
160:         cairo_line_to(cr,xBase+RIGHT2,BOTTOM); cairo_line_to(cr,xBase+RIGHT1,BOTTOM1);
161:         cairo_line_to(cr,xBase+RIGHT2,BOTTOM2);cairo_line_to(cr,xBase+LEFT2,BOTTOM2);
162:         cairo_fill(cr);
163:     }
164:     
165:     return FALSE;
166: }
167: 
168: 
169: // Exit the gtk event loop when the quit button is pressed.
170: gint on_quitButton_clicked(__attribute__((unused)) GtkButton *widget,
171:                            __attribute__((unused)) gpointer data)
172: {
173:     gtk_main_quit();
174:     return TRUE;
175: }
176: 
177: // Called every second
178: gboolean secondTick( gpointer data)
179: {
180:     struct timeval now;
181:     struct tm *clockTime;
182:     static gboolean catchUp = FALSE;
183:     gboolean timerOk;
184:    
185: 
186:     // Get current time
187:     gettimeofday(&now,NULL);
188: 
189:     // Convert to hr/min/sec
190:     clockTime = localtime(&now.tv_sec);
191: 
192: #ifdef DEBUG
193:     printf("%02d:%02d:%02d %06d\n",clockTime->tm_hour,clockTime->tm_min,clockTime->tm_sec,now.tv_usec);
194: #endif
195: 
196:     // Set the segments for each digit of the time
197:     allSegments[0] = digitCodes[clockTime->tm_hour / 10];
198:     allSegments[1] = digitCodes[clockTime->tm_hour % 10];
199:     allSegments[2] = digitCodes[clockTime->tm_min / 10];
200:     allSegments[3] = digitCodes[clockTime->tm_min % 10];
201:     allSegments[4] = digitCodes[clockTime->tm_sec / 10];
202:     allSegments[5] = digitCodes[clockTime->tm_sec % 10];
203: 
204:     // Timeouts are not precise, so try and catch up if too much time has been lost
205:     // Normally catchUp is FALSE
206: 
207:     // If last timeout was short to catchup, replace it with a normal 1 second timeout
208:     if(catchUp)
209:     {
210:         g_timeout_add(1000,secondTick,data);
211:         timerOk = FALSE;      // Set return value so that THIS timerout is deleted
212:         catchUp = FALSE;
213:     }
214:     else
215:     {
216:         // Check if more that 50000uS  (1/20th second) slow.
217:         if(now.tv_usec > 50000)
218:         {
219:             // Create a new timer which is slightly shorter than 1 second.
220:             g_timeout_add((1000000-now.tv_usec)/1000,secondTick,data);
221:             timerOk = FALSE;   // Set return value so that THIS timeout is deleted
222:             catchUp = TRUE;    // Set flag to reset to 1 second timeout on the next call
223:         }
224:         else
225:         {
226:             timerOk = TRUE;    // Continue with the current 1 second timeout
227:         }
228:     }
229: 
230:     gtk_widget_queue_draw( (GtkWidget *) data);    // Trigger a redraw of the clock
231:     
232:     return timerOk;    
233: }
234: 
235: 
236: int main(int argc, char **argv)
237: {
238:     GtkBuilder *builder;
239:     GtkWidget *drawingAreaWidget;
240: 
241:     // Start up GTK+
242:     gtk_init(&argc,&argv);
243: 
244:     // Use a GtkBuilder to read the glade file and create the GUI
245:     builder = gtk_builder_new ();
246:     if( gtk_builder_add_from_file (builder,"Clock.glade" , NULL) == 0)
247:     {
248:         printf("gtk_builder_add_from_file FAILED\n");
249:         return 1;
250:     }
251: 
252:     // Get a reference to the drawing area from the builder.
253:     drawingAreaWidget = GTK_WIDGET (gtk_builder_get_object (builder,"drawingarea"));
254: 
255:     // Wire up widgets to event handlers
256:     gtk_builder_connect_signals (builder, NULL);
257: 
258:     // Start off an inital 1 second timeout. Pass the drawing area as "userdata"
259:     g_timeout_add(1000,secondTick,(gpointer) drawingAreaWidget);
260: 
261:     // Let gtk do it's thing.....
262:     gtk_main();
263: 
264:     
265:     return 0;
266: }
267: 


This diagram shows two things:
  1. The meanings of the defined symbols TOP,TOP1,TOP2,MIDT,MID,MIDB,BOTTOM2,BOTTOM1, BOTTOM,LEFT,LEFT1,LEFT2,RIGHT2,RIGHT1,RIGHT.
  2. The assignments of bit values to each segment

Meta Stuff

Page Creation

These web pages were created with Bluefish "a powerful editor targeted towards programmers and webdevelopers"

Code hilighting

The sections of code were produced using GNU Source-highlight 3.1.8 See The command used produces .html files which were pasted into the page source.
source-highlight --src-lang c --out-format htmltable --line-number --input Ex1.c > Ex1.c.html

Comments and Corrections

Please send any comments or corrections to


Licence

Creative Commons Licence
This work is licensed under a Creative Commons Attribution-NonCommercial 2.0 UK: England & Wales License.