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.
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 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.
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.
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.
|
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)
|
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.
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:
|
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.
|
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.
|
If you don't have Cmake installed, it can be installed like this:
|
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.
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.
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.
|
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).
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:
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:
The button grows to fill an expanded window.
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:
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.
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:
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.
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."
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
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: |
|
Adding -rdynamic (as suggested by the warning message) fixes the problem.
|
Follow the steps below to produce a glade for Ex6
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: } |
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: |
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:
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.
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.
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:
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: |
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".
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.
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:
|
source-highlight --src-lang c --out-format htmltable --line-number --input Ex1.c > Ex1.c.html
Please send any comments or corrections to