diff options
author | PST 1998 Shawn T. Amundson <amundson@gimp.org> | 1998-03-12 18:23:11 +0000 |
---|---|---|
committer | Shawn Amundson <amundson@src.gnome.org> | 1998-03-12 18:23:11 +0000 |
commit | affaf4f9d265522c71f1a9ba4b359094c00dc18a (patch) | |
tree | 2ed9182335ff959014320e46f7b1e1b76d27c39a | |
parent | 4f3495f955fb0798c18325c4d061891019e6b4f5 (diff) | |
download | gdk-pixbuf-affaf4f9d265522c71f1a9ba4b359094c00dc18a.tar.gz |
I just remembered this commit failed before because
of problems with the cvs server... connection timed out.
Wed Mar 11 14:36:48 PST 1998 Shawn T. Amundson <amundson@gimp.org>
* gtk/docs/: added tutorial, changed some files around to
make more sense.
-rw-r--r-- | ChangeLog | 5 | ||||
-rw-r--r-- | ChangeLog.pre-2-0 | 5 | ||||
-rw-r--r-- | ChangeLog.pre-2-10 | 5 | ||||
-rw-r--r-- | ChangeLog.pre-2-2 | 5 | ||||
-rw-r--r-- | ChangeLog.pre-2-4 | 5 | ||||
-rw-r--r-- | ChangeLog.pre-2-6 | 5 | ||||
-rw-r--r-- | ChangeLog.pre-2-8 | 5 | ||||
-rw-r--r-- | docs/.cvsignore | 1 | ||||
-rw-r--r-- | docs/Makefile.am | 6 | ||||
-rw-r--r-- | docs/Makefile.sgml (renamed from docs/Makefile.gtkfaq) | 15 | ||||
-rw-r--r-- | docs/gtk_tut.sgml | 9092 | ||||
-rw-r--r-- | docs/gtk_tut_it.sgml | 8340 | ||||
-rwxr-xr-x | docs/gtkdocs_fix (renamed from docs/gtkfaq_fix) | 3 | ||||
-rw-r--r-- | docs/tutorial/gtk_tut.sgml | 9092 | ||||
-rw-r--r-- | docs/tutorial/gtk_tut_it.sgml | 8340 |
15 files changed, 34917 insertions, 7 deletions
@@ -1,3 +1,8 @@ +Wed Mar 11 14:36:48 PST 1998 Shawn T. Amundson <amundson@gimp.org> + + * gtk/docs/: added tutorial, changed some files around to + make more sense. + Thu Mar 12 10:49:38 1998 Tim Janik <timj@gimp.org> * gtk/gtkmain.h: diff --git a/ChangeLog.pre-2-0 b/ChangeLog.pre-2-0 index 4bcd1384c..3faa9b655 100644 --- a/ChangeLog.pre-2-0 +++ b/ChangeLog.pre-2-0 @@ -1,3 +1,8 @@ +Wed Mar 11 14:36:48 PST 1998 Shawn T. Amundson <amundson@gimp.org> + + * gtk/docs/: added tutorial, changed some files around to + make more sense. + Thu Mar 12 10:49:38 1998 Tim Janik <timj@gimp.org> * gtk/gtkmain.h: diff --git a/ChangeLog.pre-2-10 b/ChangeLog.pre-2-10 index 4bcd1384c..3faa9b655 100644 --- a/ChangeLog.pre-2-10 +++ b/ChangeLog.pre-2-10 @@ -1,3 +1,8 @@ +Wed Mar 11 14:36:48 PST 1998 Shawn T. Amundson <amundson@gimp.org> + + * gtk/docs/: added tutorial, changed some files around to + make more sense. + Thu Mar 12 10:49:38 1998 Tim Janik <timj@gimp.org> * gtk/gtkmain.h: diff --git a/ChangeLog.pre-2-2 b/ChangeLog.pre-2-2 index 4bcd1384c..3faa9b655 100644 --- a/ChangeLog.pre-2-2 +++ b/ChangeLog.pre-2-2 @@ -1,3 +1,8 @@ +Wed Mar 11 14:36:48 PST 1998 Shawn T. Amundson <amundson@gimp.org> + + * gtk/docs/: added tutorial, changed some files around to + make more sense. + Thu Mar 12 10:49:38 1998 Tim Janik <timj@gimp.org> * gtk/gtkmain.h: diff --git a/ChangeLog.pre-2-4 b/ChangeLog.pre-2-4 index 4bcd1384c..3faa9b655 100644 --- a/ChangeLog.pre-2-4 +++ b/ChangeLog.pre-2-4 @@ -1,3 +1,8 @@ +Wed Mar 11 14:36:48 PST 1998 Shawn T. Amundson <amundson@gimp.org> + + * gtk/docs/: added tutorial, changed some files around to + make more sense. + Thu Mar 12 10:49:38 1998 Tim Janik <timj@gimp.org> * gtk/gtkmain.h: diff --git a/ChangeLog.pre-2-6 b/ChangeLog.pre-2-6 index 4bcd1384c..3faa9b655 100644 --- a/ChangeLog.pre-2-6 +++ b/ChangeLog.pre-2-6 @@ -1,3 +1,8 @@ +Wed Mar 11 14:36:48 PST 1998 Shawn T. Amundson <amundson@gimp.org> + + * gtk/docs/: added tutorial, changed some files around to + make more sense. + Thu Mar 12 10:49:38 1998 Tim Janik <timj@gimp.org> * gtk/gtkmain.h: diff --git a/ChangeLog.pre-2-8 b/ChangeLog.pre-2-8 index 4bcd1384c..3faa9b655 100644 --- a/ChangeLog.pre-2-8 +++ b/ChangeLog.pre-2-8 @@ -1,3 +1,8 @@ +Wed Mar 11 14:36:48 PST 1998 Shawn T. Amundson <amundson@gimp.org> + + * gtk/docs/: added tutorial, changed some files around to + make more sense. + Thu Mar 12 10:49:38 1998 Tim Janik <timj@gimp.org> * gtk/gtkmain.h: diff --git a/docs/.cvsignore b/docs/.cvsignore index d4cf0e536..61df8a423 100644 --- a/docs/.cvsignore +++ b/docs/.cvsignore @@ -1,5 +1,6 @@ Makefile Makefile.in +*.html *.info* *.dvi *.ps diff --git a/docs/Makefile.am b/docs/Makefile.am index 8d4592b42..75eead560 100644 --- a/docs/Makefile.am +++ b/docs/Makefile.am @@ -5,9 +5,11 @@ info_TEXINFOS = gdk.texi gtk.texi glib.texi EXTRA_DIST = \ texinfo.tex \ macros.texi \ - Makefile.gtkfaq \ + Makefile.sgml \ + gtkdocs_fix \ gtkfaq.sgml \ - gtkfaq_fix \ + gtk_tut.sgml \ + gtk_tut_it.sgml \ debugging.txt \ developers.txt \ refcounting.txt \ diff --git a/docs/Makefile.gtkfaq b/docs/Makefile.sgml index 2b2dfcb7a..106232fcd 100644 --- a/docs/Makefile.gtkfaq +++ b/docs/Makefile.sgml @@ -8,9 +8,16 @@ # the automake stuff... # -all: sgml +all: html -sgml: +html: faq tut italian_tut + perl gtkdocs_fix gtkfaq*.html gtk_tut*.html + +faq: sgml2html gtkfaq.sgml - cp gtkfaq.html index.html - perl gtkfaq_fix *.html + +tut: + sgml2html gtk_tut.sgml + +italian_tut: + sgml2html gtk_tut_it.sgml diff --git a/docs/gtk_tut.sgml b/docs/gtk_tut.sgml new file mode 100644 index 000000000..1406c5e5d --- /dev/null +++ b/docs/gtk_tut.sgml @@ -0,0 +1,9092 @@ +<!doctype linuxdoc system> + +<!-- This is the tutorial marked up in SGML + (just to show how to write a comment) +--> + +<article> +<title>GTK Tutorial +<author>Ian Main <tt><htmlurl url="mailto:slow@intergate.bc.ca" + name="<slow@intergate.bc.ca>"></tt>, +Tony Gale <tt><htmlurl url="mailto:gale@gimp.org" + name="<gale@gimp.org>"></tt +<date>March 8th, 1998 + +<!-- ***************************************************************** --> +<sect>Introduction +<!-- ***************************************************************** --> +<p> +GTK (GIMP Toolkit) was originally developed as a toolkit for the GIMP +(General Image Manipulation Program). GTK is built on top of GDK (GIMP +Drawing Kit) which is basically wrapper around the Xlib functions. It's +called the GIMP toolkit because it was original written for developing +the GIMP, but has now been used in several free software projects. The +authors are +<itemize> +<item> Peter Mattis <tt><htmlurl url="mailto:petm@xcf.berkeley.edu" + name="petm@xcf.berkeley.edu"></tt> +<item> Spencer Kimball <tt><htmlurl url="mailto:spencer@xcf.berkeley.edu" + name="spencer@xcf.berkeley.edu"></tt> +<item> Josh MacDonald <tt><htmlurl url="mailto:jmacd@xcf.berkeley.edu" + name="jmacd@xcf.berkeley.edu"></tt> +</itemize> + +<p> +GTK is essentially an object oriented application programmers interface (API). +Although written completely in +C, it is implemented using the idea of classes and callback functions +(pointers to functions). +<p> +There is also a third component called glib which contains a few +replacements for some standard calls, as well as some additional functions +for handling linked lists etc. The replacement functions are used to +increase GTK's portability, as some of the functions implemented +here are not available or are nonstandard on other unicies such as +g_strerror(). Some also contain enhancements to the libc versions such as +g_malloc has enhanced debugging utilities. +<p> +This tutorial is an attempt to document as much as possible of GTK, it is by +no means complete. This +tutorial assumes a good understanding of C, and how to create C programs. +It would be a great benefit for the reader to have previous X programming +experience, but it shouldn't be necessary. If you are learning GTK as your +first widget set, please comment on how you found this tutorial, and what +you had troubles with. +Note that there is also a C++ API for GTK (GTK--) in the works, so if you +prefer to use C++, you should look into this instead. There's also an +Objective C wrapper, and guile bindings available, but I don't follow these. +<p> +I would very much like to hear any problems you have learning GTK from this +document, and would appreciate input as to how it may be improved. + +<!-- ***************************************************************** --> +<sect>Getting Started +<!-- ***************************************************************** --> + +<p> +The first thing to do of course, is download the GTK source and install +it. You can always get the latest version from ftp.gimp.org in /pub/gtk. +You can also view other sources of GTK information on http://www.gimp.org/gtk +GTK uses GNU autoconf for +configuration. Once untar'd, type ./configure --help to see a list of options. +<p> +To begin our introduction to GTK, we'll start with the simplest program +possible. This program will +create a 200x200 pixel window and has no way of exiting except to be +killed using the shell. + +<tscreen><verb> +#include <gtk/gtk.h> + +int main (int argc, char *argv[]) +{ + GtkWidget *window; + + gtk_init (&argc, &argv); + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_widget_show (window); + + gtk_main (); + + return 0; +} +</verb></tscreen> + +All programs will of course include the gtk/gtk.h which declares the +variables, functions, structures etc. that will be used in your GTK +application. +<p> +The next line: + +<tscreen><verb> +gtk_init (&argc, &argv); +</verb></tscreen> + +calls the function gtk_init(gint *argc, gchar ***argv) which will be +called in all GTK applications. This sets up a few things for us such +as the default visual and color map and then proceeds to call +gdk_init(gint *argc, gchar ***argv). This function initializes the +library for use, sets up default signal handlers, and checks the +arguments passed to your application on the command line, looking for one +of the following: + +<itemize> +<item> <tt/--display/ +<item> <tt/--debug-level/ +<item> <tt/--no-xshm/ +<item> <tt/--sync/ +<item> <tt/--show-events/ +<item> <tt/--no-show-events/ +</itemize> +<p> +It removes these from the argument list, leaving anything it does +not recognize for your application to parse or ignore. This creates a set +of standard arguments excepted by all GTK applications. +<p> +The next two lines of code create and display a window. + +<tscreen><verb> + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_widget_show (window); +</verb></tscreen> + +The GTK_WINDOW_TOPLEVEL argument specifies that we want the window to +undergo window manager decoration and placement. Rather than create a +window of 0x0 size, a window without children is set to 200x200 by default +so you can still manipulate it. +<p> +The gtk_widget_show() function, lets GTK know that we are done setting the +attributes of this widget, and it can display it. +<p> +The last line enters the GTK main processing loop. + +<tscreen><verb> +gtk_main (); +</verb></tscreen> + +gtk_main() is another call you will see in every GTK application. When +control reaches this point, GTK will sleep waiting for X events (such as +button or key presses), timeouts, or file IO notifications to occur. +In our simple example however, events are ignored. + +<!-- ----------------------------------------------------------------- --> +<sect1>Hello World in GTK +<p> +OK, now for a program with a widget (a button). It's the classic hello +world ala GTK. + +<tscreen><verb> +/* helloworld.c */ + +#include <gtk/gtk.h> + +/* this is a callback function. the data arguments are ignored in this example.. + * More on callbacks below. */ +void hello (GtkWidget *widget, gpointer data) +{ + g_print ("Hello World\n"); +} + +gint delete_event(GtkWidget *widget, gpointer data) +{ + g_print ("delete event occured\n"); + /* if you return TRUE in the "delete_event" signal handler, + * GTK will emit the "destroy" signal. Returning FALSE means + * you don't want the window to be destroyed. + * This is useful for popping up 'are you sure you want to quit ?' + * type dialogs. */ + + /* Change FALSE to TRUE and the main window will be destroyed with + * a "delete_event". */ + + return (FALSE); +} + +/* another callback */ +void destroy (GtkWidget *widget, gpointer data) +{ + gtk_main_quit (); +} + +int main (int argc, char *argv[]) +{ + /* GtkWidget is the storage type for widgets */ + GtkWidget *window; + GtkWidget *button; + + /* this is called in all GTK applications. arguments are parsed from + * the command line and are returned to the application. */ + gtk_init (&argc, &argv); + + /* create a new window */ + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + + /* when the window is given the "delete_event" signal (this is given + * by the window manager (usually the 'close' option, or on the + * titlebar), we ask it to call the delete_event () function + * as defined above. The data passed to the callback + * function is NULL and is ignored in the callback. */ + gtk_signal_connect (GTK_OBJECT (window), "delete_event", + GTK_SIGNAL_FUNC (delete_event), NULL); + + /* here we connect the "destroy" event to a signal handler. + * This event occurs when we call gtk_widget_destroy() on the window, + * or if we return 'TRUE' in the "delete_event" callback. */ + gtk_signal_connect (GTK_OBJECT (window), "destroy", + GTK_SIGNAL_FUNC (destroy), NULL); + + /* sets the border width of the window. */ + gtk_container_border_width (GTK_CONTAINER (window), 10); + + /* creates a new button with the label "Hello World". */ + button = gtk_button_new_with_label ("Hello World"); + + /* When the button receives the "clicked" signal, it will call the + * function hello() passing it NULL as it's argument. The hello() function is + * defined above. */ + gtk_signal_connect (GTK_OBJECT (button), "clicked", + GTK_SIGNAL_FUNC (hello), NULL); + + /* This will cause the window to be destroyed by calling + * gtk_widget_destroy(window) when "clicked. Again, the destroy + * signal could come from here, or the window manager. */ + gtk_signal_connect_object (GTK_OBJECT (button), "clicked", + GTK_SIGNAL_FUNC (gtk_widget_destroy), + GTK_OBJECT (window)); + + /* this packs the button into the window (a gtk container). */ + gtk_container_add (GTK_CONTAINER (window), button); + + /* the final step is to display this newly created widget... */ + gtk_widget_show (button); + + /* and the window */ + gtk_widget_show (window); + + /* all GTK applications must have a gtk_main(). Control ends here + * and waits for an event to occur (like a key press or mouse event). */ + gtk_main (); + + return 0; +} +</verb></tscreen> + +<!-- ----------------------------------------------------------------- --> +<sect1>Compiling Hello World +<p> +To compile use: + +<tscreen><verb> +gcc -Wall -g helloworld.c -o hello_world -L/usr/X11R6/lib \ + -lgtk -lgdk -lglib -lX11 -lXext -lm +</verb></tscreen> +<p> +The libraries above must all be in your default search paths, if not, add +-L<library directory> and gcc will look in these directories for +the needed +libraries. For instance, on my Debian Linux system, I have to add +<tt>-L/usr/X11R6/lib</> for it to find the X11 libraries. +<p> +The order of the libraries are significant. The linker has to know what +functions it needs from a library before it processes it. +<p> +The libraries we are linking in are: +<itemize> +<item>The GTK library (-lgtk), the widget library, based on top of GDK. +<item>The GDK library (-lgdk), the Xlib wrapper. +<item>The glib library (-lglib), containing miscellaneous functions, only +g_print() is used in this particular example. GTK is built on top +of glib so you will always require this library. See the section on +<ref id="sec_glib" name="glib"> for details. +<item>The Xlib library (-lX11) which is used by GDK. +<item>The Xext library (-lXext). This contains code for shared memory +pixmaps and other X extensions. +<item>The math library (-lm). This is used by GTK for various purposes. +</itemize> + +<!-- ----------------------------------------------------------------- --> +<sect1>Theory of Signals and Callbacks +<p> +Before we look in detail at hello world, we'll discuss events and callbacks. +GTK is an event driven toolkit, which means it will sleep in +gtk_main until an event occurs and control is passed to the appropriate +function. +<p> +This passing of control is done using the idea of "signals". When an +event occurs, such as the press of a mouse button, the +appropriate signal will be "emitted" by the widget that was pressed. +This is how GTK does +most of its useful work. To make a button perform an action, +we set up a signal handler to catch these +signals and call the appropriate function. This is done by using a +function such as: + +<tscreen><verb> +gint gtk_signal_connect (GtkObject *object, + gchar *name, + GtkSignalFunc func, + gpointer func_data); +</verb></tscreen> +<p> +Where the first argument is the widget which will be emitting the signal, and +the second, the name of the signal you wish to catch. The third is the function +you wish to be called when it is caught, and the fourth, the data you wish +to have passed to this function. +<p> +The function specified in the third argument is called a "callback +function", and should be of the form: + +<tscreen><verb> +void callback_func(GtkWidget *widget, gpointer *callback_data); +</verb></tscreen> +<p> +Where the first argument will be a pointer to the widget that emitted the signal, and +the second, a pointer to the data given as the last argument to the +gtk_signal_connect() function as shown above. +<p> +Another call used in the hello world example, is: + +<tscreen><verb> +gint gtk_signal_connect_object (GtkObject *object, + gchar *name, + GtkSignalFunc func, + GtkObject *slot_object); +</verb></tscreen> +<p> +gtk_signal_connect_object() is the same as gtk_signal_connect() except that +the callback function only uses one argument, a +pointer to a GTK +object. So when using this function to connect signals, the callback should be of +the form: + +<tscreen><verb> +void callback_func (GtkObject *object); +</verb></tscreen> +<p> +Where the object is usually a widget. We usually don't setup callbacks for +gtk_signal_connect_object however. They are usually used +to call a GTK function that accept a single widget or object as an +argument, as is the case in our hello world example. + +The purpose of having two functions to connect signals is simply to allow +the callbacks to have a different number of arguments. Many functions in +the GTK library accept only a single GtkWidget pointer as an argument, so you +want to use the gtk_signal_connect_object() for these, whereas for your +functions, you may need to have additional data supplied to the callbacks. + +<!-- ----------------------------------------------------------------- --> +<sect1>Stepping Through Hello World +<p> +Now that we know the theory behind this, lets clarify by walking through +the example hello world program. +<p> +Here is the callback function that will be called when the button is +"clicked". We ignore both the widget and the data in this example, but it +is not hard to do things with them. The next example will use the data +argument to tell us which button was pressed. + +<tscreen><verb> +void hello (GtkWidget *widget, gpointer *data) +{ + g_print ("Hello World\n"); +} +</verb></tscreen> + +<p> +This callback is a bit special. The "delete_event" occurs when the +window manager sends this event to the application. We have a choice here +as to what to do about these events. We can ignore them, make some sort of +response, or simply quit the application. + +The value you return in this callback lets GTK know what action to take. +By returning FALSE, we let it know that we don't want to have the "destroy" +signal emitted, keeping our application running. By returning TRUE, we +ask that "destroy" is emitted, which in turn will call our "destroy" +signal handler. + +<tscreen><verb> +gint delete_event(GtkWidget *widget, gpointer data) +{ + g_print ("delete event occured\n"); + + return (FALSE); +} +</verb></tscreen> + +<p> +Here is another callback function which just quits by calling +gtk_main_quit(). Not really much to say about this, it is pretty self +explanatory. +<tscreen><verb> +void destroy (GtkWidget *widget, gpointer *data) +{ + gtk_main_quit (); +} +</verb></tscreen> +<p> +I assume you know about the main() function... yes, as with other +applications, all GTK applications will also have one of these. +<tscreen><verb> +int main (int argc, char *argv[]) +{ +</verb></tscreen> +<p> +This next part, declares a pointer to a structure of type GtkWidget. These +are used below to create a window and a button. +<tscreen><verb> + GtkWidget *window; + GtkWidget *button; +</verb></tscreen> +<p> +Here is our gtk_init again. As before, this initializes the toolkit, and +parses the arguments found on the command line. Any argument it +recognizes from the command line, it removes from the list, and modifies +argc and argv to make it look like they never existed, allowing your +application to parse the remaining arguments. +<tscreen><verb> + gtk_init (&argc, &argv); +</verb></tscreen> +<p> +Create a new window. This is fairly straight forward. Memory is allocated +for the GtkWidget *window structure so it now points to a valid structure. +It sets up a new window, but it is not displayed until below where we call +gtk_widget_show(window) near the end of our program. +<tscreen><verb> + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); +</verb></tscreen> +<p> +Here is an example of connecting a signal handler to an object, in this case, the +window. Here, the "destroy" signal is caught. This is emitted when we use +the window manager to kill the window (and we return TRUE in the +"delete_event" handler), or when we use the +gtk_widget_destroy() call passing in the window widget as the object to +destroy. By setting this up, we handle both cases with a single call. +Here, it just calls the destroy() function defined above with a NULL +argument, which quits GTK for us. +<p> +The GTK_OBJECT and GTK_SIGNAL_FUNC are macros that perform type casting and +checking for us, as well as aid the readability of the code. +<tscreen><verb> + gtk_signal_connect (GTK_OBJECT (window), "destroy", + GTK_SIGNAL_FUNC (destroy), NULL); +</verb></tscreen> +<p> +This next function is used to set an attribute of a container object. +This just sets the window +so it has a blank area along the inside of it 10 pixels wide where no +widgets will go. There are other similar functions which we will look at +in the section on +<ref id="sec_setting_widget_attributes" name="Setting Widget Attributes"> +<p> +And again, GTK_CONTAINER is a macro to perform type casting. +<tscreen><verb> + gtk_container_border_width (GTK_CONTAINER (window), 10); +</verb></tscreen> +<p> +This call creates a new button. It allocates space for a new GtkWidget +structure in memory, initializes it, and makes the button pointer point to +it. It will have the label "Hello World" on it when displayed. +<tscreen><verb> + button = gtk_button_new_with_label ("Hello World"); +</verb></tscreen> +<p> +Here, we take this button, and make it do something useful. We attach a +signal handler to it so when it emits the "clicked" signal, our hello() +function is called. The data is ignored, so we simply pass in NULL to the +hello() callback function. Obviously, the "clicked" signal is emitted when +we click the button with our mouse pointer. + +<tscreen><verb> + gtk_signal_connect (GTK_OBJECT (button), "clicked", + GTK_SIGNAL_FUNC (hello), NULL); +</verb></tscreen> +<p> +We are also going to use this button to exit our program. This will +illustrate how the "destroy" +signal may come from either the window manager, or our program. When the +button is "clicked", same as above, it calls the first hello() callback function, +and then this one in the order they are set up. You may have as many +callback function as you need, and all will be executed in the order you +connected them. Because the gtk_widget_destroy() function accepts only a +GtkWidget *widget as an argument, we use the gtk_signal_connect_object() +function here instead of straight gtk_signal_connect(). + +<tscreen><verb> + gtk_signal_connect_object (GTK_OBJECT (button), "clicked", + GTK_SIGNAL_FUNC (gtk_widget_destroy), + GTK_OBJECT (window)); +</verb></tscreen> +<p> +This is a packing call, which will be explained in depth later on. But it +is fairly easy to understand. It simply tells GTK that the button is to be +placed in the window where it will be displayed. +<tscreen><verb> + gtk_container_add (GTK_CONTAINER (window), button); +</verb></tscreen> +<p> +Now that we have everything setup the way we want it to be. With all the +signal handlers in place, and the button placed in the window where it +should be, we ask GTK to "show" the widgets on the screen. The window +widget is shown last so the whole window will pop up at once rather than +seeing the window pop up, and then the button form inside of it. Although +with such simple example, you'd never notice. +<tscreen><verb> + gtk_widget_show (button); + + gtk_widget_show (window); +</verb></tscreen> +<p> +And of course, we call gtk_main() which waits for events to come from the X +server and will call on the widgets to emit signals when these events come. +<tscreen><verb> + gtk_main (); +</verb></tscreen> +And the final return. Control returns here after gtk_quit() is called. +<tscreen><verb> + return 0; +</verb></tscreen> +<p> +Now, when we click the mouse button on a GTK button, the +widget emits a "clicked" signal. In order for us to use this information, our +program sets up a signal handler to catch that signal, which dispatches the function +of our choice. In our example, when the button we created is "clicked", the +hello() function is called with a NULL +argument, and then the next handler for this signal is called. This calls +the gtk_widget_destroy() function, passing it the window widget as it's +argument, destroying the window widget. This causes the window to emit the +"destroy" signal, which is +caught, and calls our destroy() callback function, which simply exits GTK. +<p> +Another course of events, is to use the window manager to kill the window. +This will cause the "delete_event" to be emitted. This will call our +"delete_event" handler. If we return FALSE here, the window will be left as +is and nothing will happen. Returning TRUE will cause GTK to emit the +"destroy" signal which of course, calls the "destroy" callback, exiting GTK. +<p> +Note that these signals are not the same as the Unix system +signals, and are not implemented using them, although the terminology is +almost identical. + +<!-- ***************************************************************** --> +<sect>Moving On +<!-- ***************************************************************** --> + +<!-- ----------------------------------------------------------------- --> +<sect1>Data Types +<p> +There are a few things you probably noticed in the previous examples that +need explaining. The +gint, gchar etc. that you see are typedefs to int and char respectively. This is done +to get around that nasty dependency on the size of simple data types when doing calculations. +A good example is "gint32" which will be +typedef'd to a 32 bit integer for any given platform, whether it be the 64 bit +alpha, or the 32 bit i386. The +typedefs are very straight forward and intuitive. They are all defined in +glib/glib.h (which gets included from gtk.h). +<p> +You'll also notice the ability to use GtkWidget when the function calls for a GtkObject. +GTK is an object oriented design, and a widget is an object. + +<!-- ----------------------------------------------------------------- --> +<sect1>More on Signal Handlers +<p> +Lets take another look at the gtk_signal_connect declaration. + +<tscreen><verb> +gint gtk_signal_connect (GtkObject *object, gchar *name, + GtkSignalFunc func, gpointer func_data); +</verb></tscreen> + +Notice the gint return value ? This is a tag that identifies your callback +function. As said above, you may have as many callbacks per signal and per +object as you need, and each will be executed in turn, in the order they were attached. +This tag allows you to remove this callback from the list by using: +<tscreen><verb> +void gtk_signal_disconnect (GtkObject *object, + gint id); +</verb></tscreen> +So, by passing in the widget you wish to remove the handler from, and the +tag or id returned by one of the signal_connect functions, you can +disconnect a signal handler. +<p> +Another function to remove all the signal handers from an object is: +<tscreen><verb> +gtk_signal_handlers_destroy (GtkObject *object); +</verb></tscreen> +<p> +This call is fairly self explanatory. It simply removes all the current +signal handlers from the object passed in as the first argument. + +<!-- ----------------------------------------------------------------- --> +<sect1>An Upgraded Hello World +<p> +Let's take a look at a slightly improved hello world with better examples +of callbacks. This will also introduce us to our next topic, packing +widgets. + +<tscreen><verb> +/* helloworld2.c */ + +#include <gtk/gtk.h> + +/* Our new improved callback. The data passed to this function is printed + * to stdout. */ +void callback (GtkWidget *widget, gpointer *data) +{ + g_print ("Hello again - %s was pressed\n", (char *) data); +} + +/* another callback */ +void delete_event (GtkWidget *widget, gpointer *data) +{ + gtk_main_quit (); +} + +int main (int argc, char *argv[]) +{ + /* GtkWidget is the storage type for widgets */ + GtkWidget *window; + GtkWidget *button; + GtkWidget *box1; + + /* this is called in all GTK applications. arguments are parsed from + * the command line and are returned to the application. */ + gtk_init (&argc, &argv); + + /* create a new window */ + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + + /* this is a new call, this just sets the title of our + * new window to "Hello Buttons!" */ + gtk_window_set_title (GTK_WINDOW (window), "Hello Buttons!"); + + /* Here we just set a handler for delete_event that immediately + * exits GTK. */ + gtk_signal_connect (GTK_OBJECT (window), "delete_event", + GTK_SIGNAL_FUNC (delete_event), NULL); + + + /* sets the border width of the window. */ + gtk_container_border_width (GTK_CONTAINER (window), 10); + + /* we create a box to pack widgets into. this is described in detail + * in the "packing" section below. The box is not really visible, it + * is just used as a tool to arrange widgets. */ + box1 = gtk_hbox_new(FALSE, 0); + + /* put the box into the main window. */ + gtk_container_add (GTK_CONTAINER (window), box1); + + /* creates a new button with the label "Button 1". */ + button = gtk_button_new_with_label ("Button 1"); + + /* Now when the button is clicked, we call the "callback" function + * with a pointer to "button 1" as it's argument */ + gtk_signal_connect (GTK_OBJECT (button), "clicked", + GTK_SIGNAL_FUNC (callback), (gpointer) "button 1"); + + /* instead of gtk_container_add, we pack this button into the invisible + * box, which has been packed into the window. */ + gtk_box_pack_start(GTK_BOX(box1), button, TRUE, TRUE, 0); + + /* always remember this step, this tells GTK that our preparation for + * this button is complete, and it can be displayed now. */ + gtk_widget_show(button); + + /* do these same steps again to create a second button */ + button = gtk_button_new_with_label ("Button 2"); + + /* call the same callback function with a different argument, + * passing a pointer to "button 2" instead. */ + gtk_signal_connect (GTK_OBJECT (button), "clicked", + GTK_SIGNAL_FUNC (callback), (gpointer) "button 2"); + + gtk_box_pack_start(GTK_BOX(box1), button, TRUE, TRUE, 0); + + /* The order in which we show the buttons is not really important, but I + * recommend showing the window last, so it all pops up at once. */ + gtk_widget_show(button); + + gtk_widget_show(box1); + + gtk_widget_show (window); + + /* rest in gtk_main and wait for the fun to begin! */ + gtk_main (); + + return 0; +} +</verb></tscreen> +<p> +Compile this program using the same linking arguments as our first example. +You'll notice this time there is no easy way to exit the program, you have to use +your window manager or command line to kill it. A good exercise for the +reader would be to insert a third "Quit" button that will exit the +program. You may also wish to play with the options to +gtk_box_pack_start() while reading the next section. +Try resizing the window, and observe the behavior. +<p> +Just as a side note, there is another useful define for gtk_window_new() - +GTK_WINDOW_DIALOG. This interacts with the window manager a little +differently and should be used for transient windows. + +<!-- ***************************************************************** --> +<sect>Packing Widgets +<!-- ***************************************************************** --> + +<p> +When creating an application, you'll want to put more than one button +inside a window. Our first hello world example only used one widget so we +could simply use a gtk_container_add call to "pack" the widget into the +window. But when you want to put more than one widget into a window, how +do you control where that widget is positioned ? This is where packing +comes in. + +<!-- ----------------------------------------------------------------- --> +<sect1>Theory of Packing Boxes +<p> +Most packing is done by creating boxes as in the example above. These are +invisible widget containers that we can pack our widgets into and come in +two forms, a horizontal box, and a vertical box. When packing widgets +into a horizontal box, the objects are inserted horizontally from left to +right or right to left depending on the call used. In a vertical box, +widgets are packed from top to bottom or vice versa. You may use any +combination of boxes inside or beside other boxes to create the desired +effect. +<p> +To create a new horizontal box, we use a call to gtk_hbox_new(), and for +vertical boxes, gtk_vbox_new(). The gtk_box_pack_start() and +gtk_box_pack_end() functions are used to place objects inside of these +containers. The gtk_box_pack_start() function will start at the top and +work its way down in a vbox, and pack left to right in an hbox. +gtk_box_pack_end() will do the opposite, packing from bottom to top in a +vbox, and right to left in an hbox. Using these functions allow us to +right justify or left justify our widgets and may be mixed in any way to +achieve the desired effect. We will use gtk_box_pack_start() in most of +our examples. An object may be another container or a widget. And in +fact, many widgets are actually containers themselves including the +button, but we usually only use a label inside a button. +<p> +By using these calls, GTK knows where you want to place your widgets so it +can do automatic resizing and other nifty things. there's also a number +of options as to how your widgets should be packed. As you can imagine, +this method gives us a quite a bit of flexibility when placing and +creating widgets. + +<!-- ----------------------------------------------------------------- --> +<sect1>Details of Boxes +<p> +Because of this flexibility, packing boxes in GTK can be confusing at +first. There are a lot of options, and it's not immediately obvious how +they all fit together. In the end however, there are basically five +different styles you can get. + +<p> +<? <CENTER> > +<? +<IMG SRC="packbox1.gif" VSPACE="15" HSPACE="10" WIDTH="528" HEIGHT="235" +ALT="Box Packing Example Image"> +> +<? </CENTER> > + +Each line contains one horizontal box (hbox) with several buttons. The +call to gtk_box_pack is shorthand for the call to pack each of the buttons +into the hbox. Each of the buttons is packed into the hbox the same way +(i.e. same arguments to the gtk_box_pack_start () function). +<p> +This is the declaration of the gtk_box_pack_start function. + +<tscreen><verb> +void gtk_box_pack_start (GtkBox *box, + GtkWidget *child, + gint expand, + gint fill, + gint padding); +</verb></tscreen> + +The first argument is the box you are packing the object into, the second +is this object. The objects will all be buttons for now, so we'll be +packing buttons into boxes. +<p> +The expand argument to gtk_box_pack_start() or gtk_box_pack_end() controls +whether the widgets are laid out in the box to fill in all the extra space +in the box so the box is expanded to fill the area alloted to it (TRUE). +Or the box is shrunk to just fit the widgets (FALSE). Setting expand to +FALSE will allow you to do right and left +justifying of your widgets. Otherwise, they will all expand to fit in the +box, and the same effect could be achieved by using only one of +gtk_box_pack_start or pack_end functions. +<p> +The fill argument to the gtk_box_pack functions control whether the extra +space is allocated to the objects themselves (TRUE), or as extra padding +in the box around these objects (FALSE). It only has an effect if the +expand argument is also TRUE. +<p> +When creating a new box, the function looks like this: + +<tscreen><verb> +GtkWidget * gtk_hbox_new (gint homogeneous, + gint spacing); +</verb></tscreen> + +The homogeneous argument to gtk_hbox_new (and the same for gtk_vbox_new) +controls whether each object in the box has the same size (i.e. the same +width in an hbox, or the same height in a vbox). If it is set, the expand +argument to the gtk_box_pack routines is always turned on. +<p> +What's the difference between spacing (set when the box is created) and +padding (set when elements are packed)? Spacing is added between objects, +and padding is added on either side of an object. The following figure +should make it clearer: + +<? <CENTER> > +<? +<IMG ALIGN="center" SRC="packbox2.gif" WIDTH="509" HEIGHT="213" +VSPACE="15" HSPACE="10" ALT="Box Packing Example Image"> +> +<? </CENTER> > + +Here is the code used to create the above images. I've commented it fairly +heavily so hopefully you won't have any problems following it. Compile it yourself +and play with it. + +<!-- ----------------------------------------------------------------- --> +<sect1>Packing Demonstration Program +<p> +<tscreen><verb> +/* packbox.c */ + +#include "gtk/gtk.h" + +void +delete_event (GtkWidget *widget, gpointer *data) +{ + gtk_main_quit (); +} + +/* Make a new hbox filled with button-labels. Arguments for the + * variables we're interested are passed in to this function. + * We do not show the box, but do show everything inside. */ +GtkWidget *make_box (gint homogeneous, gint spacing, + gint expand, gint fill, gint padding) +{ + GtkWidget *box; + GtkWidget *button; + char padstr[80]; + + /* create a new hbox with the appropriate homogeneous and spacing + * settings */ + box = gtk_hbox_new (homogeneous, spacing); + + /* create a series of buttons with the appropriate settings */ + button = gtk_button_new_with_label ("gtk_box_pack"); + gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding); + gtk_widget_show (button); + + button = gtk_button_new_with_label ("(box,"); + gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding); + gtk_widget_show (button); + + button = gtk_button_new_with_label ("button,"); + gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding); + gtk_widget_show (button); + + /* create a button with the label depending on the value of + * expand. */ + if (expand == TRUE) + button = gtk_button_new_with_label ("TRUE,"); + else + button = gtk_button_new_with_label ("FALSE,"); + + gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding); + gtk_widget_show (button); + + /* This is the same as the button creation for "expand" + * above, but uses the shorthand form. */ + button = gtk_button_new_with_label (fill ? "TRUE," : "FALSE,"); + gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding); + gtk_widget_show (button); + + sprintf (padstr, "%d);", padding); + + button = gtk_button_new_with_label (padstr); + gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding); + gtk_widget_show (button); + + return box; +} + +int +main (int argc, char *argv[]) +{ + GtkWidget *window; + GtkWidget *button; + GtkWidget *box1; + GtkWidget *box2; + GtkWidget *separator; + GtkWidget *label; + GtkWidget *quitbox; + int which; + + /* Our init, don't forget this! :) */ + gtk_init (&argc, &argv); + + if (argc != 2) { + fprintf (stderr, "usage: packbox num, where num is 1, 2, or 3.\n"); + /* this just does cleanup in GTK, and exits with an exit status of 1. */ + gtk_exit (1); + } + + which = atoi (argv[1]); + + /* Create our window */ + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + + /* You should always remember to connect the destroy signal to the + * main window. This is very important for proper intuitive + * behavior */ + gtk_signal_connect (GTK_OBJECT (window), "delete_event", + GTK_SIGNAL_FUNC (delete_event), NULL); + gtk_container_border_width (GTK_CONTAINER (window), 10); + + /* We create a vertical box (vbox) to pack the horizontal boxes into. + * This allows us to stack the horizontal boxes filled with buttons one + * on top of the other in this vbox. */ + box1 = gtk_vbox_new (FALSE, 0); + + /* which example to show. These correspond to the pictures above. */ + switch (which) { + case 1: + /* create a new label. */ + label = gtk_label_new ("gtk_hbox_new (FALSE, 0);"); + + /* Align the label to the left side. We'll discuss this function and + * others in the section on Widget Attributes. */ + gtk_misc_set_alignment (GTK_MISC (label), 0, 0); + + /* Pack the label into the vertical box (vbox box1). Remember that + * widgets added to a vbox will be packed one on top of the other in + * order. */ + gtk_box_pack_start (GTK_BOX (box1), label, FALSE, FALSE, 0); + + /* show the label */ + gtk_widget_show (label); + + /* call our make box function - homogeneous = FALSE, spacing = 0, + * expand = FALSE, fill = FALSE, padding = 0 */ + box2 = make_box (FALSE, 0, FALSE, FALSE, 0); + gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0); + gtk_widget_show (box2); + + /* call our make box function - homogeneous = FALSE, spacing = 0, + * expand = FALSE, fill = FALSE, padding = 0 */ + box2 = make_box (FALSE, 0, TRUE, FALSE, 0); + gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0); + gtk_widget_show (box2); + + /* Args are: homogeneous, spacing, expand, fill, padding */ + box2 = make_box (FALSE, 0, TRUE, TRUE, 0); + gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0); + gtk_widget_show (box2); + + /* creates a separator, we'll learn more about these later, + * but they are quite simple. */ + separator = gtk_hseparator_new (); + + /* pack the separator into the vbox. Remember each of these + * widgets are being packed into a vbox, so they'll be stacked + * vertically. */ + gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 5); + gtk_widget_show (separator); + + /* create another new label, and show it. */ + label = gtk_label_new ("gtk_hbox_new (TRUE, 0);"); + gtk_misc_set_alignment (GTK_MISC (label), 0, 0); + gtk_box_pack_start (GTK_BOX (box1), label, FALSE, FALSE, 0); + gtk_widget_show (label); + + /* Args are: homogeneous, spacing, expand, fill, padding */ + box2 = make_box (TRUE, 0, TRUE, FALSE, 0); + gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0); + gtk_widget_show (box2); + + /* Args are: homogeneous, spacing, expand, fill, padding */ + box2 = make_box (TRUE, 0, TRUE, TRUE, 0); + gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0); + gtk_widget_show (box2); + + /* another new separator. */ + separator = gtk_hseparator_new (); + /* The last 3 arguments to gtk_box_pack_start are: expand, fill, padding. */ + gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 5); + gtk_widget_show (separator); + + break; + + case 2: + + /* create a new label, remember box1 is a vbox as created + * near the beginning of main() */ + label = gtk_label_new ("gtk_hbox_new (FALSE, 10);"); + gtk_misc_set_alignment (GTK_MISC (label), 0, 0); + gtk_box_pack_start (GTK_BOX (box1), label, FALSE, FALSE, 0); + gtk_widget_show (label); + + /* Args are: homogeneous, spacing, expand, fill, padding */ + box2 = make_box (FALSE, 10, TRUE, FALSE, 0); + gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0); + gtk_widget_show (box2); + + /* Args are: homogeneous, spacing, expand, fill, padding */ + box2 = make_box (FALSE, 10, TRUE, TRUE, 0); + gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0); + gtk_widget_show (box2); + + separator = gtk_hseparator_new (); + /* The last 3 arguments to gtk_box_pack_start are: expand, fill, padding. */ + gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 5); + gtk_widget_show (separator); + + label = gtk_label_new ("gtk_hbox_new (FALSE, 0);"); + gtk_misc_set_alignment (GTK_MISC (label), 0, 0); + gtk_box_pack_start (GTK_BOX (box1), label, FALSE, FALSE, 0); + gtk_widget_show (label); + + /* Args are: homogeneous, spacing, expand, fill, padding */ + box2 = make_box (FALSE, 0, TRUE, FALSE, 10); + gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0); + gtk_widget_show (box2); + + /* Args are: homogeneous, spacing, expand, fill, padding */ + box2 = make_box (FALSE, 0, TRUE, TRUE, 10); + gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0); + gtk_widget_show (box2); + + separator = gtk_hseparator_new (); + /* The last 3 arguments to gtk_box_pack_start are: expand, fill, padding. */ + gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 5); + gtk_widget_show (separator); + break; + + case 3: + + /* This demonstrates the ability to use gtk_box_pack_end() to + * right justify widgets. First, we create a new box as before. */ + box2 = make_box (FALSE, 0, FALSE, FALSE, 0); + /* create the label that will be put at the end. */ + label = gtk_label_new ("end"); + /* pack it using gtk_box_pack_end(), so it is put on the right side + * of the hbox created in the make_box() call. */ + gtk_box_pack_end (GTK_BOX (box2), label, FALSE, FALSE, 0); + /* show the label. */ + gtk_widget_show (label); + + /* pack box2 into box1 (the vbox remember ? :) */ + gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0); + gtk_widget_show (box2); + + /* a separator for the bottom. */ + separator = gtk_hseparator_new (); + /* this explicitly sets the separator to 400 pixels wide by 5 pixels + * high. This is so the hbox we created will also be 400 pixels wide, + * and the "end" label will be separated from the other labels in the + * hbox. Otherwise, all the widgets in the hbox would be packed as + * close together as possible. */ + gtk_widget_set_usize (separator, 400, 5); + /* pack the separator into the vbox (box1) created near the start + * of main() */ + gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 5); + gtk_widget_show (separator); + } + + /* Create another new hbox.. remember we can use as many as we need! */ + quitbox = gtk_hbox_new (FALSE, 0); + + /* Our quit button. */ + button = gtk_button_new_with_label ("Quit"); + + /* setup the signal to destroy the window. Remember that this will send + * the "destroy" signal to the window which will be caught by our signal + * handler as defined above. */ + gtk_signal_connect_object (GTK_OBJECT (button), "clicked", + GTK_SIGNAL_FUNC (gtk_main_quit), + GTK_OBJECT (window)); + /* pack the button into the quitbox. + * The last 3 arguments to gtk_box_pack_start are: expand, fill, padding. */ + gtk_box_pack_start (GTK_BOX (quitbox), button, TRUE, FALSE, 0); + /* pack the quitbox into the vbox (box1) */ + gtk_box_pack_start (GTK_BOX (box1), quitbox, FALSE, FALSE, 0); + + /* pack the vbox (box1) which now contains all our widgets, into the + * main window. */ + gtk_container_add (GTK_CONTAINER (window), box1); + + /* and show everything left */ + gtk_widget_show (button); + gtk_widget_show (quitbox); + + gtk_widget_show (box1); + /* Showing the window last so everything pops up at once. */ + gtk_widget_show (window); + + /* And of course, our main function. */ + gtk_main (); + + /* control returns here when gtk_main_quit() is called, but not when + * gtk_exit is used. */ + + return 0; +} +</verb></tscreen> + +<!-- ----------------------------------------------------------------- --> +<sect1>Packing Using Tables +<p> +Let's take a look at another way of packing - Tables. These can be +extremely useful in certain situations. + +Using tables, we create a grid that we can place widgets in. The widgets +may take up as many spaces as we specify. + +The first thing to look at of course, is the gtk_table_new function: + +<tscreen><verb> +GtkWidget* gtk_table_new (gint rows, + gint columns, + gint homogeneous); +</verb></tscreen> +<p> +The first argument is the number of rows to make in the table, while the +second, obviously, the number of columns. + +The homogeneous argument has to do with how the table's boxes are sized. If homogeneous +is TRUE, the table boxes are resized to the size of the largest widget in the table. +If homogeneous is FALSE, the size of a table boxes is dictated by the tallest widget +in its same row, and the widest widget in its column. + +The rows and columnts are laid out starting with 0 to n, where n was the +number specified in the call to gtk_table_new. So, if you specify rows = 2 and +columns = 2, the layout would look something like this: + +<tscreen><verb> + 0 1 2 +0+----------+----------+ + | | | +1+----------+----------+ + | | | +2+----------+----------+ +</verb></tscreen> +<p> +Note that the coordinate system starts in the upper left hand corner. To place a +widget into a box, use the following function: + +<tscreen><verb> +void gtk_table_attach (GtkTable *table, + GtkWidget *child, + gint left_attach, + gint right_attach, + gint top_attach, + gint bottom_attach, + gint xoptions, + gint yoptions, + gint xpadding, + gint ypadding); +</verb></tscreen> +<p> +Where the first argument ("table") is the table you've created and the second +("child") the widget you wish to place in the table. + +The left and right attach +arguments specify where to place the widget, and how many boxes to use. If you want +a button in the lower right table entry +of our 2x2 table, and want it to fill that entry ONLY. left_attach would be = 1, +right_attach = 2, top_attach = 1, bottom_attach = 2. + +Now, if you wanted a widget to take up the whole +top row of our 2x2 table, you'd use left_attach = 0, right_attach =2, top_attach = 0, +bottom_attach = 1. + +The xoptions and yoptions are used to specify packing options and may be OR'ed +together to allow multiple options. + +These options are: +<itemize> +<item>GTK_FILL - If the table box is larger than the widget, and GTK_FILL is +specified, the widget will expand to use all the room available. + +<item>GTK_SHRINK - If the table widget was allocated less space then was +requested (usually by the user resizing the window), then the widgets would +normally just be pushed off the bottom of +the window and disappear. If GTK_SHRINK is specified, the widgets will +shrink with the table. + +<item>GTK_EXPAND - This will cause the table to expand to use up any remaining +space in the window. +</itemize> + +Padding is just like in boxes, creating a clear area around the widget +specified in pixels. + +gtk_table_attach() has a LOT of options. So, there's a shortcut: + +<tscreen><verb> +void gtk_table_attach_defaults (GtkTable *table, + GtkWidget *widget, + gint left_attach, + gint right_attach, + gint top_attach, + gint bottom_attach); +</verb></tscreen> + +The X and Y options default to GTK_FILL | GTK_EXPAND, and X and Y padding +are set to 0. The rest of the arguments are identical to the previous +function. + +We also have gtk_table_set_row_spacing() and gtk_table_set_col_spacing(). This places +spacing between the rows at the specified row or column. + +<tscreen><verb> +void gtk_table_set_row_spacing (GtkTable *table, + gint row, + gint spacing); +</verb></tscreen> +and +<tscreen><verb> +void gtk_table_set_col_spacing (GtkTable *table, + gint column, + gint spacing); +</verb></tscreen> + +Note that for columns, the space goes to the right of the column, and for rows, +the space goes below the row. + +You can also set a consistent spacing of all rows and/or columns with: + +<tscreen><verb> +void gtk_table_set_row_spacings (GtkTable *table, + gint spacing); +</verb></tscreen> +<p> +And, +<tscreen><verb> +void gtk_table_set_col_spacings (GtkTable *table, + gint spacing); +</verb></tscreen> +<p> +Note that with these calls, the last row and last column do not get any spacing + +<!-- ----------------------------------------------------------------- --> +<sect1>Table Packing Example +<p> +Here we make a window with three buttons in a 2x2 table. +The first two buttons will be placed in the upper row. +A third, quit button, is placed in the lower row, spanning both columns. +Which means it should look something like this: +<p> +<? <CENTER> > +<? +<IMG SRC="table.gif" VSPACE="15" HSPACE="10" +ALT="Table Packing Example Image" WIDTH="180" HEIGHT="120"> +> +<? </CENTER> > + +Here's the source code: + +<tscreen><verb> +/* table.c */ +#include <gtk/gtk.h> + +/* our callback. + * the data passed to this function is printed to stdout */ +void callback (GtkWidget *widget, gpointer *data) +{ + g_print ("Hello again - %s was pressed\n", (char *) data); +} + +/* this callback quits the program */ +void delete_event (GtkWidget *widget, gpointer *data) +{ + gtk_main_quit (); +} + +int main (int argc, char *argv[]) +{ + GtkWidget *window; + GtkWidget *button; + GtkWidget *table; + + gtk_init (&argc, &argv); + + /* create a new window */ + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + + /* set the window title */ + gtk_window_set_title (GTK_WINDOW (window), "Table"); + + /* set a handler for delete_event that immediately + * exits GTK. */ + gtk_signal_connect (GTK_OBJECT (window), "delete_event", + GTK_SIGNAL_FUNC (delete_event), NULL); + + /* sets the border width of the window. */ + gtk_container_border_width (GTK_CONTAINER (window), 20); + + /* create a 2x2 table */ + table = gtk_table_new (2, 2, TRUE); + + /* put the table in the main window */ + gtk_container_add (GTK_CONTAINER (window), table); + + /* create first button */ + button = gtk_button_new_with_label ("button 1"); + + /* when the button is clicked, we call the "callback" function + * with a pointer to "button 1" as it's argument */ + gtk_signal_connect (GTK_OBJECT (button), "clicked", + GTK_SIGNAL_FUNC (callback), (gpointer) "button 1"); + + + /* insert button 1 into the upper left quadrant of the table */ + gtk_table_attach_defaults (GTK_TABLE(table), button, 0, 1, 0, 1); + + gtk_widget_show (button); + + /* create second button */ + + button = gtk_button_new_with_label ("button 2"); + + /* when the button is clicked, we call the "callback" function + * with a pointer to "button 2" as it's argument */ + gtk_signal_connect (GTK_OBJECT (button), "clicked", + GTK_SIGNAL_FUNC (callback), (gpointer) "button 2"); + /* insert button 2 into the upper right quadrant of the table */ + gtk_table_attach_defaults (GTK_TABLE(table), button, 1, 2, 0, 1); + + gtk_widget_show (button); + + /* create "Quit" button */ + button = gtk_button_new_with_label ("Quit"); + + /* when the button is clicked, we call the "delete_event" function + * and the program exits */ + gtk_signal_connect (GTK_OBJECT (button), "clicked", + GTK_SIGNAL_FUNC (delete_event), NULL); + + /* insert the quit button into the both + * lower quadrants of the table */ + gtk_table_attach_defaults (GTK_TABLE(table), button, 0, 2, 1, 2); + + gtk_widget_show (button); + + gtk_widget_show (table); + gtk_widget_show (window); + + gtk_main (); + + return 0; +} +</verb></tscreen> + +You can compile this program with something like: + +<tscreen><verb> +gcc -g -Wall -ansi -o table table.c -L/usr/X11R6/lib \ + -lgdk -lgtk -lglib -lX11 -lXext -lm +</verb></tscreen> + +<!-- ***************************************************************** --> +<sect>Widget Overview +<!-- ***************************************************************** --> + +<p> +The general steps to creating a widget in GTK are: +<enum> +<item> gtk_*_new - one of various functions to create a new widget. These +are all detailed in this section. + +<item> Connect all signals we wish to use to the appropriate handlers. + +<item> Set the attributes of the widget. + +<item> Pack the widget into a container using the appropriate call such as +gtk_container_add() or gtk_box_pack_start(). + +<item> gtk_widget_show() the widget. +</enum> +<p> +gtk_widget_show() lets GTK know that we are done setting the attributes +of the widget, and it is ready to be displayed. You may also use +gtk_widget_hide to make it disappear again. The order in which you +show the widgets is not important, but I suggest showing the window +last so the whole window pops up at once rather than seeing the individual +widgets come up on the screen as they're formed. The children of a widget +(a window is a widget too) +will not be displayed until the window itself is shown using the +gtk_widget_show() function. + +<!-- ----------------------------------------------------------------- --> +<sect1> Casting +<p> +You'll notice as you go on, that GTK uses a type casting system. This is +always done using macros that both test the ability to cast the given item, +and perform the cast. Some common ones you will see are: + +<itemize> +<item> GTK_WIDGET(widget) +<item> GTK_OBJECT(object) +<item> GTK_SIGNAL_FUNC(function) +<item> GTK_CONTAINER(container) +<item> GTK_WINDOW(window) +<item> GTK_BOX(box) +</itemize> + +These are all used to cast arguments in functions. You'll see them in the +examples, and can usually tell when to use them simply by looking at the +function's declaration. + +As you can see below in the class hierarchy, all GtkWidgets are derived from +the GtkObject base class. This means you can use an widget in any place the +function asks for an object - simply use the GTK_OBJECT() macro. + +For example: + +<tscreen><verb> +gtk_signal_connect(GTK_OBJECT(button), "clicked", + GTK_SIGNAL_FUNC(callback_function), callback_data); +</verb></tscreen> + +This casts the button into an object, and provides a cast for the function +pointer to the callback. + +Many widgets are also containers. If you look in the class hierarchy below, +you'll notice that many widgets drive from the GtkContainer class. Any one +of those widgets may use with the GTK_CONTAINER macro to +pass them to functions that ask for containers. + +Unfortunately, these macros are not extensively covered in the tutorial, but I +recomend taking a look through the GTK header files. It can be very +educational. In fact, it's not difficult to learn how a widget works just +by looking at the function declarations. + +<!-- ----------------------------------------------------------------- --> +<sect1>Widget Hierarchy +<p> +For your reference, here is the class hierarchy tree used to implement widgets. + +<tscreen><verb> + GtkObject + +GtkData + | +GtkAdjustment + | `GtkTooltips + `GtkWidget + +GtkContainer + | +GtkBin + | | +GtkAlignment + | | +GtkEventBox + | | +GtkFrame + | | | `GtkAspectFrame + | | +GtkHandleBox + | | +GtkItem + | | | +GtkListItem + | | | +GtkMenuItem + | | | | `GtkCheckMenuItem + | | | | `GtkRadioMenuItem + | | | `GtkTreeItem + | | +GtkViewport + | | `GtkWindow + | | +GtkColorSelectionDialog + | | +GtkDialog + | | | `GtkInputDialog + | | `GtkFileSelection + | +GtkBox + | | +GtkButtonBox + | | | +GtkHButtonBox + | | | `GtkVButtonBox + | | +GtkHBox + | | | +GtkCombo + | | | `GtkStatusbar + | | `GtkVBox + | | +GtkColorSelection + | | `GtkGammaCurve + | +GtkButton + | | +GtkOptionMenu + | | `GtkToggleButton + | | `GtkCheckButton + | | `GtkRadioButton + | +GtkCList + | +GtkFixed + | +GtkList + | +GtkMenuShell + | | +GtkMenuBar + | | `GtkMenu + | +GtkNotebook + | +GtkPaned + | | +GtkHPaned + | | `GtkVPaned + | +GtkScrolledWindow + | +GtkTable + | +GtkToolbar + | `GtkTree + +GtkDrawingArea + | `GtkCurve + +GtkEditable + | +GtkEntry + | | `GtkSpinButton + | `GtkText + +GtkMisc + | +GtkArrow + | +GtkImage + | +GtkLabel + | | `GtkTipsQuery + | `GtkPixmap + +GtkPreview + +GtkProgressBar + +GtkRange + | +GtkScale + | | +GtkHScale + | | `GtkVScale + | `GtkScrollbar + | +GtkHScrollbar + | `GtkVScrollbar + +GtkRuler + | +GtkHRuler + | `GtkVRuler + `GtkSeparator + +GtkHSeparator + `GtkVSeparator +</verb></tscreen> + +<!-- ----------------------------------------------------------------- --> +<sect1>Widgets Without Windows +<p> +The following widgets do not have an associated window. If you want to +capture events, you'll have to use the GtkEventBox. See the section on +<ref id="sec_The_EventBox_Widget" name="The EventBox Widget"> + +<tscreen><verb> +GtkAlignment +GtkArrow +GtkBin +GtkBox +GtkImage +GtkItem +GtkLabel +GtkPaned +GtkPixmap +GtkScrolledWindow +GtkSeparator +GtkTable +GtkViewport +GtkAspectFrame +GtkFrame +GtkVPaned +GtkHPaned +GtkVBox +GtkHBox +GtkVSeparator +GtkHSeparator +</verb></tscreen> +<p> +We'll further our exploration of GTK by examining each widget in turn, +creating a few simple functions to display them. Another good source is +the testgtk.c program that comes with GTK. It can be found in +gtk/testgtk.c. + +<!-- ***************************************************************** --> +<sect>The Button Widget +<!-- ***************************************************************** --> + +<!-- ----------------------------------------------------------------- --> +<sect1>Normal Buttons +<p> +We've almost seen all there is to see of the button widget. It's pretty +simple. There is however two ways to create a button. You can use the +gtk_button_new_with_label() to create a button with a label, or use +gtk_button_new() to create a blank button. It's then up to you to pack a +label or pixmap into this new button. To do this, create a new box, and +then pack your objects into this box using the usual gtk_box_pack_start, +and then use gtk_container_add to pack the box into the button. +<p> +Here's an example of using gtk_button_new to create a button with a +picture and a label in it. I've broken the code to create a box up from +the rest so you can use it in your programs. + +<tscreen><verb> +/* buttons.c */ + +#include <gtk/gtk.h> + +/* create a new hbox with an image and a label packed into it + * and return the box.. */ + +GtkWidget *xpm_label_box (GtkWidget *parent, gchar *xpm_filename, gchar *label_text) +{ + GtkWidget *box1; + GtkWidget *label; + GtkWidget *pixmapwid; + GdkPixmap *pixmap; + GdkBitmap *mask; + GtkStyle *style; + + /* create box for xpm and label */ + box1 = gtk_hbox_new (FALSE, 0); + gtk_container_border_width (GTK_CONTAINER (box1), 2); + + /* get style of button.. I assume it's to get the background color. + * if someone knows the real reason, please enlighten me. */ + style = gtk_widget_get_style(parent); + + /* now on to the xpm stuff.. load xpm */ + pixmap = gdk_pixmap_create_from_xpm (parent->window, &mask, + &style->bg[GTK_STATE_NORMAL], + xpm_filename); + pixmapwid = gtk_pixmap_new (pixmap, mask); + + /* create label for button */ + label = gtk_label_new (label_text); + + /* pack the pixmap and label into the box */ + gtk_box_pack_start (GTK_BOX (box1), + pixmapwid, FALSE, FALSE, 3); + + gtk_box_pack_start (GTK_BOX (box1), label, FALSE, FALSE, 3); + + gtk_widget_show(pixmapwid); + gtk_widget_show(label); + + return (box1); +} + +/* our usual callback function */ +void callback (GtkWidget *widget, gpointer *data) +{ + g_print ("Hello again - %s was pressed\n", (char *) data); +} + + +int main (int argc, char *argv[]) +{ + /* GtkWidget is the storage type for widgets */ + GtkWidget *window; + GtkWidget *button; + GtkWidget *box1; + + gtk_init (&argc, &argv); + + /* create a new window */ + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + + gtk_window_set_title (GTK_WINDOW (window), "Pixmap'd Buttons!"); + + /* It's a good idea to do this for all windows. */ + gtk_signal_connect (GTK_OBJECT (window), "destroy", + GTK_SIGNAL_FUNC (gtk_exit), NULL); + + gtk_signal_connect (GTK_OBJECT (window), "delete_event", + GTK_SIGNAL_FUNC (gtk_exit), NULL); + + + /* sets the border width of the window. */ + gtk_container_border_width (GTK_CONTAINER (window), 10); + gtk_widget_realize(window); + + /* create a new button */ + button = gtk_button_new (); + + /* You should be getting used to seeing most of these functions by now */ + gtk_signal_connect (GTK_OBJECT (button), "clicked", + GTK_SIGNAL_FUNC (callback), (gpointer) "cool button"); + + /* this calls our box creating function */ + box1 = xpm_label_box(window, "info.xpm", "cool button"); + + /* pack and show all our widgets */ + gtk_widget_show(box1); + + gtk_container_add (GTK_CONTAINER (button), box1); + + gtk_widget_show(button); + + gtk_container_add (GTK_CONTAINER (window), button); + + gtk_widget_show (window); + + /* rest in gtk_main and wait for the fun to begin! */ + gtk_main (); + + return 0; +} +</verb></tscreen> + +The xpm_label_box function could be used to pack xpm's and labels into any +widget that can be a container. + +<!-- ----------------------------------------------------------------- --> +<sect1> Toggle Buttons +<p> +Toggle buttons are very similar to normal buttons, except they will always +be in one of two states, alternated by a click. They may be depressed, and +when you click again, they will pop back up. Click again, and they will pop +back down. + +Toggle buttons are the basis for check buttons and radio buttons, as such, +many of the calls used for toggle buttons are inherited by radio and check +buttons. I will point these out when we come to them. + +Creating a new toggle button: + +<tscreen><verb> +GtkWidget* gtk_toggle_button_new (void); + +GtkWidget* gtk_toggle_button_new_with_label (gchar *label); +</verb></tscreen> +<p> +As you can imagine, these work identically to the normal button widget +calls. The first creates a blank toggle button, and the second, a button +with a label widget already packed into it. +<p> +To retrieve the state of the toggle widget, including radio and check +buttons, we use a macro as shown in our example below. This tests the state +of the toggle in a callback. The signal of interest emitted to us by toggle +buttons (the toggle button, check button, and radio button widgets), is the +"toggled" signal. To check the state of these buttons, set up a signal +handler to catch the toggled signal, and use the macro to determine it's +state. The callback will look something like: + +<tscreen><verb> +void toggle_button_callback (GtkWidget *widget, gpointer data) +{ + if (GTK_TOGGLE_BUTTON (widget)->active) + { + /* If control reaches here, the toggle button is down */ + + } else { + + /* If control reaches here, the toggle button is up */ + } +} +</verb></tscreen> + +<!-- + +COMMENTED! + +<tscreen><verb> +guint gtk_toggle_button_get_type (void); +</verb></tscreen> +<p> +No idea... they all have this, but I dunno what it is :) + + +<tscreen><verb> +void gtk_toggle_button_set_mode (GtkToggleButton *toggle_button, + gint draw_indicator); +</verb></tscreen> +<p> +No idea. +--> + +<tscreen><verb> +void gtk_toggle_button_set_state (GtkToggleButton *toggle_button, + gint state); +</verb></tscreen> +<p> +The above call can be used to set the state of the toggle button, and it's +children the radio and check buttons. Passing +in your created button as the first argument, and a TRUE or FALSE +for the second state argument to specify whether it should be up (released) or +down (depressed). Default is up, or FALSE. + +Note that when you use the gtk_toggle_button_set_state() function, and the +state is actually changed, it causes +the "clicked" signal to be emitted from the button. + +<tscreen><verb> +void gtk_toggle_button_toggled (GtkToggleButton *toggle_button); +</verb></tscreen> +<p> +This simply toggles the button, and emits the "toggled" signal. + +<!-- ----------------------------------------------------------------- --> +<sect1> Check Buttons +<p> +Check buttons inherent many properties and functions from the the toggle buttons above, +but look a little +different. Rather than being buttons with text inside them, they are small +squares with the text to the right of them. These are often seen for +toggling options on and off in applications. + +The two creation functions are the same as for the normal button. + +<tscreen><verb> +GtkWidget* gtk_check_button_new (void); + +GtkWidget* gtk_check_button_new_with_label (gchar *label); +</verb></tscreen> + +The new_with_label function creates a check button with a label beside it. + +Checking the state of the check button is identical to that of the toggle +button. + +<!-- ----------------------------------------------------------------- --> +<sect1> Radio Buttons +<p> +Radio buttons are similar to check buttons except they are grouped so that +only one may be selected/depressed at a time. This is good for places in +your application where you need to select from a short list of options. + +Creating a new radio button is done with one of these calls: + +<tscreen><verb> +GtkWidget* gtk_radio_button_new (GSList *group); + +GtkWidget* gtk_radio_button_new_with_label (GSList *group, + gchar *label); +</verb></tscreen> +<p> +You'll notice the extra argument to these calls. They require a group to +perform they're duty properly. The first call should pass NULL as the first +argument. Then create a group using: + +<tscreen><verb> +GSList* gtk_radio_button_group (GtkRadioButton *radio_button); +</verb></tscreen> + +<p> +The important thing to remember is that gtk_radio_button_group must be +called for each new button added to the group, with the previous button +passed in as an argument. The result is then passed into the call to +gtk_radio_button_new or gtk_radio_button_new_with_label. This allows a +chain of buttons to be established. The example below should make this +clear. + +It is also a good idea to explicitly set which button should be the +default depressed button with: + +<tscreen><verb> +void gtk_toggle_button_set_state (GtkToggleButton *toggle_button, + gint state); +</verb></tscreen> +<p> +This is described in the section on toggle buttons, and works in exactly the +same way. +<p> +The following example creates a radio button group with three buttons. + +<tscreen><verb> +/* radiobuttons.c */ + +#include <gtk/gtk.h> +#include <glib.h> + +void close_application( GtkWidget *widget, gpointer *data ) { + gtk_main_quit(); +} + +main(int argc,char *argv[]) +{ + static GtkWidget *window = NULL; + GtkWidget *box1; + GtkWidget *box2; + GtkWidget *button; + GtkWidget *separator; + GSList *group; + + gtk_init(&argc,&argv); + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + + gtk_signal_connect (GTK_OBJECT (window), "delete_event", + GTK_SIGNAL_FUNC(close_application), + NULL); + + gtk_window_set_title (GTK_WINDOW (window), "radio buttons"); + gtk_container_border_width (GTK_CONTAINER (window), 0); + + box1 = gtk_vbox_new (FALSE, 0); + gtk_container_add (GTK_CONTAINER (window), box1); + gtk_widget_show (box1); + + box2 = gtk_vbox_new (FALSE, 10); + gtk_container_border_width (GTK_CONTAINER (box2), 10); + gtk_box_pack_start (GTK_BOX (box1), box2, TRUE, TRUE, 0); + gtk_widget_show (box2); + + button = gtk_radio_button_new_with_label (NULL, "button1"); + gtk_box_pack_start (GTK_BOX (box2), button, TRUE, TRUE, 0); + gtk_widget_show (button); + + group = gtk_radio_button_group (GTK_RADIO_BUTTON (button)); + button = gtk_radio_button_new_with_label(group, "button2"); + gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON (button), TRUE); + gtk_box_pack_start (GTK_BOX (box2), button, TRUE, TRUE, 0); + gtk_widget_show (button); + + group = gtk_radio_button_group (GTK_RADIO_BUTTON (button)); + button = gtk_radio_button_new_with_label(group, "button3"); + gtk_box_pack_start (GTK_BOX (box2), button, TRUE, TRUE, 0); + gtk_widget_show (button); + + separator = gtk_hseparator_new (); + gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 0); + gtk_widget_show (separator); + + box2 = gtk_vbox_new (FALSE, 10); + gtk_container_border_width (GTK_CONTAINER (box2), 10); + gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, TRUE, 0); + gtk_widget_show (box2); + + button = gtk_button_new_with_label ("close"); + gtk_signal_connect_object (GTK_OBJECT (button), "clicked", + GTK_SIGNAL_FUNC(close_application), + GTK_OBJECT (window)); + gtk_box_pack_start (GTK_BOX (box2), button, TRUE, TRUE, 0); + GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT); + gtk_widget_grab_default (button); + gtk_widget_show (button); + gtk_widget_show (window); + + gtk_main(); + return(0); +} +</verb></tscreen> + +You can shorten this slightly by using the following syntax, which +removes the need for a variable to hold the list of buttons: + +<tscreen><verb> + button2 = gtk_radio_button_new_with_label( + gtk_radio_button_group (GTK_RADIO_BUTTON (button1)), + "button2"); +</verb></tscreen> + +<!-- ***************************************************************** --> +<sect> Miscallaneous Widgets +<!-- ***************************************************************** --> + +<!-- ----------------------------------------------------------------- --> +<sect1> Labels +<p> +Labels are used a lot in GTK, and are relatively simple. Labels emit no +signals as they do not have an associated X window. If you need to catch +signals, or do clipping, use the EventBox widget. + +To create a new label, use: + +<tscreen><verb> +GtkWidget* gtk_label_new (char *str); +</verb></tscreen> + +Where the sole argument is the string you wish the label to display. + +To change the label's text after creation, use the function: + +<tscreen><verb> +void gtk_label_set (GtkLabel *label, + char *str); +</verb></tscreen> +<p> +Where the first argument is the label you created previously (casted using +the GTK_LABEL() macro), and the second is the new string. + +The space needed for the new string will be automatically adjusted if needed. + +To retrieve the current string, use: + +<tscreen><verb> +void gtk_label_get (GtkLabel *label, + char **str); +</verb></tscreen> + +Where the first arguement is the label you've created, and the second, the +return for the string. + +<!-- ----------------------------------------------------------------- --> +<sect1>The Tooltips Widget +<p> +These are the little text strings that pop up when you leave your pointer +over a button or other widget for a few seconds. They are easy to use, so I +will just explain them without giving an example. If you want to see some +code, take a look at the testgtk.c program distributed with GDK. +<p> +Some widgets (such as the label) will not work with tooltips. +<p> +The first call you will use to create a new tooltip. You only need to do +this once in a given function. The GtkTooltip this function returns can be +used to create multiple tooltips. + +<tscreen><verb> +GtkTooltips *gtk_tooltips_new (void); +</verb></tscreen> + +Once you have created a new tooltip, and the widget you wish to use it on, +simply use this call to set it. + +<tscreen><verb> +void gtk_tooltips_set_tips (GtkTooltips *tooltips, + GtkWidget *widget, + gchar *tips_text); +</verb></tscreen> + +The first argument is the tooltip you've already created, followed by the +widget you wish to have this tooltip pop up for, and the text you wish it to +say. +<p> +Here's a short example: + +<tscreen><verb> +GtkTooltips *tooltips; +GtkWidget *button; +... +tooltips = gtk_tooltips_new (); +button = gtk_button_new_with_label ("button 1"); +... +gtk_tooltips_set_tips (tooltips, button, "This is button 1"); +</verb></tscreen> + + +There are other calls used with tooltips. I will just list them with a +brief description of what they do. + +<tscreen><verb> +void gtk_tooltips_destroy (GtkTooltips *tooltips); +</verb></tscreen> + +Destroy the created tooltips. + +<tscreen><verb> +void gtk_tooltips_enable (GtkTooltips *tooltips); +</verb></tscreen> + +Enable a disabled set of tooltips. + +<tscreen><verb> +void gtk_tooltips_disable (GtkTooltips *tooltips); +</verb></tscreen> + +Disable an enabled set of tooltips. + +<tscreen><verb> +void gtk_tooltips_set_delay (GtkTooltips *tooltips, + gint delay); + +</verb></tscreen> +Sets how many milliseconds you have to hold you pointer over the widget before the +tooltip will pop up. The default is 1000 milliseconds or 1 second. + +<tscreen><verb> +void gtk_tooltips_set_tips (GtkTooltips *tooltips, + GtkWidget *widget, + gchar *tips_text); +</verb></tscreen> + +Change the tooltip text of an already created tooltip. + +<tscreen><verb> +void gtk_tooltips_set_colors (GtkTooltips *tooltips, + GdkColor *background, + GdkColor *foreground); +</verb></tscreen> + +Set the foreground and background color of the tooltips. Again, I have no +idea how to specify the colors. +<p> +And that's all the functions associated with tooltips. More than you'll +ever want to know :) + +<!-- ----------------------------------------------------------------- --> +<sect1> Progress Bars +<p> +Progress bars are used to show the status of an operation. They are pretty +easy to use, as you will see with the code below. But first lets start out +with the call to create a new progress bar. + +<tscreen><verb> +GtkWidget *gtk_progress_bar_new (void); +</verb></tscreen> + +Now that the progress bar has been created we can use it. + +<tscreen><verb> +void gtk_progress_bar_update (GtkProgressBar *pbar, gfloat percentage); +</verb></tscreen> + +The first argument is the progress bar you wish to operate on, and the second +argument is the amount 'completed', meaning the amount the progress bar has +been filled from 0-100% (a real number between 0 and 1). + +Progress Bars are usually used with timeouts or other such functions (see +section on <ref id="sec_timeouts" name="Timeouts, I/O and Idle Functions">) +to give the illusion of multitasking. All will employ +the gtk_progress_bar_update function in the same manner. + +Here is an example of the progress bar, updated using timeouts. This +code also shows you how to reset the Progress Bar. + +<tscreen><verb> +/* progressbar.c */ + +#include <gtk/gtk.h> + +static int ptimer = 0; +int pstat = TRUE; + +/* This function increments and updates the progress bar, it also resets + the progress bar if pstat is FALSE */ +gint progress (gpointer data) +{ + gfloat pvalue; + + /* get the current value of the progress bar */ + pvalue = GTK_PROGRESS_BAR (data)->percentage; + + if ((pvalue >= 1.0) || (pstat == FALSE)) { + pvalue = 0.0; + pstat = TRUE; + } + pvalue += 0.01; + + gtk_progress_bar_update (GTK_PROGRESS_BAR (data), pvalue); + + return TRUE; +} + +/* This function signals a reset of the progress bar */ +void progress_r (void) +{ + pstat = FALSE; +} + +void destroy (GtkWidget *widget, gpointer *data) +{ + gtk_main_quit (); +} + +int main (int argc, char *argv[]) +{ + GtkWidget *window; + GtkWidget *button; + GtkWidget *label; + GtkWidget *table; + GtkWidget *pbar; + + gtk_init (&argc, &argv); + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + + gtk_signal_connect (GTK_OBJECT (window), "delete_event", + GTK_SIGNAL_FUNC (destroy), NULL); + + gtk_container_border_width (GTK_CONTAINER (window), 10); + + table = gtk_table_new(3,2,TRUE); + gtk_container_add (GTK_CONTAINER (window), table); + + label = gtk_label_new ("Progress Bar Example"); + gtk_table_attach_defaults(GTK_TABLE(table), label, 0,2,0,1); + gtk_widget_show(label); + + /* Create a new progress bar, pack it into the table, and show it */ + pbar = gtk_progress_bar_new (); + gtk_table_attach_defaults(GTK_TABLE(table), pbar, 0,2,1,2); + gtk_widget_show (pbar); + + /* Set the timeout to handle automatic updating of the progress bar */ + ptimer = gtk_timeout_add (100, progress, pbar); + + /* This button signals the progress bar to be reset */ + button = gtk_button_new_with_label ("Reset"); + gtk_signal_connect (GTK_OBJECT (button), "clicked", + GTK_SIGNAL_FUNC (progress_r), NULL); + gtk_table_attach_defaults(GTK_TABLE(table), button, 0,1,2,3); + gtk_widget_show(button); + + button = gtk_button_new_with_label ("Cancel"); + gtk_signal_connect (GTK_OBJECT (button), "clicked", + GTK_SIGNAL_FUNC (destroy), NULL); + + gtk_table_attach_defaults(GTK_TABLE(table), button, 1,2,2,3); + gtk_widget_show (button); + + gtk_widget_show(table); + gtk_widget_show(window); + + gtk_main (); + + return 0; +} +</verb></tscreen> + +In this small program there are four areas that concern the general operation +of Progress Bars, we will look at them in the order they are called. + +<tscreen><verb> +pbar = gtk_progress_bar_new (); +</verb></tscreen> + +This code creates a new progress bar, called pbar. + +<tscreen><verb> +ptimer = gtk_timeout_add (100, progress, pbar); +</verb></tscreen> + +This code, uses timeouts to enable a constant time interval, timeouts are +not necessary in the use of Progress Bars. + +<tscreen><verb> +pvalue = GTK_PROGRESS_BAR (data)->percentage; +</verb></tscreen> + +This code assigns the current value of the percentage bar to pvalue. + +<tscreen><verb> +gtk_progress_bar_update (GTK_PROGRESS_BAR (data), pvalue); +</verb></tscreen> + +Finally, this code updates the progress bar with the value of pvalue + +And that is all there is to know about Progress Bars, enjoy. + +<!-- ----------------------------------------------------------------- --> +<sect1> Dialogs +<p> + +The Dialog widget is very simple, and is actually just a window with a few +things pre-packed into it for you. The structure for a Dialog is: + +<tscreen><verb> +struct GtkDialog +{ + GtkWindow window; + + GtkWidget *vbox; + GtkWidget *action_area; +}; +</verb></tscreen> + +So you see, it simple creates a window, and then packs a vbox into the top, +then a seperator, and then an hbox for the "action_area". + +The Dialog widget can be used for pop-up messages to the user, and +other similar tasks. It is really basic, and there is only one +function for the dialog box, which is: + +<tscreen><verb> +GtkWidget* gtk_dialog_new (void); +</verb></tscreen> + +So to create a new dialog box, use, + +<tscreen><verb> +GtkWidget window; +window = gtk_dialog_new (); +</verb></tscreen> + +This will create the dialog box, and it is now up to you to use it. +you could pack a button in the action_area by doing something like so: + +<tscreen><verb> +button = ... +gtk_box_pack_start (GTK_BOX (GTK_DIALOG (window)->action_area), button, + TRUE, TRUE, 0); +gtk_widget_show (button); +</verb></tscreen> + +And you could add to the vbox area by packing, for instance, a label +in it, try something like this: + +<tscreen><verb> +label = gtk_label_new ("Dialogs are groovy"); +gtk_box_pack_start (GTK_BOX (GTK_DIALOG (window)->vbox), label, TRUE, + TRUE, 0); +gtk_widget_show (label); +</verb></tscreen> + +As an example in using the dialog box, you could put two buttons in +the action_area, a Cancel button and an Ok button, and a label in the vbox +area, asking the user a question or giving an error etc. Then you could +attach a different signal to each of the buttons and perform the +operation the user selects. + +<!-- ----------------------------------------------------------------- --> +<sect1> Pixmaps +<p> +Pixmaps are data structures that contain pictures. These pictures can be +used in various places, but most visibly as icons on the X-Windows desktop, +or as cursors. A bitmap is a 2-color pixmap. + +To use pixmaps in GTK, we must first build a GdkPixmap structure using +routines from the GDK layer. Pixmaps can either be created from in-memory +data, or from data read from a file. We'll go through each of the calls +to create a pixmap. + +<tscreen><verb> +GdkPixmap *gdk_bitmap_create_from_data( GdkWindow *window, + gchar *data, + gint width, + gint height ); +</verb></tscreen> +<p> +This routine is used to create a single-plane pixmap (2 colors) from data in +memory. Each bit of the data represents whether that pixel is off or on. +Width and height are in pixels. The GdkWindow pointer is to the current +window, since a pixmap resources are meaningful only in the context of the +screen where it is to be displayed. + +<tscreen><verb> +GdkPixmap* gdk_pixmap_create_from_data( GdkWindow *window, + gchar *data, + gint width, + gint height, + gint depth, + GdkColor *fg, + GdkColor *bg ); +</verb></tscreen> + +This is used to create a pixmap of the given depth (number of colors) from +the bitmap data specified. fg and bg are the foreground and background +color to use. + +<tscreen><verb> +GdkPixmap* gdk_pixmap_create_from_xpm( GdkWindow *window, + GdkBitmap **mask, + GdkColor *transparent_color, + const gchar *filename ); +</verb></tscreen> + +XPM format is a readable pixmap representation for the X Window System. It +is widely used and many different utilities are available for creating image +files in this format. The file specified by filename must contain an image +in that format and it is loaded into the pixmap structure. The mask specifies +what bits of the pixmap are opaque. All other bits are colored using the +color specified by transparent_color. An example using this follows below. + +<tscreen><verb> +GdkPixmap* gdk_pixmap_create_from_xpm_d (GdkWindow *window, + GdkBitmap **mask, + GdkColor *transparent_color, + gchar **data); +</verb></tscreen> + +Small images can be incorporated into a program as data in the XPM format. +A pixmap is created using this data, instead of reading it from a file. +An example of such data is + +<tscreen><verb> +/* XPM */ +static const char * xpm_data[] = { +"16 16 3 1", +" c None", +". c #000000000000", +"X c #FFFFFFFFFFFF", +" ", +" ...... ", +" .XXX.X. ", +" .XXX.XX. ", +" .XXX.XXX. ", +" .XXX..... ", +" .XXXXXXX. ", +" .XXXXXXX. ", +" .XXXXXXX. ", +" .XXXXXXX. ", +" .XXXXXXX. ", +" .XXXXXXX. ", +" .XXXXXXX. ", +" ......... ", +" ", +" "}; +</verb></tscreen> + +<tscreen><verb> +void gdk_pixmap_destroy( GdkPixmap *pixmap ); +</verb></tscreen> +<p> +When we're done using a pixmap and not likely to reuse it again soon, +it is a good idea to release the resource using gdk_pixmap_destroy. Pixmaps +should be considered a precious resource. + + +Once we've created a pixmap, we can display it as a GTK widget. We must +create a pixmap widget to contain the GDK pixmap. This is done using + +<tscreen><verb> +GtkWidget* gtk_pixmap_new( GdkPixmap *pixmap, + GdkBitmap *mask ); +</verb></tscreen> +<p> +The other pixmap widget calls are + +<tscreen><verb> +guint gtk_pixmap_get_type( void ); +void gtk_pixmap_set( GtkPixmap *pixmap, + GdkPixmap *val, + GdkBitmap *mask); +void gtk_pixmap_get( GtkPixmap *pixmap, + GdkPixmap **val, + GdkBitmap **mask); +</verb></tscreen> +<p> +gtk_pixmap_set is used to change the pixmap that the widget is currently +managing. Val is the pixmap created using GDK. + +The following is an example of using a pixmap in a button. + +<tscreen><verb> +/* pixmap.c */ + +#include <gtk/gtk.h> + + +/* XPM data of Open-File icon */ +static const char * xpm_data[] = { +"16 16 3 1", +" c None", +". c #000000000000", +"X c #FFFFFFFFFFFF", +" ", +" ...... ", +" .XXX.X. ", +" .XXX.XX. ", +" .XXX.XXX. ", +" .XXX..... ", +" .XXXXXXX. ", +" .XXXXXXX. ", +" .XXXXXXX. ", +" .XXXXXXX. ", +" .XXXXXXX. ", +" .XXXXXXX. ", +" .XXXXXXX. ", +" ......... ", +" ", +" "}; + + +/* when invoked (via signal delete_event), terminates the application. + */ +void close_application( GtkWidget *widget, gpointer *data ) { + gtk_main_quit(); +} + + +/* is invoked when the button is clicked. It just prints a message. + */ +void button_clicked( GtkWidget *widget, gpointer *data ) { + printf( "button clicked\n" ); +} + +int main( int argc, char *argv[] ) +{ + /* GtkWidget is the storage type for widgets */ + GtkWidget *window, *pixmapwid, *button; + GdkPixmap *pixmap; + GdkBitmap *mask; + GtkStyle *style; + + /* create the main window, and attach delete_event signal to terminating + the application */ + gtk_init( &argc, &argv ); + window = gtk_window_new( GTK_WINDOW_TOPLEVEL ); + gtk_signal_connect( GTK_OBJECT (window), "delete_event", + GTK_SIGNAL_FUNC (close_application), NULL ); + gtk_container_border_width( GTK_CONTAINER (window), 10 ); + gtk_widget_show( window ); + + /* now for the pixmap from gdk */ + style = gtk_widget_get_style( window ); + pixmap = gdk_pixmap_create_from_xpm_d( window->window, &mask, + &style->bg[GTK_STATE_NORMAL], + (gchar **)xpm_data ); + + /* a pixmap widget to contain the pixmap */ + pixmapwid = gtk_pixmap_new( pixmap, mask ); + gtk_widget_show( pixmapwid ); + + /* a button to contain the pixmap widget */ + button = gtk_button_new(); + gtk_container_add( GTK_CONTAINER(button), pixmapwid ); + gtk_container_add( GTK_CONTAINER(window), button ); + gtk_widget_show( button ); + + gtk_signal_connect( GTK_OBJECT(button), "clicked", + GTK_SIGNAL_FUNC(button_clicked), NULL ); + + /* show the window */ + gtk_main (); + + return 0; +} +</verb></tscreen> + + +To load a file from an XPM data file called icon0.xpm in the current +directory, we would have created the pixmap thus + +<tscreen><verb> + /* load a pixmap from a file */ + pixmap = gdk_pixmap_create_from_xpm( window->window, &mask, + &style->bg[GTK_STATE_NORMAL], + "./icon0.xpm" ); + pixmapwid = gtk_pixmap_new( pixmap, mask ); + gtk_widget_show( pixmapwid ); + gtk_container_add( GTK_CONTAINER(window), pixmapwid ); +</verb></tscreen> + + + +Using Shapes +<p> +A disadvantage of using pixmaps is that the displayed object is always +rectangular, regardless of the image. We would like to create desktops +and applications with icons that have more natural shapes. For example, +for a game interface, we would like to have round buttons to push. The +way to do this is using shaped windows. + +A shaped window is simply a pixmap where the background pixels are +transparent. This way, when the background image is multi-colored, we +don't overwrite it with a rectangular, non-matching border around our +icon. The following example displays a full wheelbarrow image on the +desktop. + +<tscreen><verb> +/* wheelbarrow.c */ + +#include <gtk/gtk.h> + +/* XPM */ +static char * WheelbarrowFull_xpm[] = { +"48 48 64 1", +" c None", +". c #DF7DCF3CC71B", +"X c #965875D669A6", +"o c #71C671C671C6", +"O c #A699A289A699", +"+ c #965892489658", +"@ c #8E38410330C2", +"# c #D75C7DF769A6", +"$ c #F7DECF3CC71B", +"% c #96588A288E38", +"& c #A69992489E79", +"* c #8E3886178E38", +"= c #104008200820", +"- c #596510401040", +"; c #C71B30C230C2", +": c #C71B9A699658", +"> c #618561856185", +", c #20811C712081", +"< c #104000000000", +"1 c #861720812081", +"2 c #DF7D4D344103", +"3 c #79E769A671C6", +"4 c #861782078617", +"5 c #41033CF34103", +"6 c #000000000000", +"7 c #49241C711040", +"8 c #492445144924", +"9 c #082008200820", +"0 c #69A618611861", +"q c #B6DA71C65144", +"w c #410330C238E3", +"e c #CF3CBAEAB6DA", +"r c #71C6451430C2", +"t c #EFBEDB6CD75C", +"y c #28A208200820", +"u c #186110401040", +"i c #596528A21861", +"p c #71C661855965", +"a c #A69996589658", +"s c #30C228A230C2", +"d c #BEFBA289AEBA", +"f c #596545145144", +"g c #30C230C230C2", +"h c #8E3882078617", +"j c #208118612081", +"k c #38E30C300820", +"l c #30C2208128A2", +"z c #38E328A238E3", +"x c #514438E34924", +"c c #618555555965", +"v c #30C2208130C2", +"b c #38E328A230C2", +"n c #28A228A228A2", +"m c #41032CB228A2", +"M c #104010401040", +"N c #492438E34103", +"B c #28A2208128A2", +"V c #A699596538E3", +"C c #30C21C711040", +"Z c #30C218611040", +"A c #965865955965", +"S c #618534D32081", +"D c #38E31C711040", +"F c #082000000820", +" ", +" .XoO ", +" +@#$%o& ", +" *=-;#::o+ ", +" >,<12#:34 ", +" 45671#:X3 ", +" +89<02qwo ", +"e* >,67;ro ", +"ty> 459@>+&& ", +"$2u+ ><ipas8* ", +"%$;=* *3:.Xa.dfg> ", +"Oh$;ya *3d.a8j,Xe.d3g8+ ", +" Oh$;ka *3d$a8lz,,xxc:.e3g54 ", +" Oh$;kO *pd$%svbzz,sxxxxfX..&wn> ", +" Oh$@mO *3dthwlsslszjzxxxxxxx3:td8M4 ", +" Oh$@g& *3d$XNlvvvlllm,mNwxxxxxxxfa.:,B* ", +" Oh$@,Od.czlllllzlmmqV@V#V@fxxxxxxxf:%j5& ", +" Oh$1hd5lllslllCCZrV#r#:#2AxxxxxxxxxcdwM* ", +" OXq6c.%8vvvllZZiqqApA:mq:Xxcpcxxxxxfdc9* ", +" 2r<6gde3bllZZrVi7S@SV77A::qApxxxxxxfdcM ", +" :,q-6MN.dfmZZrrSS:#riirDSAX@Af5xxxxxfevo", +" +A26jguXtAZZZC7iDiCCrVVii7Cmmmxxxxxx%3g", +" *#16jszN..3DZZZZrCVSA2rZrV7Dmmwxxxx&en", +" p2yFvzssXe:fCZZCiiD7iiZDiDSSZwwxx8e*>", +" OA1<jzxwwc:$d%NDZZZZCCCZCCZZCmxxfd.B ", +" 3206Bwxxszx%et.eaAp77m77mmmf3&eeeg* ", +" @26MvzxNzvlbwfpdettttttttttt.c,n& ", +" *;16=lsNwwNwgsvslbwwvccc3pcfu<o ", +" p;<69BvwwsszslllbBlllllllu<5+ ", +" OS0y6FBlvvvzvzss,u=Blllj=54 ", +" c1-699Blvlllllu7k96MMMg4 ", +" *10y8n6FjvllllB<166668 ", +" S-kg+>666<M<996-y6n<8* ", +" p71=4 m69996kD8Z-66698&& ", +" &i0ycm6n4 ogk17,0<6666g ", +" N-k-<> >=01-kuu666> ", +" ,6ky& &46-10ul,66, ", +" Ou0<> o66y<ulw<66& ", +" *kk5 >66By7=xu664 ", +" <<M4 466lj<Mxu66o ", +" *>> +66uv,zN666* ", +" 566,xxj669 ", +" 4666FF666> ", +" >966666M ", +" oM6668+ ", +" *4 ", +" ", +" "}; + + +/* when invoked (via signal delete_event), terminates the application. + */ +void close_application( GtkWidget *widget, gpointer *data ) { + gtk_main_quit(); +} + +int main (int argc, char *argv[]) +{ + /* GtkWidget is the storage type for widgets */ + GtkWidget *window, *pixmap, *fixed; + GdkPixmap *gdk_pixmap; + GdkBitmap *mask; + GtkStyle *style; + GdkGC *gc; + + /* create the main window, and attach delete_event signal to terminate + the application. Note that the main window will not have a titlebar + since we're making it a popup. */ + gtk_init (&argc, &argv); + window = gtk_window_new( GTK_WINDOW_POPUP ); + gtk_signal_connect (GTK_OBJECT (window), "delete_event", + GTK_SIGNAL_FUNC (close_application), NULL); + gtk_widget_show (window); + + /* now for the pixmap and the pixmap widget */ + style = gtk_widget_get_default_style(); + gc = style->black_gc; + gdk_pixmap = gdk_pixmap_create_from_xpm_d( window->window, &mask, + &style->bg[GTK_STATE_NORMAL], + WheelbarrowFull_xpm ); + pixmap = gtk_pixmap_new( gdk_pixmap, mask ); + gtk_widget_show( pixmap ); + + /* To display the pixmap, we use a fixed widget to place the pixmap */ + fixed = gtk_fixed_new(); + gtk_widget_set_usize( fixed, 200, 200 ); + gtk_fixed_put( GTK_FIXED(fixed), pixmap, 0, 0 ); + gtk_container_add( GTK_CONTAINER(window), fixed ); + gtk_widget_show( fixed ); + + /* This masks out everything except for the image itself */ + gtk_widget_shape_combine_mask( window, mask, 0, 0 ); + + /* show the window */ + gtk_widget_set_uposition( window, 20, 400 ); + gtk_widget_show( window ); + gtk_main (); + + return 0; +} +</verb></tscreen> +<p> +To make the wheelbarrow image sensitive, we could attach the button press +event signal to make it do something. The following few lines would make +the picture sensitive to a mouse button being pressed which makes the +application terminate. + +<tscreen><verb> +gtk_widget_set_events( window, + gtk_widget_get_events( window ) | + GDK_BUTTON_PRESS_MASK ); + +gtk_signal_connect( GTK_OBJECT(window), "button_press_event", + GTK_SIGNAL_FUNC(close_application), NULL ); +</verb></tscreen> + +<!-- ----------------------------------------------------------------- --> +<sect1>Rulers +<p> +Ruler widgets are used to indicate the location of the mouse pointer +in a given window. A window can have a vertical ruler spanning across +the width and a horizontal ruler spanning down the height. A small +triangular indicator on the ruler shows the exact location of the +pointer relative to the ruler. + +A ruler must first be created. Horizontal and vertical rulers are +created using + +<tscreen><verb> +GtkWidget *gtk_hruler_new(void); /* horizontal ruler */ +GtkWidget *gtk_vruler_new(void); /* vertical ruler */ +</verb></tscreen> + +Once a ruler is created, we can define the unit of measurement. Units +of measure for rulers can be GTK_PIXELS, GTK_INCHES or +GTK_CENTIMETERS. This is set using + +<tscreen><verb> +void gtk_ruler_set_metric( GtkRuler *ruler, + GtkMetricType metric ); +</verb></tscreen> + +The default measure is GTK_PIXELS. + +<tscreen><verb> +gtk_ruler_set_metric( GTK_RULER(ruler), GTK_PIXELS ); +</verb></tscreen> + +Other important characteristics of a ruler are how to mark the units +of scale and where the position indicator is initially placed. These +are set for a ruler using + +<tscreen><verb> +void gtk_ruler_set_range (GtkRuler *ruler, + gfloat lower, + gfloat upper, + gfloat position, + gfloat max_size); +</verb></tscreen> + +The lower and upper arguments define the extents of the ruler, and +max_size is the largest possible number that will be displayed. +Position defines the initial position of the pointer indicator within +the ruler. + +A vertical ruler can span an 800 pixel wide window thus + +<tscreen><verb> +gtk_ruler_set_range( GTK_RULER(vruler), 0, 800, 0, 800); +</verb></tscreen> + +The markings displayed on the ruler will be from 0 to 800, with +a number for every 100 pixels. If instead we wanted the ruler to +range from 7 to 16, we would code + +<tscreen><verb> +gtk_ruler_set_range( GTK_RULER(vruler), 7, 16, 0, 20); +</verb></tscreen> + +The indicator on the ruler is a small triangular mark that indicates +the position of the pointer relative to the ruler. If the ruler is +used to follow the mouse pointer, the motion_notify_event signal +should be connected to the motion_notify_event method of the ruler. +To follow all mouse movements within a window area, we would use + +<tscreen><verb> +#define EVENT_METHOD(i, x) GTK_WIDGET_CLASS(GTK_OBJECT(i)->klass)->x + +gtk_signal_connect_object( GTK_OBJECT(area), "motion_notify_event", + (GtkSignalFunc)EVENT_METHOD(ruler, motion_notify_event), + GTK_OBJECT(ruler) ); +</verb></tscreen> + +The following example creates a drawing area with a horizontal ruler +above it and a vertical ruler to the left of it. The size of the +drawing area is 600 pixels wide by 400 pixels high. The horizontal +ruler spans from 7 to 13 with a mark every 100 pixels, while the +vertical ruler spans from 0 to 400 with a mark every 100 pixels. +Placement of the drawing area and the rulers are done using a table. + +<tscreen><verb> +/* rulers.c */ + +#include <gtk/gtk.h> + +#define EVENT_METHOD(i, x) GTK_WIDGET_CLASS(GTK_OBJECT(i)->klass)->x + +#define XSIZE 600 +#define YSIZE 400 + +/* this routine gets control when the close button is clicked + */ +void close_application( GtkWidget *widget, gpointer *data ) { + gtk_main_quit(); +} + + +/* the main routine + */ +int main( int argc, char *argv[] ) { + GtkWidget *window, *table, *area, *hrule, *vrule; + + /* initialize gtk and create the main window */ + gtk_init( &argc, &argv ); + + window = gtk_window_new( GTK_WINDOW_TOPLEVEL ); + gtk_signal_connect (GTK_OBJECT (window), "delete_event", + GTK_SIGNAL_FUNC( close_application ), NULL); + gtk_container_border_width (GTK_CONTAINER (window), 10); + + /* create a table for placing the ruler and the drawing area */ + table = gtk_table_new( 3, 2, FALSE ); + gtk_container_add( GTK_CONTAINER(window), table ); + + area = gtk_drawing_area_new(); + gtk_drawing_area_size( (GtkDrawingArea *)area, XSIZE, YSIZE ); + gtk_table_attach( GTK_TABLE(table), area, 1, 2, 1, 2, + GTK_EXPAND|GTK_FILL, GTK_FILL, 0, 0 ); + gtk_widget_set_events( area, GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK ); + + /* The horizontal ruler goes on top. As the mouse moves across the drawing area, + a motion_notify_event is passed to the appropriate event handler for the ruler. */ + hrule = gtk_hruler_new(); + gtk_ruler_set_metric( GTK_RULER(hrule), GTK_PIXELS ); + gtk_ruler_set_range( GTK_RULER(hrule), 7, 13, 0, 20 ); + gtk_signal_connect_object( GTK_OBJECT(area), "motion_notify_event", + (GtkSignalFunc)EVENT_METHOD(hrule, motion_notify_event), + GTK_OBJECT(hrule) ); + /* GTK_WIDGET_CLASS(GTK_OBJECT(hrule)->klass)->motion_notify_event, */ + gtk_table_attach( GTK_TABLE(table), hrule, 1, 2, 0, 1, + GTK_EXPAND|GTK_SHRINK|GTK_FILL, GTK_FILL, 0, 0 ); + + /* The vertical ruler goes on the left. As the mouse moves across the drawing area, + a motion_notify_event is passed to the appropriate event handler for the ruler. */ + vrule = gtk_vruler_new(); + gtk_ruler_set_metric( GTK_RULER(vrule), GTK_PIXELS ); + gtk_ruler_set_range( GTK_RULER(vrule), 0, YSIZE, 10, YSIZE ); + gtk_signal_connect_object( GTK_OBJECT(area), "motion_notify_event", + (GtkSignalFunc) + GTK_WIDGET_CLASS(GTK_OBJECT(vrule)->klass)->motion_notify_event, + GTK_OBJECT(vrule) ); + gtk_table_attach( GTK_TABLE(table), vrule, 0, 1, 1, 2, + GTK_FILL, GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0 ); + + /* now show everything */ + gtk_widget_show( area ); + gtk_widget_show( hrule ); + gtk_widget_show( vrule ); + gtk_widget_show( table ); + gtk_widget_show( window ); + gtk_main(); + + return 0; +} +</verb></tscreen> + +<!-- ----------------------------------------------------------------- --> +<sect1>Statusbars +<p> +Statusbars are simple widgets used to display a text message. They keep a stack +of the messages pushed onto them, so that popping the current message +will re-display the previous text message. + +In order to allow different parts of an application to use the same statusbar to display +messages, the statusbar widget issues Context Identifiers which are used to identify +different 'users'. The message on top of the stack is the one displayed, no matter what context +it is in. Messages are stacked in last-in-first-out order, not context identifier order. + +A statusbar is created with a call to: +<tscreen><verb> +GtkWidget* gtk_statusbar_new (void); +</verb></tscreen> + +A new Context Identifier is requested using a call to the following function with a short +textual description of the context: +<tscreen><verb> +guint gtk_statusbar_get_context_id (GtkStatusbar *statusbar, + const gchar *context_description); +</verb></tscreen> + +There are three functions that can operate on statusbars. +<tscreen><verb> +guint gtk_statusbar_push (GtkStatusbar *statusbar, + guint context_id, + gchar *text); + +void gtk_statusbar_pop (GtkStatusbar *statusbar) + guint context_id); +void gtk_statusbar_remove (GtkStatusbar *statusbar, + guint context_id, + guint message_id); +</verb></tscreen> + +The first, gtk_statusbar_push, is used to add a new message to the statusbar. +It returns a Message Identifier, which can be passed later to the function gtk_statusbar_remove +to remove the message with the given Message and Context Identifiers from the statusbar's stack. + +The function gtk_statusbar_pop removes the message highest in the stack with the given +Context Identifier. + +The following example creates a statusbar and two buttons, one for pushing items +onto the statusbar, and one for popping the last item back off. + +<tscreen><verb> +/* statusbar.c */ + +#include <gtk/gtk.h> +#include <glib.h> + +GtkWidget *status_bar; + +void push_item (GtkWidget *widget, gpointer *data) +{ + static int count = 1; + char buff[20]; + + g_snprintf(buff, 20, "Item %d", count++); + gtk_statusbar_push( GTK_STATUSBAR(status_bar), (guint) &data, buff); + + return; +} + +void pop_item (GtkWidget *widget, gpointer *data) +{ + gtk_statusbar_pop( GTK_STATUSBAR(status_bar), (guint) &data ); + return; +} + +int main (int argc, char *argv[]) +{ + + GtkWidget *window; + GtkWidget *vbox; + GtkWidget *button; + + int context_id; + + gtk_init (&argc, &argv); + + /* create a new window */ + window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_widget_set_usize( GTK_WIDGET (window), 200, 100); + gtk_window_set_title(GTK_WINDOW (window), "GTK Statusbar Example"); + gtk_signal_connect(GTK_OBJECT (window), "delete_event", + (GtkSignalFunc) gtk_exit, NULL); + + vbox = gtk_vbox_new(FALSE, 1); + gtk_container_add(GTK_CONTAINER(window), vbox); + gtk_widget_show(vbox); + + status_bar = gtk_statusbar_new(); + gtk_box_pack_start (GTK_BOX (vbox), status_bar, TRUE, TRUE, 0); + gtk_widget_show (status_bar); + + context_id = gtk_statusbar_get_context_id( GTK_STATUSBAR(status_bar), "Statusbar example"); + + button = gtk_button_new_with_label("push item"); + gtk_signal_connect(GTK_OBJECT(button), "clicked", + GTK_SIGNAL_FUNC (push_item), &context_id); + gtk_box_pack_start(GTK_BOX(vbox), button, TRUE, TRUE, 2); + gtk_widget_show(button); + + button = gtk_button_new_with_label("pop last item"); + gtk_signal_connect(GTK_OBJECT(button), "clicked", + GTK_SIGNAL_FUNC (pop_item), &context_id); + gtk_box_pack_start(GTK_BOX(vbox), button, TRUE, TRUE, 2); + gtk_widget_show(button); + + /* always display the window as the last step so it all splashes on + * the screen at once. */ + gtk_widget_show(window); + + gtk_main (); + + return 0; +} +</verb></tscreen> + +<!-- ----------------------------------------------------------------- --> +<sect1>Text Entries +<p> +The Entry widget allows text to be typed and displayed in a single line text box. +The text may be set with functions calls that allow new text to replace, +prepend or append the current contents of the Entry widget. + +There are two functions for creating Entry widgets: +<tscreen><verb> +GtkWidget* gtk_entry_new (void); + +GtkWidget* gtk_entry_new_with_max_length (guint16 max); +</verb></tscreen> + +The first just creates a new Entry widget, whilst the second creates a new Entry and +sets a limit on the length of the text within the Entry.. + +The maximum length of the text within an entry widget may be changed by a call to the following +function. If the current text is longer than this maximum, then it is upto us to alter the Entries +contents appropriately. + +<tscreen><verb> +void gtk_entry_set_max_length (GtkEntry *entry, + guint16 max); +</verb></tscreen> + +There are several functions for altering the text which is currently within the Entry widget. +<tscreen><verb> +void gtk_entry_set_text (GtkEntry *entry, + const gchar *text); +void gtk_entry_append_text (GtkEntry *entry, + const gchar *text); +void gtk_entry_prepend_text (GtkEntry *entry, + const gchar *text); +</verb></tscreen> + +The function gtk_entry_set_text sets the contents of the Entry widget, replacing the +current contents. The functions gtk_entry_append_text and gtk_entry_prepend_text allow +the current contents to be appended and prepended to. + +The next function allows the current insertion point to be set. +<tscreen><verb> +void gtk_entry_set_position (GtkEntry *entry, + gint position); +</verb></tscreen> + +The contents of the Entry can be retrieved by using a call to the following function. This +is useful in the callback functions described below. +<tscreen><verb> +gchar* gtk_entry_get_text (GtkEntry *entry); +</verb></tscreen> + +If we don't want the contents of the Entry to be changed by someone typing into it, we +can change it's edittable state. +<tscreen><verb> +void gtk_entry_set_editable (GtkEntry *entry, + gboolean editable); +</verb></tscreen> + +This function allows us to toggle the edittable state of the Entry widget by passing in +TRUE or FALSE values for the editable argument. + +If we are using the Entry where we don't want the text entered to be visible, for +example when a password is being entered, we can use the following function, which +also takes a boolean flag. +<tscreen><verb> +void gtk_entry_set_visibility (GtkEntry *entry, + gboolean visible); +</verb></tscreen> + +A region of the text may be set as selected by using the following function. This would +most often be used after setting some default text in an Entry, making it easy for the user +to remove it. +<tscreen><verb> +void gtk_entry_select_region (GtkEntry *entry, + gint start, + gint end); +</verb></tscreen> + +If we want to catch when the user has entered text, we can connect to the +<tt/activate/ or <tt/changed/ signal. Activate is raised when the user hits +the enter key within the Entry widget. Changed is raised when the text changes at all, +e.g. for every character entered or removed. + +The following code is an example of using an Entry widget. +<tscreen><verb> +/* entry.c */ + +#include <gtk/gtk.h> + +void enter_callback(GtkWidget *widget, GtkWidget *entry) +{ + gchar *entry_text; + entry_text = gtk_entry_get_text(GTK_ENTRY(entry)); + printf("Entry contents: %s\n", entry_text); +} + +void entry_toggle_editable (GtkWidget *checkbutton, + GtkWidget *entry) +{ + gtk_entry_set_editable(GTK_ENTRY(entry), + GTK_TOGGLE_BUTTON(checkbutton)->active); +} + +void entry_toggle_visibility (GtkWidget *checkbutton, + GtkWidget *entry) +{ + gtk_entry_set_visibility(GTK_ENTRY(entry), + GTK_TOGGLE_BUTTON(checkbutton)->active); +} + +int main (int argc, char *argv[]) +{ + + GtkWidget *window; + GtkWidget *vbox, *hbox; + GtkWidget *entry; + GtkWidget *button; + GtkWidget *check; + + gtk_init (&argc, &argv); + + /* create a new window */ + window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_widget_set_usize( GTK_WIDGET (window), 200, 100); + gtk_window_set_title(GTK_WINDOW (window), "GTK Entry"); + gtk_signal_connect(GTK_OBJECT (window), "delete_event", + (GtkSignalFunc) gtk_exit, NULL); + + vbox = gtk_vbox_new (FALSE, 0); + gtk_container_add (GTK_CONTAINER (window), vbox); + gtk_widget_show (vbox); + + entry = gtk_entry_new_with_max_length (50); + gtk_signal_connect(GTK_OBJECT(entry), "activate", + GTK_SIGNAL_FUNC(enter_callback), + entry); + gtk_entry_set_text (GTK_ENTRY (entry), "hello"); + gtk_entry_append_text (GTK_ENTRY (entry), " world"); + gtk_entry_select_region (GTK_ENTRY (entry), + 0, GTK_ENTRY(entry)->text_length); + gtk_box_pack_start (GTK_BOX (vbox), entry, TRUE, TRUE, 0); + gtk_widget_show (entry); + + hbox = gtk_hbox_new (FALSE, 0); + gtk_container_add (GTK_CONTAINER (vbox), hbox); + gtk_widget_show (hbox); + + check = gtk_check_button_new_with_label("Editable"); + gtk_box_pack_start (GTK_BOX (hbox), check, TRUE, TRUE, 0); + gtk_signal_connect (GTK_OBJECT(check), "toggled", + GTK_SIGNAL_FUNC(entry_toggle_editable), entry); + gtk_toggle_button_set_state(GTK_TOGGLE_BUTTON(check), TRUE); + gtk_widget_show (check); + + check = gtk_check_button_new_with_label("Visible"); + gtk_box_pack_start (GTK_BOX (hbox), check, TRUE, TRUE, 0); + gtk_signal_connect (GTK_OBJECT(check), "toggled", + GTK_SIGNAL_FUNC(entry_toggle_visibility), entry); + gtk_toggle_button_set_state(GTK_TOGGLE_BUTTON(check), TRUE); + gtk_widget_show (check); + + button = gtk_button_new_with_label ("Close"); + gtk_signal_connect_object (GTK_OBJECT (button), "clicked", + GTK_SIGNAL_FUNC(gtk_exit), + GTK_OBJECT (window)); + gtk_box_pack_start (GTK_BOX (vbox), button, TRUE, TRUE, 0); + GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT); + gtk_widget_grab_default (button); + gtk_widget_show (button); + + gtk_widget_show(window); + + gtk_main(); + return(0); +} +</verb></tscreen> + +<!-- ***************************************************************** --> +<sect> Container Widgets +<!-- ***************************************************************** --> + +<!-- ----------------------------------------------------------------- --> +<sect1> Notebooks +<p> +The NoteBook Widget is a collection of 'pages' that overlap each other, +each page contains different information. This widget has become more common +lately in GUI programming, and it is a good way to show blocks similar +information that warrant separation in their display. + +The first function call you will need to know, as you can probably +guess by now, is used to create a new notebook widget. + +<tscreen><verb> +GtkWidget* gtk_notebook_new (void); +</verb></tscreen> + +Once the notebook has been created, there are 12 functions that +operate on the notebook widget. Let's look at them individually. + +The first one we will look at is how to position the page indicators. +These page indicators or 'tabs' as they are referred to, can be positioned +in four ways; top, bottom, left, or right. + +<tscreen><verb> +void gtk_notebook_set_tab_pos (GtkNotebook *notebook, GtkPositionType pos); +</verb></tscreen> + +GtkPostionType will be one of the following, and they are pretty self explanatory. +<itemize> +<item> GTK_POS_LEFT +<item> GTK_POS_RIGHT +<item> GTK_POS_TOP +<item> GTK_POS_BOTTOM +</itemize> + +GTK_POS_TOP is the default. + +Next we will look at how to add pages to the notebook. There are three +ways to add pages to the NoteBook. Let's look at the first two together as +they are quite similar. + +<tscreen><verb> +void gtk_notebook_append_page (GtkNotebook *notebook, GtkWidget *child, GtkWidget *tab_label); + +void gtk_notebook_prepend_page (GtkNotebook *notebook, GtkWidget *child, GtkWidget *tab_label); +</verb></tscreen> + +These functions add pages to the notebook by inserting them from the +back of the notebook (append), or the front of the notebook (prepend). +*child is the widget that is placed within the notebook page, and *tab_label is +the label for the page being added. + +The final function for adding a page to the notebook contains all of +the properties of the previous two, but it allows you to specify what position +you want the page to be in the notebook. + +<tscreen><verb> +void gtk_notebook_insert_page (GtkNotebook *notebook, GtkWidget *child, GtkWidget *tab_label, gint position); +</verb></tscreen> + +The parameters are the same as _append_ and _prepend_ except it +contains an extra parameter, position. This parameter is used to specify what +place this page will inserted to. + +Now that we know how to add a page, lets see how we can remove a page +from the notebook. + +<tscreen><verb> +void gtk_notebook_remove_page (GtkNotebook *notebook, gint page_num); +</verb></tscreen> + +This function takes the page specified by page_num and removes it from +the widget *notebook. + +To find out what the current page is in a notebook use the function: + +<tscreen><verb> +gint gtk_notebook_current_page (GtkNotebook *notebook); +</verb></tscreen> + +These next two functions are simple calls to move the notebook page +forward or backward. Simply provide the respective function call with the +notebook widget you wish to operate on. Note: When the NoteBook is currently +on the last page, and gtk_notebook_next_page is called, the notebook will +wrap back to the first page. Likewise, if the NoteBook is on the first page, +and gtk_notebook_prev_page is called, the notebook will wrap to the last page. + +<tscreen><verb> +void gtk_notebook_next_page (GtkNoteBook *notebook); +void gtk_notebook_prev_page (GtkNoteBook *notebook); +</verb></tscreen> + +This next function sets the 'active' page. If you wish the +notebook to be opened to page 5 for example, you would use this function. +Without using this function, the notebook defaults to the first page. + +<tscreen><verb> +void gtk_notebook_set_page (GtkNotebook *notebook, gint page_num); +</verb></tscreen> + +The next two functions add or remove the notebook page tabs and the +notebook border respectively. + +<tscreen><verb> +void gtk_notebook_set_show_tabs (GtkNotebook *notebook, gint show_tabs); +void gtk_notebook_set_show_border (GtkNotebook *notebook, gint show_border); +</verb></tscreen> + +show_tabs and show_border can both be either TRUE or FALSE (0 or 1). + +Now lets look at an example, it is expanded from the testgtk.c code +that comes with the GTK distribution, and it shows all 13 functions. This +small program, creates a window with a notebook and six buttons. The notebook +contains 11 pages, added in three different ways, appended, inserted, and +prepended. The buttons allow you rotate the tab positions, add/remove the tabs +and border, remove a page, change pages in both a forward and backward manner, +and exit the program. + +<tscreen><verb> +/* notebook.c */ + +#include <gtk/gtk.h> + +/* This function rotates the position of the tabs */ +void rotate_book (GtkButton *button, GtkNotebook *notebook) +{ + gtk_notebook_set_tab_pos (notebook, (notebook->tab_pos +1) %4); +} + +/* Add/Remove the page tabs and the borders */ +void tabsborder_book (GtkButton *button, GtkNotebook *notebook) +{ + gint tval = FALSE; + gint bval = FALSE; + if (notebook->show_tabs == 0) + tval = TRUE; + if (notebook->show_border == 0) + bval = TRUE; + + gtk_notebook_set_show_tabs (notebook, tval); + gtk_notebook_set_show_border (notebook, bval); +} + +/* Remove a page from the notebook */ +void remove_book (GtkButton *button, GtkNotebook *notebook) +{ + gint page; + + page = gtk_notebook_current_page(notebook); + gtk_notebook_remove_page (notebook, page); + /* Need to refresh the widget -- + This forces the widget to redraw itself. */ + gtk_widget_draw(GTK_WIDGET(notebook), NULL); +} + +void delete (GtkWidget *widget, gpointer *data) +{ + gtk_main_quit (); +} + +int main (int argc, char *argv[]) +{ + GtkWidget *window; + GtkWidget *button; + GtkWidget *table; + GtkWidget *notebook; + GtkWidget *frame; + GtkWidget *label; + GtkWidget *checkbutton; + int i; + char bufferf[32]; + char bufferl[32]; + + gtk_init (&argc, &argv); + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + + gtk_signal_connect (GTK_OBJECT (window), "delete_event", + GTK_SIGNAL_FUNC (delete), NULL); + + gtk_container_border_width (GTK_CONTAINER (window), 10); + + table = gtk_table_new(2,6,TRUE); + gtk_container_add (GTK_CONTAINER (window), table); + + /* Create a new notebook, place the position of the tabs */ + notebook = gtk_notebook_new (); + gtk_notebook_set_tab_pos (GTK_NOTEBOOK (notebook), GTK_POS_TOP); + gtk_table_attach_defaults(GTK_TABLE(table), notebook, 0,6,0,1); + gtk_widget_show(notebook); + + /* lets append a bunch of pages to the notebook */ + for (i=0; i < 5; i++) { + sprintf(bufferf, "Append Frame %d", i+1); + sprintf(bufferl, "Page %d", i+1); + + frame = gtk_frame_new (bufferf); + gtk_container_border_width (GTK_CONTAINER (frame), 10); + gtk_widget_set_usize (frame, 100, 75); + gtk_widget_show (frame); + + label = gtk_label_new (bufferf); + gtk_container_add (GTK_CONTAINER (frame), label); + gtk_widget_show (label); + + label = gtk_label_new (bufferl); + gtk_notebook_append_page (GTK_NOTEBOOK (notebook), frame, label); + } + + + /* now lets add a page to a specific spot */ + checkbutton = gtk_check_button_new_with_label ("Check me please!"); + gtk_widget_set_usize(checkbutton, 100, 75); + gtk_widget_show (checkbutton); + + label = gtk_label_new ("Add spot"); + gtk_container_add (GTK_CONTAINER (checkbutton), label); + gtk_widget_show (label); + label = gtk_label_new ("Add page"); + gtk_notebook_insert_page (GTK_NOTEBOOK (notebook), checkbutton, label, 2); + + /* Now finally lets prepend pages to the notebook */ + for (i=0; i < 5; i++) { + sprintf(bufferf, "Prepend Frame %d", i+1); + sprintf(bufferl, "PPage %d", i+1); + + frame = gtk_frame_new (bufferf); + gtk_container_border_width (GTK_CONTAINER (frame), 10); + gtk_widget_set_usize (frame, 100, 75); + gtk_widget_show (frame); + + label = gtk_label_new (bufferf); + gtk_container_add (GTK_CONTAINER (frame), label); + gtk_widget_show (label); + + label = gtk_label_new (bufferl); + gtk_notebook_prepend_page (GTK_NOTEBOOK(notebook), frame, label); + } + + /* Set what page to start at (page 4) */ + gtk_notebook_set_page (GTK_NOTEBOOK(notebook), 3); + + + /* create a bunch of buttons */ + button = gtk_button_new_with_label ("close"); + gtk_signal_connect_object (GTK_OBJECT (button), "clicked", + GTK_SIGNAL_FUNC (delete), NULL); + gtk_table_attach_defaults(GTK_TABLE(table), button, 0,1,1,2); + gtk_widget_show(button); + + button = gtk_button_new_with_label ("next page"); + gtk_signal_connect_object (GTK_OBJECT (button), "clicked", + (GtkSignalFunc) gtk_notebook_next_page, + GTK_OBJECT (notebook)); + gtk_table_attach_defaults(GTK_TABLE(table), button, 1,2,1,2); + gtk_widget_show(button); + + button = gtk_button_new_with_label ("prev page"); + gtk_signal_connect_object (GTK_OBJECT (button), "clicked", + (GtkSignalFunc) gtk_notebook_prev_page, + GTK_OBJECT (notebook)); + gtk_table_attach_defaults(GTK_TABLE(table), button, 2,3,1,2); + gtk_widget_show(button); + + button = gtk_button_new_with_label ("tab position"); + gtk_signal_connect_object (GTK_OBJECT (button), "clicked", + (GtkSignalFunc) rotate_book, GTK_OBJECT(notebook)); + gtk_table_attach_defaults(GTK_TABLE(table), button, 3,4,1,2); + gtk_widget_show(button); + + button = gtk_button_new_with_label ("tabs/border on/off"); + gtk_signal_connect_object (GTK_OBJECT (button), "clicked", + (GtkSignalFunc) tabsborder_book, + GTK_OBJECT (notebook)); + gtk_table_attach_defaults(GTK_TABLE(table), button, 4,5,1,2); + gtk_widget_show(button); + + button = gtk_button_new_with_label ("remove page"); + gtk_signal_connect_object (GTK_OBJECT (button), "clicked", + (GtkSignalFunc) remove_book, + GTK_OBJECT(notebook)); + gtk_table_attach_defaults(GTK_TABLE(table), button, 5,6,1,2); + gtk_widget_show(button); + + gtk_widget_show(table); + gtk_widget_show(window); + + gtk_main (); + + return 0; +} +</verb></tscreen> +<p> +Hopefully this helps you on your way with creating notebooks for your +GTK applications. + +<!-- ----------------------------------------------------------------- --> +<sect1> Scrolled Windows +<p> +Scrolled windows are used to create a scrollable area inside a real window. +You may insert any types of widgets to these scrolled windows, and they will +all be accessable regardless of the size by using the scrollbars. + +The following function is used to create a new scolled window. + +<tscreen><verb> +GtkWidget* gtk_scrolled_window_new (GtkAdjustment *hadjustment, + GtkAdjustment *vadjustment); +</verb></tscreen> +<p> +Where the first argument is the adjustment for the horizontal +direction, and the second, the adjustment for the vertical direction. +These are almost always set to NULL. + +<tscreen><verb> +void gtk_scrolled_window_set_policy (GtkScrolledWindow *scrolled_window, + GtkPolicyType hscrollbar_policy, + GtkPolicyType vscrollbar_policy); +</verb></tscreen> + +This sets the policy to be used with respect to the scrollbars. +The first arguement is the scrolled window you wish to change. The second +sets the policiy for the horizontal scrollbar, and the third, +the vertical scrollbar. + +The policy may be one of GTK_POLICY AUTOMATIC, or GTK_POLICY_ALWAYS. +GTK_POLICY_AUTOMATIC will automatically decide whether you need +scrollbars, wheras GTK_POLICY_ALWAYS will always leave the scrollbars +there. + +Here is a simple example that packs 100 toggle buttons into a scrolled window. +I've only commented on the parts that may be new to you. + +<tscreen><verb> +/* scrolledwin.c */ + +#include <gtk/gtk.h> + +void destroy(GtkWidget *widget, gpointer *data) +{ + gtk_main_quit(); +} + +int main (int argc, char *argv[]) +{ + static GtkWidget *window; + GtkWidget *scrolled_window; + GtkWidget *table; + GtkWidget *button; + char buffer[32]; + int i, j; + + gtk_init (&argc, &argv); + + /* Create a new dialog window for the scrolled window to be + * packed into. A dialog is just like a normal window except it has a + * vbox and a horizontal seperator packed into it. It's just a shortcut + * for creating dialogs */ + window = gtk_dialog_new (); + gtk_signal_connect (GTK_OBJECT (window), "destroy", + (GtkSignalFunc) destroy, NULL); + gtk_window_set_title (GTK_WINDOW (window), "dialog"); + gtk_container_border_width (GTK_CONTAINER (window), 0); + gtk_widget_set_usize(window, 300, 300); + + /* create a new scrolled window. */ + scrolled_window = gtk_scrolled_window_new (NULL, NULL); + + gtk_container_border_width (GTK_CONTAINER (scrolled_window), 10); + + /* the policy is one of GTK_POLICY AUTOMATIC, or GTK_POLICY_ALWAYS. + * GTK_POLICY_AUTOMATIC will automatically decide whether you need + * scrollbars, wheras GTK_POLICY_ALWAYS will always leave the scrollbars + * there. The first one is the horizontal scrollbar, the second, + * the vertical. */ + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window), + GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS); + /* The dialog window is created with a vbox packed into it. */ + gtk_box_pack_start (GTK_BOX (GTK_DIALOG(window)->vbox), scrolled_window, + TRUE, TRUE, 0); + gtk_widget_show (scrolled_window); + + /* create a table of 10 by 10 squares. */ + table = gtk_table_new (10, 10, FALSE); + + /* set the spacing to 10 on x and 10 on y */ + gtk_table_set_row_spacings (GTK_TABLE (table), 10); + gtk_table_set_col_spacings (GTK_TABLE (table), 10); + + /* pack the table into the scrolled window */ + gtk_container_add (GTK_CONTAINER (scrolled_window), table); + gtk_widget_show (table); + + /* this simply creates a grid of toggle buttons on the table + * to demonstrate the scrolled window. */ + for (i = 0; i < 10; i++) + for (j = 0; j < 10; j++) { + sprintf (buffer, "button (%d,%d)\n", i, j); + button = gtk_toggle_button_new_with_label (buffer); + gtk_table_attach_defaults (GTK_TABLE (table), button, + i, i+1, j, j+1); + gtk_widget_show (button); + } + + /* Add a "close" button to the bottom of the dialog */ + button = gtk_button_new_with_label ("close"); + gtk_signal_connect_object (GTK_OBJECT (button), "clicked", + (GtkSignalFunc) gtk_widget_destroy, + GTK_OBJECT (window)); + + /* this makes it so the button is the default. */ + + GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT); + gtk_box_pack_start (GTK_BOX (GTK_DIALOG (window)->action_area), button, TRUE, TRUE, 0); + + /* This grabs this button to be the default button. Simply hitting + * the "Enter" key will cause this button to activate. */ + gtk_widget_grab_default (button); + gtk_widget_show (button); + + gtk_widget_show (window); + + gtk_main(); + + return(0); +} +</verb></tscreen> +<p> +Try playing with resizing the window. You'll notice how the scrollbars +react. You may also wish to use the gtk_widget_set_usize() call to set the default +size of the window or other widgets. + +<!-- ***************************************************************** --> +<sect> List Widgets +<!-- ***************************************************************** --> + +<p> +The GtkList widget is designed to act as a vertical container for widgets +that should be of the type GtkListItem. + +A GtkList widget has its own window to receive events and it's own +background color which is usualy white. As it is directly derived from a +GtkContainer it can be treated as such by using the GTK_CONTAINER(List) +macro, see the GtkContainer widget for more on this. +One should already be familar whith the usage of a GList and its +related functions g_list_*() to be able to use the GtkList widget to +its fully extends. + +There is one field inside the structure definition of the GtkList widget +that will be of greater interest to us, this is: + +<tscreen><verb> +struct _GtkList +{ + ... + GList *selection; + guint selection_mode; + ... +}; +</verb></tscreen> + +The selection field of a GtkList points to a linked list of all items +that are cureently selected, or `NULL' if the selection is empty. +So to learn about the current selection we read the GTK_LIST()->selection +field, but do not modify it since the internal fields are maintained by +the gtk_list_*() functions. + +The selection_mode of the GtkList determines the selection facilities +of a GtkList and therefore the contents of the GTK_LIST()->selection +field: + +The selection_mode may be one of the following: +<itemize> +<item> GTK_SELECTION_SINGLE - The selection is either `NULL' + or contains a GList* pointer + for a single selected item. + +<item> GTK_SELECTION_BROWSE - The selection is `NULL' if the list + contains no widgets or insensitive + ones only, otherwise it contains + a GList pointer for one GList + structure, and therefore exactly + one list item. + +<item> GTK_SELECTION_MULTIPLE - The selection is `NULL' if no list + items are selected or a GList pointer + for the first selected item. That + in turn points to a GList structure + for the second selected item and so + on. + +<item> GTK_SELECTION_EXTENDED - The selection is always `NULL'. +</itemize> +<p> +The default is GTK_SELECTION_MULTIPLE. + +<!-- ----------------------------------------------------------------- --> +<sect1> Signals +<p> +<tscreen><verb> +void selection_changed (GtkList *LIST) +</verb></tscreen> + +This signal will be invoked whenever a the selection field +of a GtkList has changed. This happens when a child of +the GtkList got selected or unselected. + +<tscreen><verb> +void select_child (GtkList *LIST, GtkWidget *CHILD) +</verb></tscreen> + +This signal is invoked when a child of the GtkList is about +to get selected. This happens mainly on calls to +gtk_list_select_item(), gtk_list_select_child(), button presses +and sometimes indirectly triggered on some else occasions where +children get added to or removed from the GtkList. + +<tscreen><verb> +void unselect_child (GtkList *LIST, GtkWidget *CHILD) +</verb></tscreen> + +This signal is invoked when a child of the GtkList is about +to get unselected. This happens mainly on calls to +gtk_list_unselect_item(), gtk_list_unselect_child(), button presses +and sometimes indirectly triggered on some else occasions where +children get added to or removed from the GtkList. + +<!-- ----------------------------------------------------------------- --> +<sect1> Functions +<p> +<tscreen><verb> +guint gtk_list_get_type (void) +</verb></tscreen> + +Returns the `GtkList' type identifier. + +<tscreen><verb> +GtkWidget* gtk_list_new (void) +</verb></tscreen> + +Create a new `GtkList' object. The new widget is +returned as a pointer to a `GtkWidget' object. +`NULL' is returned on failure. + +<tscreen><verb> +void gtk_list_insert_items (GtkList *LIST, GList *ITEMS, gint POSITION) +</verb></tscreen> + +Insert list items into the LIST, starting at POSITION. +ITEMS is a doubly linked list where each nodes data +pointer is expected to point to a newly created GtkListItem. +The GList nodes of ITEMS are taken over by the LIST. + +<tscreen><verb> +void gtk_list_append_items (GtkList *LIST, GList *ITEMS) +</verb></tscreen> + +Insert list items just like gtk_list_insert_items() at the end +of the LIST. The GList nodes of ITEMS are taken over by the LIST. + +<tscreen><verb> +void gtk_list_prepend_items (GtkList *LIST, GList *ITEMS) +</verb></tscreen> + +Insert list items just like gtk_list_insert_items() at the very +beginning of the LIST. The GList nodes of ITEMS are taken over +by the LIST. + +<tscreen><verb> +void gtk_list_remove_items (GtkList *LIST, GList *ITEMS) +</verb></tscreen> + +Remove list items from the LIST. ITEMS is a doubly linked +list where each nodes data pointer is expected to point to a +direct child of LIST. It is the callers responsibility to make a +call to g_list_free(ITEMS) afterwards. Also the caller has to +destroy the list items himself. + +<tscreen><verb> +void gtk_list_clear_items (GtkList *LIST, gint START, gint END) +</verb></tscreen> + +Remove and destroy list items from the LIST. a widget is affected if +its current position within LIST is in the range specified by START +and END. + +<tscreen><verb> +void gtk_list_select_item (GtkList *LIST, gint ITEM) +</verb></tscreen> + +Invoke the select_child signal for a list item +specified through its current position within LIST. + +<tscreen><verb> +void gtk_list_unselect_item (GtkList *LIST, gint ITEM) +</verb></tscreen> + +Invoke the unselect_child signal for a list item +specified through its current position within LIST. + +<tscreen><verb> +void gtk_list_select_child (GtkList *LIST, GtkWidget *CHILD) +</verb></tscreen> + +Invoke the select_child signal for the specified CHILD. + +<tscreen><verb> +void gtk_list_unselect_child (GtkList *LIST, GtkWidget *CHILD) +</verb></tscreen> + +Invoke the unselect_child signal for the specified CHILD. + +<tscreen><verb> +gint gtk_list_child_position (GtkList *LIST, GtkWidget *CHILD) +</verb></tscreen> + +Return the position of CHILD within LIST. `-1' is returned on failure. + +<tscreen><verb> +void gtk_list_set_selection_mode (GtkList *LIST, GtkSelectionMode MODE) +</verb></tscreen> + +Set LIST to the selection mode MODE wich can be of GTK_SELECTION_SINGLE, +GTK_SELECTION_BROWSE, GTK_SELECTION_MULTIPLE or GTK_SELECTION_EXTENDED. + +<tscreen><verb> +GtkList* GTK_LIST (gpointer OBJ) +</verb></tscreen> + +Cast a generic pointer to `GtkList*'. *Note Standard Macros::, for +more info. + +<tscreen><verb> +GtkListClass* GTK_LIST_CLASS (gpointer CLASS) +</verb></tscreen> + +Cast a generic pointer to `GtkListClass*'. *Note Standard Macros::, +for more info. + +<tscreen><verb> +gint GTK_IS_LIST (gpointer OBJ) +</verb></tscreen> + +Determine if a generic pointer refers to a `GtkList' object. *Note +Standard Macros::, for more info. + +<!-- ----------------------------------------------------------------- --> +<sect1> Example +<p> +Following is an example program that will print out the changes +of the selection of a GtkList, and lets you "arrest" list items +into a prison by selecting them with the rightmost mouse button: + +<tscreen><verb> +/* list.c */ + +/* include the gtk+ header files + * include stdio.h, we need that for the printf() function + */ +#include <gtk/gtk.h> +#include <stdio.h> + +/* this is our data identification string to store + * data in list items + */ +const gchar *list_item_data_key="list_item_data"; + + +/* prototypes for signal handler that we are going to connect + * to the GtkList widget + */ +static void sigh_print_selection (GtkWidget *gtklist, + gpointer func_data); +static void sigh_button_event (GtkWidget *gtklist, + GdkEventButton *event, + GtkWidget *frame); + + +/* main function to set up the user interface */ + +gint main (int argc, gchar *argv[]) +{ + GtkWidget *separator; + GtkWidget *window; + GtkWidget *vbox; + GtkWidget *scrolled_window; + GtkWidget *frame; + GtkWidget *gtklist; + GtkWidget *button; + GtkWidget *list_item; + GList *dlist; + guint i; + gchar buffer[64]; + + + /* initialize gtk+ (and subsequently gdk) */ + + gtk_init(&argc, &argv); + + + /* create a window to put all the widgets in + * connect gtk_main_quit() to the "destroy" event of + * the window to handle window manager close-window-events + */ + window=gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_window_set_title(GTK_WINDOW(window), "GtkList Example"); + gtk_signal_connect(GTK_OBJECT(window), + "destroy", + GTK_SIGNAL_FUNC(gtk_main_quit), + NULL); + + + /* inside the window we need a box to arrange the widgets + * vertically */ + vbox=gtk_vbox_new(FALSE, 5); + gtk_container_border_width(GTK_CONTAINER(vbox), 5); + gtk_container_add(GTK_CONTAINER(window), vbox); + gtk_widget_show(vbox); + + /* this is the scolled window to put the GtkList widget inside */ + scrolled_window=gtk_scrolled_window_new(NULL, NULL); + gtk_widget_set_usize(scrolled_window, 250, 150); + gtk_container_add(GTK_CONTAINER(vbox), scrolled_window); + gtk_widget_show(scrolled_window); + + /* create the GtkList widget + * connect the sigh_print_selection() signal handler + * function to the "selection_changed" signal of the GtkList + * to print out the selected items each time the selection + * has changed */ + gtklist=gtk_list_new(); + gtk_container_add(GTK_CONTAINER(scrolled_window), gtklist); + gtk_widget_show(gtklist); + gtk_signal_connect(GTK_OBJECT(gtklist), + "selection_changed", + GTK_SIGNAL_FUNC(sigh_print_selection), + NULL); + + /* we create a "Prison" to put a list item in ;) + */ + frame=gtk_frame_new("Prison"); + gtk_widget_set_usize(frame, 200, 50); + gtk_container_border_width(GTK_CONTAINER(frame), 5); + gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_OUT); + gtk_container_add(GTK_CONTAINER(vbox), frame); + gtk_widget_show(frame); + + /* connect the sigh_button_event() signal handler to the GtkList + * wich will handle the "arresting" of list items + */ + gtk_signal_connect(GTK_OBJECT(gtklist), + "button_release_event", + GTK_SIGNAL_FUNC(sigh_button_event), + frame); + + /* create a separator + */ + separator=gtk_hseparator_new(); + gtk_container_add(GTK_CONTAINER(vbox), separator); + gtk_widget_show(separator); + + /* finaly create a button and connect it´s "clicked" signal + * to the destroyment of the window + */ + button=gtk_button_new_with_label("Close"); + gtk_container_add(GTK_CONTAINER(vbox), button); + gtk_widget_show(button); + gtk_signal_connect_object(GTK_OBJECT(button), + "clicked", + GTK_SIGNAL_FUNC(gtk_widget_destroy), + GTK_OBJECT(window)); + + + /* now we create 5 list items, each having it´s own + * label and add them to the GtkList using gtk_container_add() + * also we query the text string from the label and + * associate it with the list_item_data_key for each list item + */ + for (i=0; i<5; i++) { + GtkWidget *label; + gchar *string; + + sprintf(buffer, "ListItemContainer with Label #%d", i); + label=gtk_label_new(buffer); + list_item=gtk_list_item_new(); + gtk_container_add(GTK_CONTAINER(list_item), label); + gtk_widget_show(label); + gtk_container_add(GTK_CONTAINER(gtklist), list_item); + gtk_widget_show(list_item); + gtk_label_get(GTK_LABEL(label), &string); + gtk_object_set_data(GTK_OBJECT(list_item), + list_item_data_key, + string); + } + /* here, we are creating another 5 labels, this time + * we use gtk_list_item_new_with_label() for the creation + * we can´t query the text string from the label because + * we don´t have the labels pointer and therefore + * we just associate the list_item_data_key of each + * list item with the same text string + * for adding of the list items we put them all into a doubly + * linked list (GList), and then add them by a single call to + * gtk_list_append_items() + * because we use g_list_prepend() to put the items into the + * doubly linked list, their order will be descending (instead + * of ascending when using g_list_append()) + */ + dlist=NULL; + for (; i<10; i++) { + sprintf(buffer, "List Item with Label %d", i); + list_item=gtk_list_item_new_with_label(buffer); + dlist=g_list_prepend(dlist, list_item); + gtk_widget_show(list_item); + gtk_object_set_data(GTK_OBJECT(list_item), + list_item_data_key, + "ListItem with integrated Label"); + } + gtk_list_append_items(GTK_LIST(gtklist), dlist); + + /* finaly we want to see the window, don´t we? ;) + */ + gtk_widget_show(window); + + /* fire up the main event loop of gtk + */ + gtk_main(); + + /* we get here after gtk_main_quit() has been called which + * happens if the main window gets destroyed + */ + return 0; +} + +/* this is the signal handler that got connected to button + * press/release events of the GtkList + */ +void +sigh_button_event (GtkWidget *gtklist, + GdkEventButton *event, + GtkWidget *frame) +{ + /* we only do something if the third (rightmost mouse button + * was released + */ + if (event->type==GDK_BUTTON_RELEASE && + event->button==3) { + GList *dlist, *free_list; + GtkWidget *new_prisoner; + + /* fetch the currently selected list item which + * will be our next prisoner ;) + */ + dlist=GTK_LIST(gtklist)->selection; + if (dlist) + new_prisoner=GTK_WIDGET(dlist->data); + else + new_prisoner=NULL; + + /* look for already prisoned list items, we + * will put them back into the list + * remember to free the doubly linked list that + * gtk_container_children() returns + */ + dlist=gtk_container_children(GTK_CONTAINER(frame)); + free_list=dlist; + while (dlist) { + GtkWidget *list_item; + + list_item=dlist->data; + + gtk_widget_reparent(list_item, gtklist); + + dlist=dlist->next; + } + g_list_free(free_list); + + /* if we have a new prisoner, remove him from the + * GtkList and put him into the frame "Prison" + * we need to unselect the item before + */ + if (new_prisoner) { + GList static_dlist; + + static_dlist.data=new_prisoner; + static_dlist.next=NULL; + static_dlist.prev=NULL; + + gtk_list_unselect_child(GTK_LIST(gtklist), + new_prisoner); + gtk_widget_reparent(new_prisoner, frame); + } + } +} + +/* this is the signal handler that gets called if GtkList + * emits the "selection_changed" signal + */ +void +sigh_print_selection (GtkWidget *gtklist, + gpointer func_data) +{ + GList *dlist; + + /* fetch the doubly linked list of selected items + * of the GtkList, remember to treat this as read-only! + */ + dlist=GTK_LIST(gtklist)->selection; + + /* if there are no selected items there is nothing more + * to do than just telling the user so + */ + if (!dlist) { + g_print("Selection cleared\n"); + return; + } + /* ok, we got a selection and so we print it + */ + g_print("The selection is a "); + + /* get the list item from the doubly linked list + * and then query the data associated with list_item_data_key + * we then just print it + */ + while (dlist) { + GtkObject *list_item; + gchar *item_data_string; + + list_item=GTK_OBJECT(dlist->data); + item_data_string=gtk_object_get_data(list_item, + list_item_data_key); + g_print("%s ", item_data_string); + + dlist=dlist->next; + } + g_print("\n"); +} +</verb></tscreen> + +<!-- ----------------------------------------------------------------- --> +<sect1> List Item Widget +<p> +The GtkListItem widget is designed to act as a container holding up +to one child, providing functions for selection/deselection just like +the GtkList widget requires them for its children. + +A GtkListItem has its own window to receive events and has its own +background color which is usualy white. + +As it is directly derived from a +GtkItem it can be treated as such by using the GTK_ITEM(ListItem) +macro, see the GtkItem widget for more on this. +Usualy a GtkListItem just holds a label to identify e.g. a filename +within a GtkList -- therefore the convenient function +gtk_list_item_new_with_label() is provided. The same effect can be +achieved by creating a GtkLabel on its own, setting its alignment +to xalign=0 and yalign=0.5 with a subsequent container addition +to the GtkListItem. + +As one is not forced to add a GtkLabel to a GtkListItem, you could +also add a GtkVBox or a GtkArrow etc. to the GtkListItem. + +<!-- ----------------------------------------------------------------- --> +<sect1> Signals +<p> +A GtkListItem does not create new signals on its own, but inherits +the signals of a GtkItem. *Note GtkItem::, for more info. + +<!-- ----------------------------------------------------------------- --> +<sect1> Functions +<p> + +<tscreen><verb> +guint gtk_list_item_get_type (void) +</verb></tscreen> + +Returns the `GtkListItem' type identifier. + +<tscreen><verb> +GtkWidget* gtk_list_item_new (void) +</verb></tscreen> + +Create a new `GtkListItem' object. The new widget is +returned as a pointer to a `GtkWidget' object. +`NULL' is returned on failure. + +<tscreen><verb> +GtkWidget* gtk_list_item_new_with_label (gchar *LABEL) +</verb></tscreen> + +Create a new `GtkListItem' object, having a single GtkLabel as +the sole child. The new widget is returned as a pointer to a +`GtkWidget' object. +`NULL' is returned on failure. + +<tscreen><verb> +void gtk_list_item_select (GtkListItem *LIST_ITEM) +</verb></tscreen> + +This function is basicaly a wrapper around a call to +gtk_item_select (GTK_ITEM (list_item)) which will emit the +select signal. +*Note GtkItem::, for more info. + +<tscreen><verb> +void gtk_list_item_deselect (GtkListItem *LIST_ITEM) +</verb></tscreen> + +This function is basicaly a wrapper around a call to +gtk_item_deselect (GTK_ITEM (list_item)) which will emit the +deselect signal. +*Note GtkItem::, for more info. + +<tscreen><verb> +GtkListItem* GTK_LIST_ITEM (gpointer OBJ) +</verb></tscreen> + +Cast a generic pointer to `GtkListItem*'. *Note Standard Macros::, +for more info. + +<tscreen><verb> +GtkListItemClass* GTK_LIST_ITEM_CLASS (gpointer CLASS) +</verb></tscreen> + +Cast a generic pointer to `GtkListItemClass*'. *Note Standard +Macros::, for more info. + +<tscreen><verb> +gint GTK_IS_LIST_ITEM (gpointer OBJ) +</verb></tscreen> + +Determine if a generic pointer refers to a `GtkListItem' object. +*Note Standard Macros::, for more info. + +<!-- ----------------------------------------------------------------- --> +<sect1> Example +<p> +Please see the GtkList example on this, which covers the usage of a +GtkListItem as well. + +<!-- ***************************************************************** --> +<sect> File Selections +<!-- ***************************************************************** --> +<p> +The file selection widget is a quick and simple way to display a File +dialog box. It comes complete with Ok, Cancel, and Help buttons, a great way +to cut down on programming time. + +To create a new file selection box use: + +<tscreen><verb> +GtkWidget* gtk_file_selection_new (gchar *title); +</verb></tscreen> + +To set the filename, for example to bring up a specific directory, or +give a default filename, use this function: + +<tscreen><verb> +void gtk_file_selection_set_filename (GtkFileSelection *filesel, gchar *filename); +</verb></tscreen> + +To grab the text that the user has entered or clicked on, use this +function: + +<tscreen><verb> +gchar* gtk_file_selection_get_filename (GtkFileSelection *filesel); +</verb></tscreen> + +There are also pointers to the widgets contained within the file +selection widget. These are: + +<itemize> +<item>dir_list +<item>file_list +<item>selection_entry +<item>selection_text +<item>main_vbox +<item>ok_button +<item>cancel_button +<item>help_button +</itemize> + +Most likely you will want to use the ok_button, cancel_button, and +help_button pointers in signaling their use. + +Included here is an example stolen from testgtk.c, modified to run +on it's own. As you will see, there is nothing much to creating a file +selection widget. While, in this example, the Help button appears on the +screen, it does nothing as there is not a signal attached to it. + +<tscreen><verb> +/* filesel.c */ + +#include <gtk/gtk.h> + +/* Get the selected filename and print it to the console */ +void file_ok_sel (GtkWidget *w, GtkFileSelection *fs) +{ + g_print ("%s\n", gtk_file_selection_get_filename (GTK_FILE_SELECTION (fs))); +} + +void destroy (GtkWidget *widget, gpointer *data) +{ + gtk_main_quit (); +} + +int main (int argc, char *argv[]) +{ + GtkWidget *filew; + + gtk_init (&argc, &argv); + + /* Create a new file selection widget */ + filew = gtk_file_selection_new ("File selection"); + + gtk_signal_connect (GTK_OBJECT (filew), "destroy", + (GtkSignalFunc) destroy, &filew); + /* Connect the ok_button to file_ok_sel function */ + gtk_signal_connect (GTK_OBJECT (GTK_FILE_SELECTION (filew)->ok_button), + "clicked", (GtkSignalFunc) file_ok_sel, filew ); + + /* Connect the cancel_button to destroy the widget */ + gtk_signal_connect_object (GTK_OBJECT (GTK_FILE_SELECTION (filew)->cancel_button), + "clicked", (GtkSignalFunc) gtk_widget_destroy, + GTK_OBJECT (filew)); + + /* Lets set the filename, as if this were a save dialog, and we are giving + a default filename */ + gtk_file_selection_set_filename (GTK_FILE_SELECTION(filew), + "penguin.png"); + + gtk_widget_show(filew); + gtk_main (); + return 0; +} +</verb></tscreen> + +<!-- ***************************************************************** --> +<sect>Menu Widgets +<!-- ***************************************************************** --> + +<p> +There are two ways to create menus, there's the easy way, and there's the +hard way. Both have their uses, but you can usually use the menufactory +(the easy way). The "hard" way is to create all the menus using the calls +directly. The easy way is to use the gtk_menu_factory calls. This is +much simpler, but there are advantages and disadvantages to each approach. + +The menufactory is much easier to use, and to add new menus to, although +writing a few wrapper functions to create menus using the manual method +could go a long way towards usability. With the menufactory, it is not +possible to add images or the character '/' to the menus. + +<!-- ----------------------------------------------------------------- --> +<sect1>Manual Menu Creation +<p> +In the true tradition of teaching, we'll show you the hard +way first. <tt>:)</> +<p> +There are three widgets that go into making a menubar and submenus: +<itemize> +<item>a menu item, which is what the user wants to select, e.g. 'Save' +<item>a menu, which acts as a container for the menu items, and +<item>a menubar, which is a container for each of the individual menus, +</itemize> + +This is slightly complicated by the fact that menu item widgets are used for two different things. They are +both the widets that are packed into the menu, and the widget that is packed into the menubar, which, +when selected, activiates the menu. + +Let's look at the functions that are used to create menus and menubars. +This first function is used to create a new menubar. + +<tscreen><verb> +GtkWidget *gtk_menu_bar_new(void); +</verb></tscreen> + +This rather self explanatory function creates a new menubar. You use +gtk_container_add to pack this into a window, or the box_pack functions to +pack it into a box - the same as buttons. + +<tscreen><verb> +GtkWidget *gtk_menu_new(); +</verb></tscreen> + +This function returns a pointer to a new menu, it is never actually shown +(with gtk_widget_show), it is just a container for the menu items. Hopefully this will +become more clear when you look at the example below. +<p> +The next two calls are used to create menu items that are packed into +the menu (and menubar). + +<tscreen><verb> +GtkWidget *gtk_menu_item_new(); +</verb></tscreen> + +and + +<tscreen><verb> +GtkWidget *gtk_menu_item_new_with_label(const char *label); +</verb></tscreen> + +These calls are used to create the menu items that are to be displayed. +Remember to differentiate between a "menu" as created with gtk_menu_new +and a "menu item" as created by the gtk_menu_item_new functions. The +menu item will be an actual button with an associated action, +whereas a menu will be a container holding menu items. + +The gtk_menu_new_with_label and gtk_menu_new functions are just as you'd expect after +reading about the buttons. One creates a new menu item with a label +already packed into it, and the other just creates a blank menu item. + +Once you've created a menu item you have to put it into a menu. This is done using the function +gtk_menu_append. In order to capture when the item is selected by the user, we need to connect +to the <tt/activate/ signal in the usual way. +So, if we wanted to create a standard <tt/File/ menu, with the options <tt/Open/, +<tt/Save/ and <tt/Quit/ the code would look something like + +<tscreen><verb> +file_menu = gtk_menu_new(); /* Don't need to show menus */ + +/* Create the menu items */ +open_item = gtk_menu_item_new_with_label("Open"); +save_item = gtk_menu_item_new_with_label("Save"); +quit_item = gtk_menu_item_new_with_label("Quit"); + +/* Add them to the menu */ +gtk_menu_append( GTK_MENU(file_menu), open_item); +gtk_menu_append( GTK_MENU(file_menu), save_item); +gtk_menu_append( GTK_MENU(file_menu), quit_item); + +/* Attach the callback functions to the activate signal */ +gtk_signal_connect_object( GTK_OBJECT(open_items), "activate", + GTK_SIGNAL_FUNC(menuitem_response), (gpointer) "file.open"); +gtk_signal_connect_object( GTK_OBJECT(save_items), "activate", + GTK_SIGNAL_FUNC(menuitem_response), (gpointer) "file.save"); + +/* We can attach the Quit menu item to our exit function */ +gtk_signal_connect_object( GTK_OBJECT(quit_items), "activate", + GTK_SIGNAL_FUNC(destroy), (gpointer) "file.quit"); + +/* We do need to show menu items */ +gtk_widget_show( open_item ); +gtk_widget_show( save_item ); +gtk_widget_show( quit_item ); +</verb></tscreen> + +At this point we have our menu. Now we need to create a menubar and a menu item for the <tt/File/ entry, +to which we add our menu. The code looks like this + +<tscreen><verb> +menu_bar = gtk_menu_bar_new(); +gtk_container_add( GTK_CONTAINER(window), menu_bar); +gtk_widget_show( menu_bar ); + +file_item = gtk_menu_item_new_with_label("File"); +gtk_widget_show(file_item); +</verb></tscreen> + +Now we need to associate the menu with <tt/file_item/. This is done with the function + +<tscreen> +void gtk_menu_item_set_submenu( GtkMenuItem *menu_item, + GtkWidget *submenu); +</tscreen> + +So, our example would continue with + +<tscreen><verb> +gtk_menu_item_set_submenu( GTK_MENU_ITEM(file_item), file_menu); +</verb></tscreen> + +All that is left to do is to add the menu to the menubar, which is accomplished using the function + +<tscreen> +void gtk_menu_bar_append( GtkMenuBar *menu_bar, GtkWidget *menu_item); +</tscreen> + +which in our case looks like this: + +<tscreen><verb> +gtk_menu_bar_append( menu_bar, file_item ); +</verb></tscreen> + +If we wanted the menu right justified on the menubar, such as help menus often are, we can +use the following function (again on <tt/file_item/ in the current example) before attaching +it to the menubar. +<tscreen><verb> +void gtk_menu_item_right_justify (GtkMenuItem *menu_item); +</verb></tscreen> + +Here is a summary of the steps needed to create a menu bar with menus attached: +<itemize> +<item> Create a new menu using gtk_menu_new() +<item> Use multiple calls to gtk_menu_item_new() for each item you wish to have on + your menu. And use gtk_menu_append() to put each of these new items on + to the menu. +<item> Create a menu item using gtk_menu_item_new(). This will be the root of + the menu, the text appearing here will be on the menubar itself. +<item> Use gtk_menu_item_set_submenu() to attach the menu to + the root menu item (The one created in the above step). +<item> Create a new menubar using gtk_menu_bar_new. This step only needs + to be done once when creating a series of menus on one menu bar. +<item> Use gtk_menu_bar_append to put the root menu onto the menubar. +</itemize> +<p> +Creating a popup menu is nearly the same. The difference is that the +menu is not posted `automatically' by a menubar, but explicitly +by calling the function gtk_menu_popup() from a button-press event, for example. +Take these steps: +<itemize> +<item>Create an event handling function. It needs to have the prototype +<tscreen> +static gint handler(GtkWidget *widget, GdkEvent *event); +</tscreen> +and it will use the event to find out where to pop up the menu. +<item>In the event handler, if event is a mouse button press, treat +<tt>event</tt> as a button event (which it is) and use it as +shown in the sample code to pass information to gtk_menu_popup(). +<item>Bind that event handler to a widget with +<tscreen> +gtk_signal_connect_object(GTK_OBJECT(widget), "event", + GTK_SIGNAL_FUNC (handler), GTK_OBJECT(menu)); +</tscreen> +where <tt>widget</tt> is the widget you are binding to, <tt>handler</tt> +is the handling function, and <tt>menu</tt> is a menu created with +gtk_menu_new(). This can be a menu which is also posted by a menu bar, +as shown in the sample code. +</itemize> + +<!-- ----------------------------------------------------------------- --> +<sect1>Manual Menu Example +<p> +That should about do it. Let's take a look at an example to help clarify. + +<tscreen><verb> +/* menu.c */ + +#include <gtk/gtk.h> + +static gint button_press (GtkWidget *, GdkEvent *); +static void menuitem_response (gchar *); + +int main (int argc, char *argv[]) +{ + + GtkWidget *window; + GtkWidget *menu; + GtkWidget *menu_bar; + GtkWidget *root_menu; + GtkWidget *menu_items; + GtkWidget *vbox; + GtkWidget *button; + char buf[128]; + int i; + + gtk_init (&argc, &argv); + + /* create a new window */ + window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_widget_set_usize( GTK_WIDGET (window), 200, 100); + gtk_window_set_title(GTK_WINDOW (window), "GTK Menu Test"); + gtk_signal_connect(GTK_OBJECT (window), "delete_event", + (GtkSignalFunc) gtk_exit, NULL); + + /* Init the menu-widget, and remember -- never + * gtk_show_widget() the menu widget!! + * This is the menu that holds the menu items, the one that + * will pop up when you click on the "Root Menu" in the app */ + menu = gtk_menu_new(); + + /* Next we make a little loop that makes three menu-entries for "test-menu". + * Notice the call to gtk_menu_append. Here we are adding a list of + * menu items to our menu. Normally, we'd also catch the "clicked" + * signal on each of the menu items and setup a callback for it, + * but it's omitted here to save space. */ + + for(i = 0; i < 3; i++) + { + /* Copy the names to the buf. */ + sprintf(buf, "Test-undermenu - %d", i); + + /* Create a new menu-item with a name... */ + menu_items = gtk_menu_item_new_with_label(buf); + + /* ...and add it to the menu. */ + gtk_menu_append(GTK_MENU (menu), menu_items); + + /* Do something interesting when the menuitem is selected */ + gtk_signal_connect_object(GTK_OBJECT(menu_items), "activate", + GTK_SIGNAL_FUNC(menuitem_response), (gpointer) g_strdup(buf)); + + /* Show the widget */ + gtk_widget_show(menu_items); + } + + /* This is the root menu, and will be the label + * displayed on the menu bar. There won't be a signal handler attached, + * as it only pops up the rest of the menu when pressed. */ + root_menu = gtk_menu_item_new_with_label("Root Menu"); + + gtk_widget_show(root_menu); + + /* Now we specify that we want our newly created "menu" to be the menu + * for the "root menu" */ + gtk_menu_item_set_submenu(GTK_MENU_ITEM (root_menu), menu); + + /* A vbox to put a menu and a button in: */ + vbox = gtk_vbox_new(FALSE, 0); + gtk_container_add(GTK_CONTAINER(window), vbox); + gtk_widget_show(vbox); + + /* Create a menu-bar to hold the menus and add it to our main window */ + menu_bar = gtk_menu_bar_new(); + gtk_box_pack_start(GTK_BOX(vbox), menu_bar, FALSE, FALSE, 2); + gtk_widget_show(menu_bar); + + /* Create a button to which to attach menu as a popup */ + button = gtk_button_new_with_label("press me"); + gtk_signal_connect_object(GTK_OBJECT(button), "event", + GTK_SIGNAL_FUNC (button_press), GTK_OBJECT(menu)); + gtk_box_pack_end(GTK_BOX(vbox), button, TRUE, TRUE, 2); + gtk_widget_show(button); + + /* And finally we append the menu-item to the menu-bar -- this is the + * "root" menu-item I have been raving about =) */ + gtk_menu_bar_append(GTK_MENU_BAR (menu_bar), root_menu); + + /* always display the window as the last step so it all splashes on + * the screen at once. */ + gtk_widget_show(window); + + gtk_main (); + + return 0; +} + + + +/* Respond to a button-press by posting a menu passed in as widget. + * + * Note that the "widget" argument is the menu being posted, NOT + * the button that was pressed. + */ + +static gint button_press (GtkWidget *widget, GdkEvent *event) +{ + + if (event->type == GDK_BUTTON_PRESS) { + GdkEventButton *bevent = (GdkEventButton *) event; + gtk_menu_popup (GTK_MENU(widget), NULL, NULL, NULL, NULL, + bevent->button, bevent->time); + /* Tell calling code that we have handled this event; the buck + * stops here. */ + return TRUE; + } + + /* Tell calling code that we have not handled this event; pass it on. */ + return FALSE; +} + + +/* Print a string when a menu item is selected */ + +static void menuitem_response (gchar *string) +{ + printf("%s\n", string); +} +</verb></tscreen> + +You may also set a menu item to be insensitive and, using an accelerator +table, bind keys to menu functions. + +<!-- ----------------------------------------------------------------- --> +<sect1>Using GtkMenuFactory +<p> +Now that we've shown you the hard way, here's how you do it using the +gtk_menu_factory calls. + +<!-- ----------------------------------------------------------------- --> +<sect1>Menu Factory Example +<p> +Here is an example using the GTK menu factory. This is the first file, +menufactory.h. We keep a separate menufactory.c and mfmain.c because of the global variables used +in the menufactory.c file. + +<tscreen><verb> +/* menufactory.h */ + +#ifndef __MENUFACTORY_H__ +#define __MENUFACTORY_H__ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +void get_main_menu (GtkWidget **menubar, GtkAcceleratorTable **table); +void menus_create(GtkMenuEntry *entries, int nmenu_entries); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __MENUFACTORY_H__ */ +</verb></tscreen> +<p> +And here is the menufactory.c file. + +<tscreen><verb> +/* menufactory.c */ + +#include <gtk/gtk.h> +#include <strings.h> + +#include "mfmain.h" + + +static void menus_remove_accel(GtkWidget * widget, gchar * signal_name, gchar * path); +static gint menus_install_accel(GtkWidget * widget, gchar * signal_name, gchar key, gchar modifiers, gchar * path); +void menus_init(void); +void menus_create(GtkMenuEntry * entries, int nmenu_entries); + + +/* this is the GtkMenuEntry structure used to create new menus. The + * first member is the menu definition string. The second, the + * default accelerator key used to access this menu function with + * the keyboard. The third is the callback function to call when + * this menu item is selected (by the accelerator key, or with the + * mouse.) The last member is the data to pass to your callback function. + */ + +static GtkMenuEntry menu_items[] = +{ + {"<Main>/File/New", "<control>N", NULL, NULL}, + {"<Main>/File/Open", "<control>O", NULL, NULL}, + {"<Main>/File/Save", "<control>S", NULL, NULL}, + {"<Main>/File/Save as", NULL, NULL, NULL}, + {"<Main>/File/<separator>", NULL, NULL, NULL}, + {"<Main>/File/Quit", "<control>Q", file_quit_cmd_callback, "OK, I'll quit"}, + {"<Main>/Options/Test", NULL, NULL, NULL} +}; + +/* calculate the number of menu_item's */ +static int nmenu_items = sizeof(menu_items) / sizeof(menu_items[0]); + +static int initialize = TRUE; +static GtkMenuFactory *factory = NULL; +static GtkMenuFactory *subfactory[1]; +static GHashTable *entry_ht = NULL; + +void get_main_menu(GtkWidget ** menubar, GtkAcceleratorTable ** table) +{ + if (initialize) + menus_init(); + + if (menubar) + *menubar = subfactory[0]->widget; + if (table) + *table = subfactory[0]->table; +} + +void menus_init(void) +{ + if (initialize) { + initialize = FALSE; + + factory = gtk_menu_factory_new(GTK_MENU_FACTORY_MENU_BAR); + subfactory[0] = gtk_menu_factory_new(GTK_MENU_FACTORY_MENU_BAR); + + gtk_menu_factory_add_subfactory(factory, subfactory[0], "<Main>"); + menus_create(menu_items, nmenu_items); + } +} + +void menus_create(GtkMenuEntry * entries, int nmenu_entries) +{ + char *accelerator; + int i; + + if (initialize) + menus_init(); + + if (entry_ht) + for (i = 0; i < nmenu_entries; i++) { + accelerator = g_hash_table_lookup(entry_ht, entries[i].path); + if (accelerator) { + if (accelerator[0] == '\0') + entries[i].accelerator = NULL; + else + entries[i].accelerator = accelerator; + } + } + gtk_menu_factory_add_entries(factory, entries, nmenu_entries); + + for (i = 0; i < nmenu_entries; i++) + if (entries[i].widget) { + gtk_signal_connect(GTK_OBJECT(entries[i].widget), "install_accelerator", + (GtkSignalFunc) menus_install_accel, + entries[i].path); + gtk_signal_connect(GTK_OBJECT(entries[i].widget), "remove_accelerator", + (GtkSignalFunc) menus_remove_accel, + entries[i].path); + } +} + +static gint menus_install_accel(GtkWidget * widget, gchar * signal_name, gchar key, gchar modifiers, gchar * path) +{ + char accel[64]; + char *t1, t2[2]; + + accel[0] = '\0'; + if (modifiers & GDK_CONTROL_MASK) + strcat(accel, "<control>"); + if (modifiers & GDK_SHIFT_MASK) + strcat(accel, "<shift>"); + if (modifiers & GDK_MOD1_MASK) + strcat(accel, "<alt>"); + + t2[0] = key; + t2[1] = '\0'; + strcat(accel, t2); + + if (entry_ht) { + t1 = g_hash_table_lookup(entry_ht, path); + g_free(t1); + } else + entry_ht = g_hash_table_new(g_str_hash, g_str_equal); + + g_hash_table_insert(entry_ht, path, g_strdup(accel)); + + return TRUE; +} + +static void menus_remove_accel(GtkWidget * widget, gchar * signal_name, gchar * path) +{ + char *t; + + if (entry_ht) { + t = g_hash_table_lookup(entry_ht, path); + g_free(t); + + g_hash_table_insert(entry_ht, path, g_strdup("")); + } +} + +void menus_set_sensitive(char *path, int sensitive) +{ + GtkMenuPath *menu_path; + + if (initialize) + menus_init(); + + menu_path = gtk_menu_factory_find(factory, path); + if (menu_path) + gtk_widget_set_sensitive(menu_path->widget, sensitive); + else + g_warning("Unable to set sensitivity for menu which doesn't exist: %s", path); +} + +</verb></tscreen> +<p> +And here's the mfmain.h + +<tscreen><verb> +/* mfmain.h */ + +#ifndef __MFMAIN_H__ +#define __MFMAIN_H__ + + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +void file_quit_cmd_callback(GtkWidget *widget, gpointer data); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __MFMAIN_H__ */ +</verb></tscreen> +<p> +And mfmain.c + +<tscreen><verb> +/* mfmain.c */ + +#include <gtk/gtk.h> + +#include "mfmain.h" +#include "menufactory.h" + + +int main(int argc, char *argv[]) +{ + GtkWidget *window; + GtkWidget *main_vbox; + GtkWidget *menubar; + + GtkAcceleratorTable *accel; + + gtk_init(&argc, &argv); + + window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_signal_connect(GTK_OBJECT(window), "destroy", + GTK_SIGNAL_FUNC(file_quit_cmd_callback), + "WM destroy"); + gtk_window_set_title(GTK_WINDOW(window), "Menu Factory"); + gtk_widget_set_usize(GTK_WIDGET(window), 300, 200); + + main_vbox = gtk_vbox_new(FALSE, 1); + gtk_container_border_width(GTK_CONTAINER(main_vbox), 1); + gtk_container_add(GTK_CONTAINER(window), main_vbox); + gtk_widget_show(main_vbox); + + get_main_menu(&menubar, &accel); + gtk_window_add_accelerator_table(GTK_WINDOW(window), accel); + gtk_box_pack_start(GTK_BOX(main_vbox), menubar, FALSE, TRUE, 0); + gtk_widget_show(menubar); + + gtk_widget_show(window); + gtk_main(); + + return(0); +} + +/* This is just to demonstrate how callbacks work when using the + * menufactory. Often, people put all the callbacks from the menus + * in a separate file, and then have them call the appropriate functions + * from there. Keeps it more organized. */ +void file_quit_cmd_callback (GtkWidget *widget, gpointer data) +{ + g_print ("%s\n", (char *) data); + gtk_exit(0); +} +</verb></tscreen> +<p> +And a makefile so it'll be easier to compile it. + +<tscreen><verb> +# Makefile.mf + +CC = gcc +PROF = -g +C_FLAGS = -Wall $(PROF) -L/usr/local/include -DDEBUG +L_FLAGS = $(PROF) -L/usr/X11R6/lib -L/usr/local/lib +L_POSTFLAGS = -lgtk -lgdk -lglib -lXext -lX11 -lm +PROGNAME = menufactory + +O_FILES = menufactory.o mfmain.o + +$(PROGNAME): $(O_FILES) + rm -f $(PROGNAME) + $(CC) $(L_FLAGS) -o $(PROGNAME) $(O_FILES) $(L_POSTFLAGS) + +.c.o: + $(CC) -c $(C_FLAGS) $< + +clean: + rm -f core *.o $(PROGNAME) nohup.out +distclean: clean + rm -f *~ +</verb></tscreen> +<p> +For now, there's only this example. An explanation and lots 'o' comments +will follow later. + +<!-- ***************************************************************** --> +<sect> Undocumented Widgets +<!-- ***************************************************************** --> + +<p> +These all require authors! :) Please consider contributing to our tutorial. + +If you must use one of these widgets that are undocumented, I strongly +suggest you take a look at their respective header files in the GTK distro. +GTK's function names are very descriptive. Once you have an understanding +of how things work, it's not easy to figure out how to use a widget simply +by looking at it's function declarations. This, along with a few examples +from others' code, and it should be no problem. + +When you do come to understand all the functions of a new undocumented +widget, please consider writing a tutorial on it so others may benifit from +your time. + +<!-- ----------------------------------------------------------------- --> +<sect1> Color Selections + +<!-- ----------------------------------------------------------------- --> +<sect1> Range Controls + +<!-- ----------------------------------------------------------------- --> +<sect1> Text Boxes +<p> + +<!-- ----------------------------------------------------------------- --> +<sect1> Previews +<p> + +(This may need to be rewritten to follow the style of the rest of the tutorial) + +<tscreen><verb> + +Previews serve a number of purposes in GIMP/GTK. The most important one is +this. High quality images may take up to tens of megabytes of memory - easy! +Any operation on an image that big is bound to take a long time. If it takes +you 5-10 trial-and-errors (i.e. 10-20 steps, since you have to revert after +you make an error) to choose the desired modification, it make take you +literally hours to make the right one - if you don't run out of memory +first. People who have spent hours in color darkrooms know the feeling. +Previews to the rescue! + +But the annoyance of the delay is not the only issue. Oftentimes it is +helpful to compare the Before and After versions side-by-side or at least +back-to-back. If you're working with big images and 10 second delays, +obtaining the Before and After impressions is, to say the least, difficult. +For 30M images (4"x6", 600dpi, 24 bit) the side-by-side comparison is right +out for most people, while back-to-back is more like back-to-1001, 1002, +..., 1010-back! Previews to the rescue! + +But there's more. Previews allow for side-by-side pre-previews. In other +words, you write a plug-in (e.g. the filterpack simulation) which would have +a number of here's-what-it-would-look-like-if-you-were-to-do-this previews. +An approach like this acts as a sort of a preview palette and is very +effective fow subtle changes. Let's go previews! + +There's more. For certain plug-ins real-time image-specific human +intervention maybe necessary. In the SuperNova plug-in, for example, the +user is asked to enter the coordinates of the center of the future +supernova. The easiest way to do this, really, is to present the user with a +preview and ask him to intereactively select the spot. Let's go previews! + +Finally, a couple of misc uses. One can use previews even when not working +with big images. For example, they are useful when rendering compicated +patterns. (Just check out the venerable Diffraction plug-in + many other +ones!) As another example, take a look at the colormap rotation plug-in +(work in progress). You can also use previews for little logo's inside you +plug-ins and even for an image of yourself, The Author. Let's go previews! + +When Not to Use Previews + +Don't use previews for graphs, drawing etc. GDK is much faster for that. Use +previews only for rendered images! + +Let's go previews! + +You can stick a preview into just about anything. In a vbox, an hbox, a +table, a button, etc. But they look their best in tight frames around them. +Previews by themselves do not have borders and look flat without them. (Of +course, if the flat look is what you want...) Tight frames provide the +necessary borders. + + [Image][Image] + +Previews in many ways are like any other widgets in GTK (whatever that +means) except they possess an addtional feature: they need to be filled with +some sort of an image! First, we will deal exclusively with the GTK aspect +of previews and then we'll discuss how to fill them. + +GtkWidget *preview! + +Without any ado: + + /* Create a preview widget, + set its size, an show it */ +GtkWidget *preview; +preview=gtk_preview_new(GTK_PREVIEW_COLOR) + /*Other option: + GTK_PREVIEW_GRAYSCALE);*/ +gtk_preview_size (GTK_PREVIEW (preview), WIDTH, HEIGHT); +gtk_widget_show(preview); +my_preview_rendering_function(preview); + +Oh yeah, like I said, previews look good inside frames, so how about: + +GtkWidget *create_a_preview(int Width, + int Height, + int Colorfulness) +{ + GtkWidget *preview; + GtkWidget *frame; + + frame = gtk_frame_new(NULL); + gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN); + gtk_container_border_width (GTK_CONTAINER(frame),0); + gtk_widget_show(frame); + + preview=gtk_preview_new (Colorfulness?GTK_PREVIEW_COLOR + :GTK_PREVIEW_GRAYSCALE); + gtk_preview_size (GTK_PREVIEW (preview), Width, Height); + gtk_container_add(GTK_CONTAINER(frame),preview); + gtk_widget_show(preview); + + my_preview_rendering_function(preview); + return frame; +} + +That's my basic preview. This routine returns the "parent" frame so you can +place it somewhere else in your interface. Of course, you can pass the +parent frame to this routine as a parameter. In many situations, however, +the contents of the preview are changed continually by your application. In +this case you may want to pass a pointer to the preview to a +"create_a_preview()" and thus have control of it later. + +One more important note that may one day save you a lot of time. Sometimes +it is desirable to label you preview. For example, you may label the preview +containing the original image as "Original" and the one containing the +modified image as "Less Original". It might occure to you to pack the +preview along with the appropriate label into a vbox. The unexpected caveat +is that if the label is wider than the preview (which may happen for a +variety of reasons unforseeable to you, from the dynamic decision on the +size of the preview to the size of the font) the frame expands and no longer +fits tightly over the preview. The same problem can probably arise in other +situations as well. + + [Image] + +The solution is to place the preview and the label into a 2x1 table and by +attaching them with the following paramters (this is one possible variations +of course. The key is no GTK_FILL in the second attachment): + +gtk_table_attach(GTK_TABLE(table),label,0,1,0,1, + 0, + GTK_EXPAND|GTK_FILL, + 0,0); +gtk_table_attach(GTK_TABLE(table),frame,0,1,1,2, + GTK_EXPAND, + GTK_EXPAND, + 0,0); + + +And here's the result: + + [Image] + +Misc + +Making a preview clickable is achieved most easily by placing it in a +button. It also adds a nice border around the preview and you may not even +need to place it in a frame. See the Filter Pack Simulation plug-in for an +example. + +This is pretty much it as far as GTK is concerned. + +Filling In a Preview + +In order to familiarize ourselves with the basics of filling in previews, +let's create the following pattern (contrived by trial and error): + + [Image] + +void +my_preview_rendering_function(GtkWidget *preview) +{ +#define SIZE 100 +#define HALF (SIZE/2) + + guchar *row=(guchar *) malloc(3*SIZE); /* 3 bits per dot */ + gint i, j; /* Coordinates */ + double r, alpha, x, y; + + if (preview==NULL) return; /* I usually add this when I want */ + /* to avoid silly crashes. You */ + /* should probably make sure that */ + /* everything has been nicely */ + /* initialized! */ + for (j=0; j < ABS(cos(2*alpha)) ) { /* Are we inside the shape? */ + /* glib.h contains ABS(x). */ + row[i*3+0] = sqrt(1-r)*255; /* Define Red */ + row[i*3+1] = 128; /* Define Green */ + row[i*3+2] = 224; /* Define Blue */ + } /* "+0" is for alignment! */ + else { + row[i*3+0] = r*255; + row[i*3+1] = ABS(sin((float)i/SIZE*2*PI))*255; + row[i*3+2] = ABS(sin((float)j/SIZE*2*PI))*255; + } + } + gtk_preview_draw_row( GTK_PREVIEW(preview),row,0,j,SIZE); + /* Insert "row" into "preview" starting at the point with */ + /* coordinates (0,j) first column, j_th row extending SIZE */ + /* pixels to the right */ + } + + free(row); /* save some space */ + gtk_widget_draw(preview,NULL); /* what does this do? */ + gdk_flush(); /* or this? */ +} + +Non-GIMP users can have probably seen enough to do a lot of things already. +For the GIMP users I have a few pointers to add. + +Image Preview + +It is probably wize to keep a reduced version of the image around with just +enough pixels to fill the preview. This is done by selecting every n'th +pixel where n is the ratio of the size of the image to the size of the +preview. All further operations (including filling in the previews) are then +performed on the reduced number of pixels only. The following is my +implementation of reducing the image. (Keep in mind that I've had only basic +C!) + +(UNTESTED CODE ALERT!!!) + +typedef struct { + gint width; + gint height; + gint bbp; + guchar *rgb; + guchar *mask; +} ReducedImage; + +enum { + SELECTION_ONLY, + SELCTION_IN_CONTEXT, + ENTIRE_IMAGE +}; + +ReducedImage *Reduce_The_Image(GDrawable *drawable, + GDrawable *mask, + gint LongerSize, + gint Selection) +{ + /* This function reduced the image down to the the selected preview size */ + /* The preview size is determine by LongerSize, i.e. the greater of the */ + /* two dimentions. Works for RGB images only! */ + gint RH, RW; /* Reduced height and reduced width */ + gint width, height; /* Width and Height of the area being reduced */ + gint bytes=drawable->bpp; + ReducedImage *temp=(ReducedImage *)malloc(sizeof(ReducedImage)); + + guchar *tempRGB, *src_row, *tempmask, *src_mask_row,R,G,B; + gint i, j, whichcol, whichrow, x1, x2, y1, y2; + GPixelRgn srcPR, srcMask; + gint NoSelectionMade=TRUE; /* Assume that we're dealing with the entire */ + /* image. */ + + gimp_drawable_mask_bounds (drawable->id, &x1, &y1, &x2, &y2); + width = x2-x1; + height = y2-y1; + /* If there's a SELECTION, we got its bounds!) + + if (width != drawable->width && height != drawable->height) + NoSelectionMade=FALSE; + /* Become aware of whether the user has made an active selection */ + /* This will become important later, when creating a reduced mask. */ + + /* If we want to preview the entire image, overrule the above! */ + /* Of course, if no selection has been made, this does nothing! */ + if (Selection==ENTIRE_IMAGE) { + x1=0; + x2=drawable->width; + y1=0; + y2=drawable->height; + } + + /* If we want to preview a selection with some surronding area we */ + /* have to expand it a little bit. Consider it a bit of a riddle. */ + if (Selection==SELECTION_IN_CONTEXT) { + x1=MAX(0, x1-width/2.0); + x2=MIN(drawable->width, x2+width/2.0); + y1=MAX(0, y1-height/2.0); + y2=MIN(drawable->height, y2+height/2.0); + } + + /* How we can determine the width and the height of the area being */ + /* reduced. */ + width = x2-x1; + height = y2-y1; + + /* The lines below determine which dimension is to be the longer */ + /* side. The idea borrowed from the supernova plug-in. I suspect I */ + /* could've thought of it myself, but the truth must be told. */ + /* Plagiarism stinks! */ + if (width>height) { + RW=LongerSize; + RH=(float) height * (float) LongerSize/ (float) width; + } + else { + RH=LongerSize; + RW=(float)width * (float) LongerSize/ (float) height; + } + + /* The intire image is stretched into a string! */ + tempRGB = (guchar *) malloc(RW*RH*bytes); + tempmask = (guchar *) malloc(RW*RH); + + gimp_pixel_rgn_init (&srcPR, drawable, x1, y1, width, height, FALSE, FALSE); + gimp_pixel_rgn_init (&srcMask, mask, x1, y1, width, height, FALSE, FALSE); + + /* Grab enough to save a row of image and a row of mask. */ + src_row = (guchar *) malloc (width*bytes); + src_mask_row = (guchar *) malloc (width); + + for (i=0; i < RH; i++) { + whichrow=(float)i*(float)height/(float)RH; + gimp_pixel_rgn_get_row (&srcPR, src_row, x1, y1+whichrow, width); + gimp_pixel_rgn_get_row (&srcMask, src_mask_row, x1, y1+whichrow, width); + + for (j=0; j < RW; j++) { + whichcol=(float)j*(float)width/(float)RW; + + /* No selection made = each point is completely selected! */ + if (NoSelectionMade) + tempmask[i*RW+j]=255; + else + tempmask[i*RW+j]=src_mask_row[whichcol]; + + /* Add the row to the one long string which now contains the image! */ + tempRGB[i*RW*bytes+j*bytes+0]=src_row[whichcol*bytes+0]; + tempRGB[i*RW*bytes+j*bytes+1]=src_row[whichcol*bytes+1]; + tempRGB[i*RW*bytes+j*bytes+2]=src_row[whichcol*bytes+2]; + + /* Hold on to the alpha as well */ + if (bytes==4) + tempRGB[i*RW*bytes+j*bytes+3]=src_row[whichcol*bytes+3]; + } + } + temp->bpp=bytes; + temp->width=RW; + temp->height=RH; + temp->rgb=tempRGB; + temp->mask=tempmask; + return temp; +} + +The following is a preview function which used the same ReducedImage type! +Note that it uses fakes transparancy (if one is present by means of +fake_transparancy which is defined as follows: + +gint fake_transparency(gint i, gint j) +{ + if ( ((i%20)- 10) * ((j%20)- 10)>0 ) + return 64; + else + return 196; +} + +Now here's the preview function: + +void +my_preview_render_function(GtkWidget *preview, + gint changewhat, + gint changewhich) +{ + gint Inten, bytes=drawable->bpp; + gint i, j, k; + float partial; + gint RW=reduced->width; + gint RH=reduced->height; + guchar *row=malloc(bytes*RW);; + + + for (i=0; i < RH; i++) { + for (j=0; j < RW; j++) { + + row[j*3+0] = reduced->rgb[i*RW*bytes + j*bytes + 0]; + row[j*3+1] = reduced->rgb[i*RW*bytes + j*bytes + 1]; + row[j*3+2] = reduced->rgb[i*RW*bytes + j*bytes + 2]; + + if (bytes==4) + for (k=0; k<3; k++) { + float transp=reduced->rgb[i*RW*bytes+j*bytes+3]/255.0; + row[3*j+k]=transp*a[3*j+k]+(1-transp)*fake_transparency(i,j); + } + } + gtk_preview_draw_row( GTK_PREVIEW(preview),row,0,i,RW); + } + + free(a); + gtk_widget_draw(preview,NULL); + gdk_flush(); +} + +Applicable Routines + +guint gtk_preview_get_type (void); +/* No idea */ +void gtk_preview_uninit (void); +/* No idea */ +GtkWidget* gtk_preview_new (GtkPreviewType type); +/* Described above */ +void gtk_preview_size (GtkPreview *preview, + gint width, + gint height); +/* Allows you to resize an existing preview. */ +/* Apparantly there's a bug in GTK which makes */ +/* this process messy. A way to clean up a mess */ +/* is to manually resize the window containing */ +/* the preview after resizing the preview. */ + +void gtk_preview_put (GtkPreview *preview, + GdkWindow *window, + GdkGC *gc, + gint srcx, + gint srcy, + gint destx, + gint desty, + gint width, + gint height); +/* No idea */ + +void gtk_preview_put_row (GtkPreview *preview, + guchar *src, + guchar *dest, + gint x, + gint y, + gint w); +/* No idea */ + +void gtk_preview_draw_row (GtkPreview *preview, + guchar *data, + gint x, + gint y, + gint w); +/* Described in the text */ + +void gtk_preview_set_expand (GtkPreview *preview, + gint expand); +/* No idea */ + +/* No clue for any of the below but */ +/* should be standard for most widgets */ +void gtk_preview_set_gamma (double gamma); +void gtk_preview_set_color_cube (guint nred_shades, + guint ngreen_shades, + guint nblue_shades, + guint ngray_shades); +void gtk_preview_set_install_cmap (gint install_cmap); +void gtk_preview_set_reserved (gint nreserved); +GdkVisual* gtk_preview_get_visual (void); +GdkColormap* gtk_preview_get_cmap (void); +GtkPreviewInfo* gtk_preview_get_info (void); + +That's all, folks! + +</verb></tscreen> + +<!-- ----------------------------------------------------------------- --> +<sect1> Curves +<p> + +<!-- ***************************************************************** --> +<sect>The EventBox Widget<label id="sec_The_EventBox_Widget"> +<!-- ***************************************************************** --> + +<p> +Some gtk widgets don't have associated X windows, so they just draw on +thier parents. Because of this, they cannot recieve events +and if they are incorrectly sized, they don't clip so you can get +messy overwritting etc. If you require more from these widgets, the +EventBox is for you. + +At first glance, the EventBox widget might appear to be totally +useless. It draws nothing on the screen and responds to no +events. However, it does serve a function - it provides an X window for +its child widget. This is important as many GTK widgets do not +have an associated X window. Not having an X window saves memory and +improves performance, but also has some drawbacks. A widget without an +X window cannot receive events, and does not perform any clipping on +it's contents. Although the name ``EventBox'' emphasizes the +event-handling function, the widget also can be used for clipping. +(And more ... see the example below.) + +<p> +To create a new EventBox widget, use: + +<tscreen><verb> +GtkWidget* gtk_event_box_new (void); +</verb></tscreen> + +<p> +A child widget can then be added to this EventBox: + +<tscreen><verb> +gtk_container_add (GTK_CONTAINER(event_box), widget); +</verb></tscreen> + +<p> +The following example demonstrates both uses of an EventBox - a label +is created that clipped to a small box, and set up so that a +mouse-click on the label causes the program to exit. + +<tscreen><verb> +/* eventbox.c */ + +#include <gtk/gtk.h> + +int +main (int argc, char *argv[]) +{ + GtkWidget *window; + GtkWidget *event_box; + GtkWidget *label; + + gtk_init (&argc, &argv); + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + + gtk_window_set_title (GTK_WINDOW (window), "Event Box"); + + gtk_signal_connect (GTK_OBJECT (window), "destroy", + GTK_SIGNAL_FUNC (gtk_exit), NULL); + + gtk_container_border_width (GTK_CONTAINER (window), 10); + + /* Create an EventBox and add it to our toplevel window */ + + event_box = gtk_event_box_new (); + gtk_container_add (GTK_CONTAINER(window), event_box); + gtk_widget_show (event_box); + + /* Create a long label */ + + label = gtk_label_new ("Click here to quit, quit, quit, quit, quit"); + gtk_container_add (GTK_CONTAINER (event_box), label); + gtk_widget_show (label); + + /* Clip it short. */ + gtk_widget_set_usize (label, 110, 20); + + /* And bind an action to it */ + gtk_widget_set_events (event_box, GDK_BUTTON_PRESS_MASK); + gtk_signal_connect (GTK_OBJECT(event_box), "button_press_event", + GTK_SIGNAL_FUNC (gtk_exit), NULL); + + /* Yet one more thing you need an X window for ... */ + + gtk_widget_realize (event_box); + gdk_window_set_cursor (event_box->window, gdk_cursor_new (GDK_HAND1)); + + gtk_widget_show (window); + + gtk_main (); + + return 0; +} +</verb></tscreen> + +<!-- ***************************************************************** --> +<sect>Setting Widget Attributes<label id="sec_setting_widget_attributes"> +<!-- ***************************************************************** --> + +<p> +This describes the functions used to operate on widgets. These can be used +to set style, padding, size etc. + +(Maybe I should make a whole section on accelerators.) + +<tscreen><verb> +void gtk_widget_install_accelerator (GtkWidget *widget, + GtkAcceleratorTable *table, + gchar *signal_name, + gchar key, + guint8 modifiers); + +void gtk_widget_remove_accelerator (GtkWidget *widget, + GtkAcceleratorTable *table, + gchar *signal_name); + +void gtk_widget_activate (GtkWidget *widget); + +void gtk_widget_set_name (GtkWidget *widget, + gchar *name); +gchar* gtk_widget_get_name (GtkWidget *widget); + +void gtk_widget_set_sensitive (GtkWidget *widget, + gint sensitive); + +void gtk_widget_set_style (GtkWidget *widget, + GtkStyle *style); + +GtkStyle* gtk_widget_get_style (GtkWidget *widget); + +GtkStyle* gtk_widget_get_default_style (void); + +void gtk_widget_set_uposition (GtkWidget *widget, + gint x, + gint y); +void gtk_widget_set_usize (GtkWidget *widget, + gint width, + gint height); + +void gtk_widget_grab_focus (GtkWidget *widget); + +void gtk_widget_show (GtkWidget *widget); + +void gtk_widget_hide (GtkWidget *widget); +</verb></tscreen> + +<!-- ***************************************************************** --> +<sect>Timeouts, IO and Idle Functions<label id="sec_timeouts"> +<!-- ***************************************************************** --> + +<!-- ----------------------------------------------------------------- --> +<sect1>Timeouts +<p> +You may be wondering how you make GTK do useful work when in gtk_main. +Well, you have several options. Using the following functions you can +create a timeout function that will be called every "interval" milliseconds. + +<tscreen><verb> +gint gtk_timeout_add (guint32 interval, + GtkFunction function, + gpointer data); +</verb></tscreen> + +The first argument is the number of milliseconds +between calls to your function. The second argument is the function +you wish to have called, and +the third, the data passed to this callback function. The return value is +an integer "tag" which may be used to stop the timeout by calling: + +<tscreen><verb> +void gtk_timeout_remove (gint tag); +</verb></tscreen> + +You may also stop the timeout function by returning zero or FALSE from +your callback function. Obviously this means if you want your function to +continue to be called, it should return a non-zero value, ie TRUE. + +The declaration of your callback should look something like this: + +<tscreen><verb> +gint timeout_callback (gpointer data); +</verb></tscreen> + +<!-- ----------------------------------------------------------------- --> +<sect1>Monitoring IO +<p> +Another nifty feature of GTK, is the ability to have it check for data on a +file descriptor for you (as returned by open(2) or socket(2)). This is +especially useful for networking applications. The function: + +<tscreen><verb> +gint gdk_input_add (gint source, + GdkInputCondition condition, + GdkInputFunction function, + gpointer data); +</verb></tscreen> + +Where the first argument is the file descriptor you wish to have watched, +and the second specifies what you want GDK to look for. This may be one of: +<p> +GDK_INPUT_READ - Call your function when there is data ready for reading on +your file descriptor. +<p> +GDK_INPUT_WRITE - Call your function when the file descriptor is ready for +writing. +<p> +As I'm sure you've figured out already, the third argument is the function +you wish to have called when the above conditions are satisfied, and the +fourth is the data to pass to this function. +<p> +The return value is a tag that may be used to stop GDK from monitoring this +file descriptor using the following function. +<p> +<tscreen><verb> +void gdk_input_remove (gint tag); +</verb></tscreen> +<p> +The callback function should be declared: +<p> +<tscreen><verb> +void input_callback (gpointer data, gint source, + GdkInputCondition condition); +</verb></tscreen> +<p> + +<!-- ----------------------------------------------------------------- --> +<sect1>Idle Functions +<p> +What if you have a function you want called when nothing else is +happening ? + +<tscreen><verb> +gint gtk_idle_add (GtkFunction function, + gpointer data); +</verb></tscreen> + +This causes GTK to call the specified function whenever nothing else is +happening. + +<tscreen><verb> +void gtk_idle_remove (gint tag); +</verb></tscreen> +<p> +I won't explain the meaning of the arguments as they follow very much like +the ones above. The function pointed to by the first argument to +gtk_idle_add will be called whenever the opportunity arises. As with the +others, returning FALSE will stop the idle function from being called. + +<!-- ***************************************************************** --> +<sect>Managing Selections +<!-- ***************************************************************** --> + +<!-- ----------------------------------------------------------------- --> +<sect1> Overview + +<p> + +One type of interprocess communication supported by GTK is +<em>selections</em>. A selection identifies a chunk of data, for +instance, a portion of text, selected by the user in some fashion, for +instance, by dragging with the mouse. Only one application on a +display, (he <em>owner</em>_ can own a particular selection at one +time, so when a selection is claimed by one application, the previous +owner must indicate to the user that selection has been +relinquished. Other applications can request the contents of a +selection in different forms, called <em>targets</em>. There can be +any number of selections, but most X applications only handle one, the +<em>primary selection</em>. + +<p> +In most cases, it isn't necessary for a GTK application to deal with +selections itself. The standard widgets, such as the Entry widget, +already have the capability to claim the selection when appropriate +(e.g., when the user drags over text), and to retrieve the contents of +the selection owned by another widget, or another application (e.g., +when the user clicks the second mouse button). However, there may be +cases in which you want to give other widgets the ability to supply +the selection, or you wish to retrieve targets not supported by +default. + +<p> +A fundamental concept needed to understand selection handling is that +of the <em>atom</em>. An atom is an integer that uniquely identifies a +string (on a certain display). Certain atoms are predefined by the X +server, and in some cases there are constants in in <tt>gtk.h</tt> +corresponding to these atoms. For instance the constant +<tt>GDK_PRIMARY_SELECTION</tt> corresponds to the string "PRIMARY". +In other cases, you should use the functions +<tt>gdk_atom_intern()</tt>, to get the atom corresponding to a string, +and <tt>gdk_atom_name()</tt>, to get the name of an atom. Both +selections and targets are identifed by atoms. + +<!-- ----------------------------------------------------------------- --> +<sect1> Retrieving the selection + +<p> + +Retrieving the selection is an asynchronous process. To start the +process, you call: + +<tscreen><verb> +gint gtk_selection_convert (GtkWidget *widget, + GdkAtom selection, + GdkAtom target, + guint32 time) +</verb</tscreen> + +This <em>converts</em> the selection into the form specified by +<tt/target/. If it all possible, the time field should be the time +from the event that triggered the selection. This helps make sure that +events occur in the order that the user requested them. + However, if it is not available (for instance, if the conversion was +triggered by a "clicked" signal), then you can use the constant +<tt>GDK_CURRENT_TIME</tt>. + +<p> +When the selection owner responds to the request, a +"selection_received" signal is sent to your application. The handler +for this signal receives a pointer to a <tt>GtkSelectionData</tt> +structure, which is defined as: + +<tscreen><verb> +struct _GtkSelectionData +{ + GdkAtom selection; + GdkAtom target; + GdkAtom type; + gint format; + guchar *data; + gint length; +}; +</verb></tscreen> + +<tt>selection</tt> and <tt>target</tt> are the values you gave in your +<tt>gtk_selection_convert()</tt> call. <tt>type</tt> is an atom that +identifies the type of data returned by the selection owner. Some +possible values are "STRING", a string of latin-1 characters, "ATOM", +a series of atoms, "INTEGER", an integer, etc. Most targets can only +return one type. <tt/format/ gives the length of the units (for +instance characters) in bits. Usually, you don't care about this when +receiving data. <tt>data</tt> is a pointer to the returned data, and +<tt>length</tt> gives the length of the returned data, in bytes. If +<tt>length</tt> is negative, then an error occurred and the selection +could not be retrieved. This might happen if no application owned the +selection, or if you requested a target that the application didn't +support. The buffer is actually guaranteed to be one byte longer than +<tt>length</tt>; the extra byte will always be zero, so it isn't +necessary to make a copy of strings just to null terminate them. + +<p> +In the following example, we retrieve the special target "TARGETS", +which is a list of all targets into which the selection can be +converted. + +<tscreen><verb> +/* gettargets.c */ + +#include <gtk/gtk.h> + +void selection_received (GtkWidget *widget, + GtkSelectionData *selection_data, + gpointer data); + +/* Signal handler invoked when user clicks on the "Get Targets" button */ +void +get_targets (GtkWidget *widget, gpointer data) +{ + static GdkAtom targets_atom = GDK_NONE; + + /* Get the atom corresonding to the string "TARGETS" */ + if (targets_atom == GDK_NONE) + targets_atom = gdk_atom_intern ("TARGETS", FALSE); + + /* And request the "TARGETS" target for the primary selection */ + gtk_selection_convert (widget, GDK_SELECTION_PRIMARY, targets_atom, + GDK_CURRENT_TIME); +} + +/* Signal handler called when the selections owner returns the data */ +void +selection_received (GtkWidget *widget, GtkSelectionData *selection_data, + gpointer data) +{ + GdkAtom *atoms; + GList *item_list; + int i; + + /* **** IMPORTANT **** Check to see if retrieval succeeded */ + if (selection_data->length < 0) + { + g_print ("Selection retrieval failed\n"); + return; + } + /* Make sure we got the data in the expected form */ + if (selection_data->type != GDK_SELECTION_TYPE_ATOM) + { + g_print ("Selection \"TARGETS\" was not returned as atoms!\n"); + return; + } + + /* Print out the atoms we received */ + atoms = (GdkAtom *)selection_data->data; + + item_list = NULL; + for (i=0; i<selection_data->length/sizeof(GdkAtom); i++) + { + char *name; + name = gdk_atom_name (atoms[i]); + if (name != NULL) + g_print ("%s\n",name); + else + g_print ("(bad atom)\n"); + } + + return; +} + +int +main (int argc, char *argv[]) +{ + GtkWidget *window; + GtkWidget *button; + + gtk_init (&argc, &argv); + + /* Create the toplevel window */ + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_window_set_title (GTK_WINDOW (window), "Event Box"); + gtk_container_border_width (GTK_CONTAINER (window), 10); + + gtk_signal_connect (GTK_OBJECT (window), "destroy", + GTK_SIGNAL_FUNC (gtk_exit), NULL); + + /* Create a button the user can click to get targets */ + + button = gtk_button_new_with_label ("Get Targets"); + gtk_container_add (GTK_CONTAINER (window), button); + + gtk_signal_connect (GTK_OBJECT(button), "clicked", + GTK_SIGNAL_FUNC (get_targets), NULL); + gtk_signal_connect (GTK_OBJECT(button), "selection_received", + GTK_SIGNAL_FUNC (selection_received), NULL); + + gtk_widget_show (button); + gtk_widget_show (window); + + gtk_main (); + + return 0; +} +</verb></tscreen> + +<!-- ----------------------------------------------------------------- --> +<sect1> Supplying the selection + +<p> + +Supplying the selection is a bit more complicated. You must register +handlers that will be called when your selection is requested. For +each selection/target pair you will handle, you make a call to: + +<tscreen><verb> +void gtk_selection_add_handler (GtkWidget *widget, + GdkAtom selection, + GdkAtom target, + GtkSelectionFunction function, + GtkRemoveFunction remove_func, + gpointer data); +</verb></tscreen> + +<tt/widget/, <tt/selection/, and <tt/target/ identify the requests +this handler will manage. <tt/remove_func/ if not +NULL, will be called when the signal handler is removed. This is +useful, for instance, for interpreted languages which need to +keep track of a reference count for <tt/data/. + +<p> +The callback function has the signature: + +<tscreen><verb> +typedef void (*GtkSelectionFunction) (GtkWidget *widget, + GtkSelectionData *selection_data, + gpointer data); + +</verb></tscreen> + +The GtkSelectionData is the same as above, but this time, we're +responsible for filling in the fields <tt/type/, <tt/format/, +<tt/data/, and <tt/length/. (The <tt/format/ field is actually +important here - the X server uses it to figure out whether the data +needs to be byte-swapped or not. Usually it will be 8 - <em/i.e./ a +character - or 32 - <em/i.e./ a. integer.) This is done by calling the +function: + +<tscreen><verb> +void gtk_selection_data_set (GtkSelectionData *selection_data, + GdkAtom type, + gint format, + guchar *data, + gint length); +</verb></tscreen> + +This function takes care of properly making a copy of the data so that +you don't have to worry about keeping it around. (You should not fill +in the fields of the GtkSelectionData structure by hand.) + +<p> +When prompted by the user, you claim ownership of the selection by +calling: + +<tscreen><verb> +gint gtk_selection_owner_set (GtkWidget *widget, + GdkAtom selection, + guint32 time); +</verb></tscreen> + +If another application claims ownership of the selection, you will +receive a "selection_clear_event". + +As an example of supplying the selection, the following program adds +selection functionality to a toggle button. When the toggle button is +depressed, the program claims the primary selection. The only target +supported (aside from certain targets like "TARGETS" supplied by GTK +itself), is the "STRING" target. When this target is requested, a +string representation of the time is returned. + +<tscreen><verb> +/* setselection.c */ + +#include <gtk/gtk.h> +#include <time.h> + +/* Callback when the user toggles the selection */ +void +selection_toggled (GtkWidget *widget, gint *have_selection) +{ + if (GTK_TOGGLE_BUTTON(widget)->active) + { + *have_selection = gtk_selection_owner_set (widget, + GDK_SELECTION_PRIMARY, + GDK_CURRENT_TIME); + /* if claiming the selection failed, we return the button to + the out state */ + if (!*have_selection) + gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON(widget), FALSE); + } + else + { + if (*have_selection) + { + /* Before clearing the selection by setting the owner to NULL, + we check if we are the actual owner */ + if (gdk_selection_owner_get (GDK_SELECTION_PRIMARY) == widget->window) + gtk_selection_owner_set (NULL, GDK_SELECTION_PRIMARY, + GDK_CURRENT_TIME); + *have_selection = FALSE; + } + } +} + +/* Called when another application claims the selection */ +gint +selection_clear (GtkWidget *widget, GdkEventSelection *event, + gint *have_selection) +{ + *have_selection = FALSE; + gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON(widget), FALSE); + + return TRUE; +} + +/* Supplies the current time as the selection. */ +void +selection_handle (GtkWidget *widget, + GtkSelectionData *selection_data, + gpointer data) +{ + gchar *timestr; + time_t current_time; + + current_time = time (NULL); + timestr = asctime (localtime(&current_time)); + /* When we return a single string, it should not be null terminated. + That will be done for us */ + + gtk_selection_data_set (selection_data, GDK_SELECTION_TYPE_STRING, + 8, timestr, strlen(timestr)); +} + +int +main (int argc, char *argv[]) +{ + GtkWidget *window; + + GtkWidget *selection_button; + + static int have_selection = FALSE; + + gtk_init (&argc, &argv); + + /* Create the toplevel window */ + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_window_set_title (GTK_WINDOW (window), "Event Box"); + gtk_container_border_width (GTK_CONTAINER (window), 10); + + gtk_signal_connect (GTK_OBJECT (window), "destroy", + GTK_SIGNAL_FUNC (gtk_exit), NULL); + + /* Create a toggle button to act as the selection */ + + selection_button = gtk_toggle_button_new_with_label ("Claim Selection"); + gtk_container_add (GTK_CONTAINER (window), selection_button); + gtk_widget_show (selection_button); + + gtk_signal_connect (GTK_OBJECT(selection_button), "toggled", + GTK_SIGNAL_FUNC (selection_toggled), &have_selection); + gtk_signal_connect (GTK_OBJECT(selection_button), "selection_clear_event", + GTK_SIGNAL_FUNC (selection_clear), &have_selection); + + gtk_selection_add_handler (selection_button, GDK_SELECTION_PRIMARY, + GDK_SELECTION_TYPE_STRING, + selection_handle, NULL); + + gtk_widget_show (selection_button); + gtk_widget_show (window); + + gtk_main (); + + return 0; +} +</verb></tscreen> + + +<!-- ***************************************************************** --> +<sect>glib<label id="sec_glib"> +<!-- ***************************************************************** --> + +<p> +glib provides many useful functions and definitions available for use +when creating GDK +and GTK applications. I will list them all here with a brief explanation. +Many are duplicates of standard libc functions so I won't go into +detail on those. This is mostly to be used as a reference, so you know what is +available for use. + +<!-- ----------------------------------------------------------------- --> +<sect1>Definitions +<p> +Definitions for the extremes of many of the standard types are: + +<tscreen><verb> +G_MINFLOAT +G_MAXFLOAT +G_MINDOUBLE +G_MAXDOUBLE +G_MINSHORT +G_MAXSHORT +G_MININT +G_MAXINT +G_MINLONG +G_MAXLONG +</verb></tscreen> + +Also, the following typedefs. The ones left unspecified are dynamically set +depending on the architecture. Remember to avoid counting on the size of a +pointer if you want to be portable! Eg, a pointer on an Alpha is 8 bytes, but 4 +on Intel. + +<tscreen><verb> +char gchar; +short gshort; +long glong; +int gint; +char gboolean; + +unsigned char guchar; +unsigned short gushort; +unsigned long gulong; +unsigned int guint; + +float gfloat; +double gdouble; +long double gldouble; + +void* gpointer; + +gint8 +guint8 +gint16 +guint16 +gint32 +guint32 +</verb></tscreen> + +<!-- ----------------------------------------------------------------- --> +<sect1>Doubly Linked Lists +<p> +The following functions are used to create, manage, and destroy doubly +linked lists. I assume you know what linked lists are, as it is beyond the scope +of this document to explain them. Of course, it's not required that you +know these for general use of GTK, but they are nice to know. + +<tscreen><verb> +GList* g_list_alloc (void); + +void g_list_free (GList *list); + +void g_list_free_1 (GList *list); + +GList* g_list_append (GList *list, + gpointer data); + +GList* g_list_prepend (GList *list, + gpointer data); + +GList* g_list_insert (GList *list, + gpointer data, + gint position); + +GList* g_list_remove (GList *list, + gpointer data); + +GList* g_list_remove_link (GList *list, + GList *link); + +GList* g_list_reverse (GList *list); + +GList* g_list_nth (GList *list, + gint n); + +GList* g_list_find (GList *list, + gpointer data); + +GList* g_list_last (GList *list); + +GList* g_list_first (GList *list); + +gint g_list_length (GList *list); + +void g_list_foreach (GList *list, + GFunc func, + gpointer user_data); +</verb></tscreen> + +<!-- ----------------------------------------------------------------- --> +<sect1>Singly Linked Lists +<p> +Many of the above functions for singly linked lists are identical to the +above. Here is a complete list: +<tscreen><verb> +GSList* g_slist_alloc (void); + +void g_slist_free (GSList *list); + +void g_slist_free_1 (GSList *list); + +GSList* g_slist_append (GSList *list, + gpointer data); + +GSList* g_slist_prepend (GSList *list, + gpointer data); + +GSList* g_slist_insert (GSList *list, + gpointer data, + gint position); + +GSList* g_slist_remove (GSList *list, + gpointer data); + +GSList* g_slist_remove_link (GSList *list, + GSList *link); + +GSList* g_slist_reverse (GSList *list); + +GSList* g_slist_nth (GSList *list, + gint n); + +GSList* g_slist_find (GSList *list, + gpointer data); + +GSList* g_slist_last (GSList *list); + +gint g_slist_length (GSList *list); + +void g_slist_foreach (GSList *list, + GFunc func, + gpointer user_data); + +</verb></tscreen> + +<!-- ----------------------------------------------------------------- --> +<sect1>Memory Management +<p> +<tscreen><verb> +gpointer g_malloc (gulong size); +</verb></tscreen> + +This is a replacement for malloc(). You do not need to check the return +vaule as it is done for you in this function. + +<tscreen><verb> +gpointer g_malloc0 (gulong size); +</verb></tscreen> + +Same as above, but zeroes the memory before returning a pointer to it. + +<tscreen><verb> +gpointer g_realloc (gpointer mem, + gulong size); +</verb></tscreen> + +Relocates "size" bytes of memory starting at "mem". Obviously, the memory should have been +previously allocated. + +<tscreen><verb> +void g_free (gpointer mem); +</verb></tscreen> + +Frees memory. Easy one. + +<tscreen><verb> +void g_mem_profile (void); +</verb></tscreen> + +Dumps a profile of used memory, but requries that you add #define +MEM_PROFILE to the top of glib/gmem.c and re-make and make install. + +<tscreen><verb> +void g_mem_check (gpointer mem); +</verb></tscreen> + +Checks that a memory location is valid. Requires you add #define +MEM_CHECK to the top of gmem.c and re-make and make install. + +<!-- ----------------------------------------------------------------- --> +<sect1>Timers +<p> +Timer functions.. + +<tscreen><verb> +GTimer* g_timer_new (void); + +void g_timer_destroy (GTimer *timer); + +void g_timer_start (GTimer *timer); + +void g_timer_stop (GTimer *timer); + +void g_timer_reset (GTimer *timer); + +gdouble g_timer_elapsed (GTimer *timer, + gulong *microseconds); +</verb></tscreen> + +<!-- ----------------------------------------------------------------- --> +<sect1>String Handling +<p> +A whole mess of string handling functions. They all look very interesting, and +probably better for many purposes than the standard C string functions, but +require documentation. + +<tscreen><verb> +GString* g_string_new (gchar *init); +void g_string_free (GString *string, + gint free_segment); + +GString* g_string_assign (GString *lval, + gchar *rval); + +GString* g_string_truncate (GString *string, + gint len); + +GString* g_string_append (GString *string, + gchar *val); + +GString* g_string_append_c (GString *string, + gchar c); + +GString* g_string_prepend (GString *string, + gchar *val); + +GString* g_string_prepend_c (GString *string, + gchar c); + +void g_string_sprintf (GString *string, + gchar *fmt, + ...); + +void g_string_sprintfa (GString *string, + gchar *fmt, + ...); +</verb></tscreen> + +<!-- ----------------------------------------------------------------- --> +<sect1>Utility and Error Functions +<p> +<tscreen><verb> +gchar* g_strdup (const gchar *str); +</verb></tscreen> + +Replacement strdup function. Copies the +original strings contents to newly allocated memory, and returns a pointer to it. + +<tscreen><verb> +gchar* g_strerror (gint errnum); +</verb></tscreen> + +I recommend using this for all error messages. It's much nicer, and more +portable than perror() or others. The output is usually of the form: + +<tscreen><verb> +program name:function that failed:file or further description:strerror +</verb></tscreen> + +Here's an example of one such call used in our hello_world program: + +<tscreen><verb> +g_print("hello_world:open:%s:%s\n", filename, g_strerror(errno)); +</verb></tscreen> + +<tscreen><verb> +void g_error (gchar *format, ...); +</verb></tscreen> + +Prints an error message. The format is just like printf, but it +prepends "** ERROR **: " to your message, and exits the program. +Use only for fatal errors. + +<tscreen><verb> +void g_warning (gchar *format, ...); +</verb></tscreen> + +Same as above, but prepends "** WARNING **: ", and does not exit the +program. + +<tscreen><verb> +void g_message (gchar *format, ...); +</verb></tscreen> + +Prints "message: " prepended to the string you pass in. + +<tscreen><verb> +void g_print (gchar *format, ...); +</verb></tscreen> + +Replacement for printf(). + +And our last function: + +<tscreen><verb> +gchar* g_strsignal (gint signum); +</verb></tscreen> + +Prints out the name of the Unix system signal given the signal number. +Useful in generic signal handling functions. + +All of the above are more or less just stolen from glib.h. If anyone cares +to document any function, just send me an email! + +<!-- ***************************************************************** --> +<sect>GTK's rc Files +<!-- ***************************************************************** --> + +<p> +GTK has it's own way of dealing with application defaults, by using rc +files. These can be used to set the colors of just about any widget, and +can also be used to tile pixmaps onto the background of some widgets. + +<!-- ----------------------------------------------------------------- --> +<sect1>Functions For rc Files +<p> +When your application starts, you should include a call to: +<tscreen><verb> +void gtk_rc_parse (char *filename); +</verb></tscreen> +<p> +Passing in the filename of your rc file. This will cause GTK to parse this +file, and use the style settings for the widget types defined there. +<p> +If you wish to have a special set of widgets that can take on a different +style from others, or any other logical division of widgets, use a call to: +<tscreen><verb> +void gtk_widget_set_name (GtkWidget *widget, + gchar *name); +</verb></tscreen> +<p> +Passing your newly created widget as the first argument, and the name +you wish to give it as the second. This will allow you to change the +attributes of this widget by name through the rc file. +<p> +If we use a call something like this: + +<tscreen><verb> +button = gtk_button_new_with_label ("Special Button"); +gtk_widget_set_name (button, "special button"); +</verb></tscreen> +<p> +Then this button is given the name "special button" and may be addressed by +name in the rc file as "special button.GtkButton". [<--- Verify ME!] +<p> +The example rc file below, sets the properties of the main window, and lets +all children of that main window inherit the style described by the "main +button" style. The code used in the application is: + +<tscreen><verb> +window = gtk_window_new (GTK_WINDOW_TOPLEVEL); +gtk_widget_set_name (window, "main window"); +</verb></tscreen> +<p> +And then the style is defined in the rc file using: + +<tscreen><verb> +widget "main window.*GtkButton*" style "main_button" +</verb></tscreen> +<p> +Which sets all the GtkButton widgets in the "main window" to the +"main_buttons" style as defined in the rc file. +<p> +As you can see, this is a fairly powerful and flexible system. Use your +imagination as to how best to take advantage of this. + +<!-- ----------------------------------------------------------------- --> +<sect1>GTK's rc File Format +<p> +The format of the GTK file is illustrated in the example below. This is +the testgtkrc file from the GTK distribution, but I've added a +few comments and things. You may wish to include this explanation +your application to allow the user to fine tune his application. +<p> +There are several directives to change the attributes of a widget. +<itemize> +<item>fg - Sets the foreground color of a widget. +<item>bg - Sets the background color of a widget. +<item>bg_pixmap - Sets the background of a widget to a tiled pixmap. +<item>font - Sets the font to be used with the given widget. +</itemize> +<p> +In addition to this, there are several states a widget can be in, and you +can set different colors, pixmaps and fonts for each state. These states are: +<itemize> +<item>NORMAL - The normal state of a widget, without the mouse over top of +it, and not being pressed etc. +<item>PRELIGHT - When the mouse is over top of the widget, colors defined +using this state will be in effect. +<item>ACTIVE - When the widget is pressed or clicked it will be active, and +the attributes assigned by this tag will be in effect. +<item>INSENSITIVE - When a widget is set insensitive, and cannot be +activated, it will take these attributes. +<item>SELECTED - When an object is selected, it takes these attributes. +</itemize> +<p> +When using the "fg" and "bg" keywords to set the colors of widgets, the +format is: +<tscreen><verb> +fg[<STATE>] = { Red, Green, Blue } +</verb></tscreen> +<p> +Where STATE is one of the above states (PRELIGHT, ACTIVE etc), and the Red, +Green and Blue are values in the range of 0 - 1.0, { 1.0, 1.0, 1.0 } being +white. +They must be in float form, or they will register as 0, so a straight +"1" will not work, it must +be "1.0". A straight "0" is fine because it doesn't matter if it's not +recognized. Unrecognized values are set to 0. +<p> +bg_pixmap is very similar to the above, except the colors are replaced by a +filename. + +pixmap_path is a list of paths seperated by ":"'s. These paths will be +searched for any pixmap you specify. + +<p> +The font directive is simply: +<tscreen><verb> +font = "<font name>" +</verb></tscreen> +<p> +Where the only hard part is figuring out the font string. Using xfontsel or +similar utility should help. +<p> +The "widget_class" sets the style of a class of widgets. These classes are +listed in the widget overview on the class hierarchy. +<p> +The "widget" directive sets a specificaly named set of widgets to a +given style, overriding any style set for the given widget class. +These widgets are registered inside the application using the +gtk_widget_set_name() call. This allows you to specify the attributes of a +widget on a per widget basis, rather than setting the attributes of an +entire widget class. I urge you to document any of these special widgets so +users may customize them. +<p> +When the keyword "<tt>parent</>" is used as an attribute, the widget will take on +the attributes of it's parent in the application. +<p> +When defining a style, you may assign the attributes of a previously defined +style to this new one. +<tscreen><verb> +style "main_button" = "button" +{ + font = "-adobe-helvetica-medium-r-normal--*-100-*-*-*-*-*-*" + bg[PRELIGHT] = { 0.75, 0, 0 } +} +</verb></tscreen> +<p> +This example takes the "button" style, and creates a new "main_button" style +simply by changing the font and prelight background color of the "button" +style. +<p> +Of course, many of these attributes don't apply to all widgets. It's a +simple matter of common sense really. Anything that could apply, should. + +<!-- ----------------------------------------------------------------- --> +<sect1>Example rc file +<p> + +<tscreen><verb> +# pixmap_path "<dir 1>:<dir 2>:<dir 3>:..." +# +pixmap_path "/usr/include/X11R6/pixmaps:/home/imain/pixmaps" +# +# style <name> [= <name>] +# { +# <option> +# } +# +# widget <widget_set> style <style_name> +# widget_class <widget_class_set> style <style_name> + + +# Here is a list of all the possible states. Note that some do not apply to +# certain widgets. +# +# NORMAL - The normal state of a widget, without the mouse over top of +# it, and not being pressed etc. +# +# PRELIGHT - When the mouse is over top of the widget, colors defined +# using this state will be in effect. +# +# ACTIVE - When the widget is pressed or clicked it will be active, and +# the attributes assigned by this tag will be in effect. +# +# INSENSITIVE - When a widget is set insensitive, and cannot be +# activated, it will take these attributes. +# +# SELECTED - When an object is selected, it takes these attributes. +# +# Given these states, we can set the attributes of the widgets in each of +# these states using the following directives. +# +# fg - Sets the foreground color of a widget. +# fg - Sets the background color of a widget. +# bg_pixmap - Sets the background of a widget to a tiled pixmap. +# font - Sets the font to be used with the given widget. +# + +# This sets a style called "button". The name is not really important, as +# it is assigned to the actual widgets at the bottom of the file. + +style "window" +{ + #This sets the padding around the window to the pixmap specified. + #bg_pixmap[<STATE>] = "<pixmap filename>" + bg_pixmap[NORMAL] = "warning.xpm" +} + +style "scale" +{ + #Sets the foreground color (font color) to red when in the "NORMAL" + #state. + + fg[NORMAL] = { 1.0, 0, 0 } + + #Sets the background pixmap of this widget to that of it's parent. + bg_pixmap[NORMAL] = "<parent>" +} + +style "button" +{ + # This shows all the possible states for a button. The only one that + # doesn't apply is the SELECTED state. + + fg[PRELIGHT] = { 0, 1.0, 1.0 } + bg[PRELIGHT] = { 0, 0, 1.0 } + bg[ACTIVE] = { 1.0, 0, 0 } + fg[ACTIVE] = { 0, 1.0, 0 } + bg[NORMAL] = { 1.0, 1.0, 0 } + fg[NORMAL] = { .99, 0, .99 } + bg[INSENSITIVE] = { 1.0, 1.0, 1.0 } + fg[INSENSITIVE] = { 1.0, 0, 1.0 } +} + +# In this example, we inherit the attributes of the "button" style and then +# override the font and background color when prelit to create a new +# "main_button" style. + +style "main_button" = "button" +{ + font = "-adobe-helvetica-medium-r-normal--*-100-*-*-*-*-*-*" + bg[PRELIGHT] = { 0.75, 0, 0 } +} + +style "toggle_button" = "button" +{ + fg[NORMAL] = { 1.0, 0, 0 } + fg[ACTIVE] = { 1.0, 0, 0 } + + # This sets the background pixmap of the toggle_button to that of it's + # parent widget (as defined in the application). + bg_pixmap[NORMAL] = "<parent>" +} + +style "text" +{ + bg_pixmap[NORMAL] = "marble.xpm" + fg[NORMAL] = { 1.0, 1.0, 1.0 } +} + +style "ruler" +{ + font = "-adobe-helvetica-medium-r-normal--*-80-*-*-*-*-*-*" +} + +# pixmap_path "~/.pixmaps" + +# These set the widget types to use the styles defined above. +# The widget types are listed in the class hierarchy, but could probably be +# just listed in this document for the users reference. + +widget_class "GtkWindow" style "window" +widget_class "GtkDialog" style "window" +widget_class "GtkFileSelection" style "window" +widget_class "*Gtk*Scale" style "scale" +widget_class "*GtkCheckButton*" style "toggle_button" +widget_class "*GtkRadioButton*" style "toggle_button" +widget_class "*GtkButton*" style "button" +widget_class "*Ruler" style "ruler" +widget_class "*GtkText" style "text" + +# This sets all the buttons that are children of the "main window" to +# the main_buton style. These must be documented to be taken advantage of. +widget "main window.*GtkButton*" style "main_button" +</verb></tscreen> + +<!-- ***************************************************************** --> +<sect>Writing Your Own Widgets +<!-- ***************************************************************** --> + +<!-- ----------------------------------------------------------------- --> +<sect1> Overview +<p> +Although the GTK distribution comes with many types of widgets that +should cover most basic needs, there may come a time when you need to +create your own new widget type. Since GTK uses widget inheretence +extensively, and there is already a widget that +is close to what you want, it is often possible to make a useful new widget type in +just a few lines of code. But before starting work on a new widget, check +around first to make sure that someone has not already written +it. This will prevent duplication of effort and keep the number of +GTK widgets out there to a minimum, which will help keep both the code +and the interface of different applications consistent. As a flip side +to this, once you finish your widget, announce it to the world so +other people can benefit. The best place to do this is probably the +<tt>gtk-list</tt>. + +Complete sources for the example widgets are available at the place you +got this tutorial, or from: + +<htmlurl url="http://www.msc.cornell.edu/~otaylor/gtk-gimp/tutorial" +name="http://www.msc.cornell.edu/~otaylor/gtk-gimp/tutorial"> + + +<!-- ----------------------------------------------------------------- --> +<sect1> The Anatomy Of A Widget + +<p> +In order to create a new widget, it is important to have an +understanding of how GTK objects work. This section is just meant as a +brief overview. See the reference documentation for the details. + +<p> +GTK widgets are implemented in an object oriented fashion. However, +they are implemented in standard C. This greatly improves portability +and stability over using current generation C++ compilers; however, +it does mean that the widget writer has to pay attention to some of +the implementation details. The information common to all instances of +one class of widgets (e.g., to all Button widgets) is stored in the +<em>class structure</em>. There is only one copy of this in +which is stored information about the class's signals +(which act like virtual functions in C). To support inheritance, the +first field in the class structure must be a copy of the parent's +class structure. The declaration of the class structure of GtkButtton +looks like: + +<tscreen><verb> +struct _GtkButtonClass +{ + GtkContainerClass parent_class; + + void (* pressed) (GtkButton *button); + void (* released) (GtkButton *button); + void (* clicked) (GtkButton *button); + void (* enter) (GtkButton *button); + void (* leave) (GtkButton *button); +}; +</verb></tscreen> + +<p> +When a button is treated as a container (for instance, when it is +resized), its class structure can be casted to GtkContainerClass, and +the relevant fields used to handle the signals. + +<p> +There is also a structure for each widget that is created on a +per-instance basis. This structure has fields to store information that +is different for each instance of the widget. We'll call this +structure the <em>object structure</em>. For the Button class, it looks +like: + +<tscreen><verb> +struct _GtkButton +{ + GtkContainer container; + + GtkWidget *child; + + guint in_button : 1; + guint button_down : 1; +}; +</verb></tscreen> + +<p> +Note that, similar to the class structure, the first field is the +object structure of the parent class, so that this structure can be +casted to the parent class's object structure as needed. + +<!-- ----------------------------------------------------------------- --> +<sect1> Creating a Composite widget + +<!-- ----------------------------------------------------------------- --> +<sect2> Introduction + +<p> +One type of widget that you may be interested in creating is a +widget that is merely an aggregate of other GTK widgets. This type of +widget does nothing that couldn't be done without creating new +widgets, but provides a convenient way of packaging user interface +elements for reuse. The FileSelection and ColorSelection widgets in +the standard distribution are examples of this type of widget. + +<p> +The example widget that we'll create in this section is the Tictactoe +widget, a 3x3 array of toggle buttons which triggers a signal when all +three buttons in a row, column, or on one of the diagonals are +depressed. + +<!-- ----------------------------------------------------------------- --> +<sect2> Choosing a parent class + +<p> +The parent class for a composite widget is typically the container +class that holds all of the elements of the composite widget. For +example, the parent class of the FileSelection widget is the +Dialog class. Since our buttons will be arranged in a table, it +might seem natural to make our parent class the GtkTable +class. Unfortunately, this turns out not to work. The creation of a +widget is divided among two functions - a <tt/WIDGETNAME_new()/ +function that the user calls, and a <tt/WIDGETNAME_init()/ function +which does the basic work of initializing the widget which is +independent of the arguments passed to the <tt/_new()/ +function. Descendent widgets only call the <tt/_init/ function of +their parent widget. But this division of labor doesn't work well for +tables, which when created, need to know the number of rows and +columns in the table. Unless we want to duplicate most of the +functionality of <tt/gtk_table_new()/ in our Tictactoe widget, we had +best avoid deriving it from GtkTable. For that reason, we derive it +from GtkVBox instead, and stick our table inside the VBox. + +<!-- ----------------------------------------------------------------- --> +<sect2> The header file + +<p> +Each widget class has a header file which declares the object and +class structures for that widget, along with public functions. +A couple of features are worth pointing out. To prevent duplicate +definitions, we wrap the entire header file in: + +<tscreen><verb> +#ifndef __TICTACTOE_H__ +#define __TICTACTOE_H__ +. +. +. +#endif /* __TICTACTOE_H__ */ +</verb></tscreen> + +And to keep C++ programs that include the header file happy, in: + +<tscreen><verb> +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ +. +. +. +#ifdef __cplusplus +} +#endif /* __cplusplus */ +</verb></tscreen> + +Along with the functions and structures, we declare three standard +macros in our header file, <tt/TICTACTOE(obj)/, +<tt/TICTACTOE_CLASS(klass)/, and <tt/IS_TICTACTOE(obj)/, which cast a +pointer into a pointer to the the object or class structure, and check +if an object is a Tictactoe widget respectively. + +<p> +Here is the complete header file: + +<tscreen><verb> +/* tictactoe.h */ + +#ifndef __TICTACTOE_H__ +#define __TICTACTOE_H__ + +#include <gdk/gdk.h> +#include <gtk/gtkvbox.h> + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#define TICTACTOE(obj) GTK_CHECK_CAST (obj, tictactoe_get_type (), Tictactoe) +#define TICTACTOE_CLASS(klass) GTK_CHECK_CLASS_CAST (klass, tictactoe_get_type (), TictactoeClass) +#define IS_TICTACTOE(obj) GTK_CHECK_TYPE (obj, tictactoe_get_type ()) + + +typedef struct _Tictactoe Tictactoe; +typedef struct _TictactoeClass TictactoeClass; + +struct _Tictactoe +{ + GtkVBox vbox; + + GtkWidget *buttons[3][3]; +}; + +struct _TictactoeClass +{ + GtkVBoxClass parent_class; + + void (* tictactoe) (Tictactoe *ttt); +}; + +guint tictactoe_get_type (void); +GtkWidget* tictactoe_new (void); +void tictactoe_clear (Tictactoe *ttt); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __TICTACTOE_H__ */ + +</verb></tscreen> + +<!-- ----------------------------------------------------------------- --> +<sect2> The <tt/_get_type()/ function. + +<p> +We now continue on to the implementation of our widget. A core +function for every widget is the function +<tt/WIDGETNAME_get_type()/. This function, when first called, tells +GTK about the widget class, and gets an ID that uniquely identifies +the widget class. Upon subsequent calls, it just returns the ID. + +<tscreen><verb> +guint +tictactoe_get_type () +{ + static guint ttt_type = 0; + + if (!ttt_type) + { + GtkTypeInfo ttt_info = + { + "Tictactoe", + sizeof (Tictactoe), + sizeof (TictactoeClass), + (GtkClassInitFunc) tictactoe_class_init, + (GtkObjectInitFunc) tictactoe_init, + (GtkArgFunc) NULL, + }; + + ttt_type = gtk_type_unique (gtk_vbox_get_type (), &ttt_info); + } + + return ttt_type; +} +</verb></tscreen> + +<p> +The GtkTypeInfo structure has the following definition: + +<tscreen><verb> +struct _GtkTypeInfo +{ + gchar *type_name; + guint object_size; + guint class_size; + GtkClassInitFunc class_init_func; + GtkObjectInitFunc object_init_func; + GtkArgFunc arg_func; +}; +</verb></tscreen> + +<p> +The fields of this structure are pretty self-explanatory. We'll ignore +the <tt/arg_func/ field here: It has an important, but as yet largely +unimplemented, role in allowing widget options to be conveniently set +from interpreted languages. Once GTK has a correctly filled in copy of +this structure, it knows how to create objects of a particular widget +type. + +<!-- ----------------------------------------------------------------- --> +<sect2> The <tt/_class_init()/ function + +<p> +The <tt/WIDGETNAME_class_init()/ function initializes the fields of +the widget's class structure, and sets up any signals for the +class. For our Tictactoe widget it looks like: + +<tscreen><verb> + +enum { + TICTACTOE_SIGNAL, + LAST_SIGNAL +}; + +static gint tictactoe_signals[LAST_SIGNAL] = { 0 }; + +static void +tictactoe_class_init (TictactoeClass *class) +{ + GtkObjectClass *object_class; + + object_class = (GtkObjectClass*) class; + + tictactoe_signals[TICTACTOE_SIGNAL] = gtk_signal_new ("tictactoe", + GTK_RUN_FIRST, + object_class->type, + GTK_SIGNAL_OFFSET (TictactoeClass, tictactoe), + gtk_signal_default_marshaller, GTK_ARG_NONE, 0); + + + gtk_object_class_add_signals (object_class, tictactoe_signals, LAST_SIGNAL); + + class->tictactoe = NULL; +} +</verb></tscreen> + +<p> +Our widget has just one signal, the ``tictactoe'' signal that is +invoked when a row, column, or diagonal is completely filled in. Not +every composite widget needs signals, so if you are reading this for +the first time, you may want to skip to the next section now, as +things are going to get a bit complicated. + +The function: + +<tscreen><verb> +gint gtk_signal_new (gchar *name, + GtkSignalRunType run_type, + gint object_type, + gint function_offset, + GtkSignalMarshaller marshaller, + GtkArgType return_val, + gint nparams, + ...); +</verb></tscreen> + +Creates a new signal. The parameters are: + +<itemize> +<item> <tt/name/: The name of the signal. +<item> <tt/run_type/: Whether the default handler runs before or after +user handlers. Usually this will be <tt/GTK_RUN_FIRST/, or <tt/GTK_RUN_LAST/, +although there are other possibilities. +<item> <tt/object_type/: The ID of the object that this signal applies +to. (It will also apply to that objects descendents) +<item> <tt/function_offset/: The offset within the class structure of +a pointer to the default handler. +<item> <tt/marshaller/: A function that is used to invoke the signal +handler. For signal handlers that have no arguments other than the +object that emitted the signal and user data, we can use the +presupplied marshaller function <tt/gtk_signal_default_marshaller/. +<item> <tt/return_val/: The type of the return val. +<item> <tt/nparams/: The number of parameters of the signal handler +(other than the two default ones mentioned above) +<item> <tt/.../: The types of the parameters. +</itemize> + +When specifying types, the <tt/GtkArgType/ enumeration is used: + +<tscreen><verb> +typedef enum +{ + GTK_ARG_INVALID, + GTK_ARG_NONE, + GTK_ARG_CHAR, + GTK_ARG_SHORT, + GTK_ARG_INT, + GTK_ARG_LONG, + GTK_ARG_POINTER, + GTK_ARG_OBJECT, + GTK_ARG_FUNCTION, + GTK_ARG_SIGNAL +} GtkArgType; +</verb></tscreen> + +<p> +<tt/gtk_signal_new()/ returns a unique integer identifier for the +signal, that we store in the <tt/tictactoe_signals/ array, which we +index using an enumeration. (Conventionally, the enumeration elements +are the signal name, uppercased, but here there would be a conflict +with the <tt/TICTACTOE()/ macro, so we called it <tt/TICTACTOE_SIGNAL/ +instead. + +After creating our signals, we need to tell GTK to associate our +signals with the Tictactoe class. We do that by calling +<tt/gtk_object_class_add_signals()/. We then set the pointer which +points to the default handler for the ``tictactoe'' signal to NULL, +indicating that there is no default action. + +<!-- ----------------------------------------------------------------- --> +<sect2> The <tt/_init()/ function. + +<p> + +Each widget class also needs a function to initialize the object +structure. Usually, this function has the fairly limited role of +setting the fields of the structure to default values. For composite +widgets, however, this function also creates the component widgets. + +<tscreen><verb> + +static void +tictactoe_init (Tictactoe *ttt) +{ + GtkWidget *table; + gint i,j; + + table = gtk_table_new (3, 3, TRUE); + gtk_container_add (GTK_CONTAINER(ttt), table); + gtk_widget_show (table); + + for (i=0;i<3; i++) + for (j=0;j<3; j++) + { + ttt->buttons[i][j] = gtk_toggle_button_new (); + gtk_table_attach_defaults (GTK_TABLE(table), ttt->buttons[i][j], + i, i+1, j, j+1); + gtk_signal_connect (GTK_OBJECT (ttt->buttons[i][j]), "toggled", + GTK_SIGNAL_FUNC (tictactoe_toggle), ttt); + gtk_widget_set_usize (ttt->buttons[i][j], 20, 20); + gtk_widget_show (ttt->buttons[i][j]); + } +} +</verb></tscreen> + +<!-- ----------------------------------------------------------------- --> +<sect2> And the rest... + +<p> + +There is one more function that every widget (except for base widget +types like GtkBin that cannot be instantiated) needs to have - the +function that the user calls to create an object of that type. This is +conventionally called <tt/WIDGETNAME_new()/In some +widgets, thought not for the Tictactoe widgets, this function takes +arguments, and does some setup based on the arguments. The other two +functions are specific to the Tictactoe widget. + +<p> +<tt/tictactoe_clear()/ is a public function that resets all the +buttons in the widget to the up position. Note the use of +<tt/gtk_signal_handler_block_by_data()/ to keep our signal handler for +button toggles from being triggered unnecessarily. + +<p> +<tt/tictactoe_toggle()/ is the signal handler that is invoked when the +user clicks on a button. It checks to see if there are any winning +combinations that involve the toggled button, and if so, emits +the "tictactoe" signal. + +<tscreen><verb> +GtkWidget* +tictactoe_new () +{ + return GTK_WIDGET ( gtk_type_new (tictactoe_get_type ())); +} + +void +tictactoe_clear (Tictactoe *ttt) +{ + int i,j; + + for (i=0;i<3;i++) + for (j=0;j<3;j++) + { + gtk_signal_handler_block_by_data (GTK_OBJECT(ttt->buttons[i][j]), ttt); + gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON (ttt->buttons[i][j]), + FALSE); + gtk_signal_handler_unblock_by_data (GTK_OBJECT(ttt->buttons[i][j]), ttt); + } +} + +static void +tictactoe_toggle (GtkWidget *widget, Tictactoe *ttt) +{ + int i,k; + + static int rwins[8][3] = { { 0, 0, 0 }, { 1, 1, 1 }, { 2, 2, 2 }, + { 0, 1, 2 }, { 0, 1, 2 }, { 0, 1, 2 }, + { 0, 1, 2 }, { 0, 1, 2 } }; + static int cwins[8][3] = { { 0, 1, 2 }, { 0, 1, 2 }, { 0, 1, 2 }, + { 0, 0, 0 }, { 1, 1, 1 }, { 2, 2, 2 }, + { 0, 1, 2 }, { 2, 1, 0 } }; + + int success, found; + + for (k=0; k<8; k++) + { + success = TRUE; + found = FALSE; + + for (i=0;i<3;i++) + { + success = success && + GTK_TOGGLE_BUTTON(ttt->buttons[rwins[k][i]][cwins[k][i]])->active; + found = found || + ttt->buttons[rwins[k][i]][cwins[k][i]] == widget; + } + + if (success && found) + { + gtk_signal_emit (GTK_OBJECT (ttt), + tictactoe_signals[TICTACTOE_SIGNAL]); + break; + } + } +} +</verb></tscreen> + +<p> + +And finally, an example program using our Tictactoe widget: + +<tscreen><verb> +#include <gtk/gtk.h> +#include "tictactoe.h" + +/* Invoked when a row, column or diagonal is completed */ +void +win (GtkWidget *widget, gpointer data) +{ + g_print ("Yay!\n"); + tictactoe_clear (TICTACTOE (widget)); +} + +int +main (int argc, char *argv[]) +{ + GtkWidget *window; + GtkWidget *ttt; + + gtk_init (&argc, &argv); + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + + gtk_window_set_title (GTK_WINDOW (window), "Aspect Frame"); + + gtk_signal_connect (GTK_OBJECT (window), "destroy", + GTK_SIGNAL_FUNC (gtk_exit), NULL); + + gtk_container_border_width (GTK_CONTAINER (window), 10); + + /* Create a new Tictactoe widget */ + ttt = tictactoe_new (); + gtk_container_add (GTK_CONTAINER (window), ttt); + gtk_widget_show (ttt); + + /* And attach to its "tictactoe" signal */ + gtk_signal_connect (GTK_OBJECT (ttt), "tictactoe", + GTK_SIGNAL_FUNC (win), NULL); + + gtk_widget_show (window); + + gtk_main (); + + return 0; +} + +</verb></tscreen> + +<!-- ----------------------------------------------------------------- --> +<sect1> Creating a widget from scratch. + +<!-- ----------------------------------------------------------------- --> +<sect2> Introduction + +<p> + +In this section, we'll learn more about how widgets display themselves +on the screen and interact with events. As an example of this, we'll +create a analog dial widget with a pointer that the user can drag to +set the value. + +<!-- ----------------------------------------------------------------- --> +<sect2> Displaying a widget on the screen + +<p> +There are several steps that are involved in displaying on the screen. +After the widget is created with a call to <tt/WIDGETNAME_new()/, +several more functions are needed: + +<itemize> +<item> <tt/WIDGETNAME_realize()/ is responsible for creating an X +window for the widget if it has one. +<item> <tt/WIDGETNAME_map()/ is invoked after the user calls +<tt/gtk_widget_show()/. It is responsible for making sure the widget +is actually drawn on the screen (<em/mapped/). For a container class, +it must also make calls to <tt/map()/> functions of any child widgets. +<item> <tt/WIDGETNAME_draw()/ is invoked when <tt/gtk_widget_draw()/ +is called for the widget or one of its ancestors. It makes the actual +calls to the drawing functions to draw the widget on the screen. For +container widgets, this function must make calls to +<tt/gtk_widget_draw()/ for its child widgets. +<item> <tt/WIDGETNAME_expose()/ is a handler for expose events for the +widget. It makes the necessary calls to the drawing functions to draw +the exposed portion on the screen. For container widgets, this +function must generate expose events for its child widgets which don't +have their own windows. (If they have their own windows, then X will +generate the necessary expose events) +</itemize> + +<p> +You might notice that the last two functions are quite similar - each +is responsible for drawing the widget on the screen. In fact many +types of widgets don't really care about the difference between the +two. The default <tt/draw()/ function in the widget class simply +generates a synthetic expose event for the redrawn area. However, some +types of widgets can save work by distinguishing between the two +functions. For instance, if a widget has multiple X windows, then +since expose events identify the exposed window, it can redraw only +the affected window, which is not possible for calls to <tt/draw()/. + +<p> +Container widgets, even if they don't care about the difference for +themselves, can't simply use the default <tt/draw()/ function because +their child widgets might care about the difference. However, +it would be wasteful to duplicate the drawing code between the two +functions. The convention is that such widgets have a function called +<tt/WIDGETNAME_paint()/ that does the actual work of drawing the +widget, that is then called by the <tt/draw()/ and <tt/expose()/ +functions. + +<p> +In our example approach, since the dial widget is not a container +widget, and only has a single window, we can take the simplest +approach and use the default <tt/draw()/ function and only implement +an <tt/expose()/ function. + +<!-- ----------------------------------------------------------------- --> +<sect2> The origins of the Dial Widget + +<p> +Just as all land animals are just variants on the first amphibian that +crawled up out of the mud, Gtk widgets tend to start off as variants +of some other, previously written widget. Thus, although this section +is entilted ``Creating a Widget from Scratch'', the Dial widget really +began with the source code for the Range widget. This was picked as a +starting point because it would be nice if our Dial had the same +interface as the Scale widgets which are just specialized descendents +of the Range widget. So, though the source code is presented below in +finished form, it should not be implied that it was written, <em>deus +ex machina</em> in this fashion. Also, if you aren't yet familiar with +how scale widgets work from the application writer's point of view, it +would be a good idea to look them over before continuing. + +<!-- ----------------------------------------------------------------- --> +<sect2> The Basics + +<p> +Quite a bit of our widget should look pretty familiar from the +Tictactoe widget. First, we have a header file: + +<tscreen><verb> +/* GTK - The GIMP Toolkit + * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free + * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef __GTK_DIAL_H__ +#define __GTK_DIAL_H__ + +#include <gdk/gdk.h> +#include <gtk/gtkadjustment.h> +#include <gtk/gtkwidget.h> + + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +#define GTK_DIAL(obj) GTK_CHECK_CAST (obj, gtk_dial_get_type (), GtkDial) +#define GTK_DIAL_CLASS(klass) GTK_CHECK_CLASS_CAST (klass, gtk_dial_get_type (), GtkDialClass) +#define GTK_IS_DIAL(obj) GTK_CHECK_TYPE (obj, gtk_dial_get_type ()) + + +typedef struct _GtkDial GtkDial; +typedef struct _GtkDialClass GtkDialClass; + +struct _GtkDial +{ + GtkWidget widget; + + /* update policy (GTK_UPDATE_[CONTINUOUS/DELAYED/DISCONTINUOUS]) */ + guint policy : 2; + + /* Button currently pressed or 0 if none */ + guint8 button; + + /* Dimensions of dial components */ + gint radius; + gint pointer_width; + + /* ID of update timer, or 0 if none */ + guint32 timer; + + /* Current angle */ + gfloat angle; + + /* Old values from adjustment stored so we know when something changes */ + gfloat old_value; + gfloat old_lower; + gfloat old_upper; + + /* The adjustment object that stores the data for this dial */ + GtkAdjustment *adjustment; +}; + +struct _GtkDialClass +{ + GtkWidgetClass parent_class; +}; + + +GtkWidget* gtk_dial_new (GtkAdjustment *adjustment); +guint gtk_dial_get_type (void); +GtkAdjustment* gtk_dial_get_adjustment (GtkDial *dial); +void gtk_dial_set_update_policy (GtkDial *dial, + GtkUpdateType policy); + +void gtk_dial_set_adjustment (GtkDial *dial, + GtkAdjustment *adjustment); +#ifdef __cplusplus +} +#endif /* __cplusplus */ + + +#endif /* __GTK_DIAL_H__ */ + +</verb></tscreen> + +Since there is quite a bit more going on in this widget, than the last +one, we have more fields in the data structure, but otherwise things +are pretty similar. + +<p> + +Next, after including header files, and declaring a few constants, +we have some functions to provide information about the widget +and initialize it: + +<tscreen><verb> +#include <math.h> +#include <stdio.h> +#include <gtk/gtkmain.h> +#include <gtk/gtksignal.h> + +#include "gtkdial.h" + +#define SCROLL_DELAY_LENGTH 300 +#define DIAL_DEFAULT_SIZE 100 + +/* Forward declararations */ + +[ omitted to save space ] + +/* Local data */ + +static GtkWidgetClass *parent_class = NULL; + +guint +gtk_dial_get_type () +{ + static guint dial_type = 0; + + if (!dial_type) + { + GtkTypeInfo dial_info = + { + "GtkDial", + sizeof (GtkDial), + sizeof (GtkDialClass), + (GtkClassInitFunc) gtk_dial_class_init, + (GtkObjectInitFunc) gtk_dial_init, + (GtkArgFunc) NULL, + }; + + dial_type = gtk_type_unique (gtk_widget_get_type (), &dial_info); + } + + return dial_type; +} + +static void +gtk_dial_class_init (GtkDialClass *class) +{ + GtkObjectClass *object_class; + GtkWidgetClass *widget_class; + + object_class = (GtkObjectClass*) class; + widget_class = (GtkWidgetClass*) class; + + parent_class = gtk_type_class (gtk_widget_get_type ()); + + object_class->destroy = gtk_dial_destroy; + + widget_class->realize = gtk_dial_realize; + widget_class->expose_event = gtk_dial_expose; + widget_class->size_request = gtk_dial_size_request; + widget_class->size_allocate = gtk_dial_size_allocate; + widget_class->button_press_event = gtk_dial_button_press; + widget_class->button_release_event = gtk_dial_button_release; + widget_class->motion_notify_event = gtk_dial_motion_notify; +} + +static void +gtk_dial_init (GtkDial *dial) +{ + dial->button = 0; + dial->policy = GTK_UPDATE_CONTINUOUS; + dial->timer = 0; + dial->radius = 0; + dial->pointer_width = 0; + dial->angle = 0.0; + dial->old_value = 0.0; + dial->old_lower = 0.0; + dial->old_upper = 0.0; + dial->adjustment = NULL; +} + +GtkWidget* +gtk_dial_new (GtkAdjustment *adjustment) +{ + GtkDial *dial; + + dial = gtk_type_new (gtk_dial_get_type ()); + + if (!adjustment) + adjustment = (GtkAdjustment*) gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0); + + gtk_dial_set_adjustment (dial, adjustment); + + return GTK_WIDGET (dial); +} + +static void +gtk_dial_destroy (GtkObject *object) +{ + GtkDial *dial; + + g_return_if_fail (object != NULL); + g_return_if_fail (GTK_IS_DIAL (object)); + + dial = GTK_DIAL (object); + + if (dial->adjustment) + gtk_object_unref (GTK_OBJECT (dial->adjustment)); + + if (GTK_OBJECT_CLASS (parent_class)->destroy) + (* GTK_OBJECT_CLASS (parent_class)->destroy) (object); +} +</verb></tscreen> + +Note that this <tt/init()/ function does less than for the Tictactoe +widget, since this is not a composite widget, and the <tt/new()/ +function does more, since it now has an argument. Also, note that when +we store a pointer to the Adjustment object, we increment its +reference count, (and correspondingly decrement when we no longer use +it) so that GTK can keep track of when it can be safely destroyed. + +<p> +Also, there are a few function to manipulate the widget's options: + +<tscreen><verb> +GtkAdjustment* +gtk_dial_get_adjustment (GtkDial *dial) +{ + g_return_val_if_fail (dial != NULL, NULL); + g_return_val_if_fail (GTK_IS_DIAL (dial), NULL); + + return dial->adjustment; +} + +void +gtk_dial_set_update_policy (GtkDial *dial, + GtkUpdateType policy) +{ + g_return_if_fail (dial != NULL); + g_return_if_fail (GTK_IS_DIAL (dial)); + + dial->policy = policy; +} + +void +gtk_dial_set_adjustment (GtkDial *dial, + GtkAdjustment *adjustment) +{ + g_return_if_fail (dial != NULL); + g_return_if_fail (GTK_IS_DIAL (dial)); + + if (dial->adjustment) + { + gtk_signal_disconnect_by_data (GTK_OBJECT (dial->adjustment), (gpointer) dial); + gtk_object_unref (GTK_OBJECT (dial->adjustment)); + } + + dial->adjustment = adjustment; + gtk_object_ref (GTK_OBJECT (dial->adjustment)); + + gtk_signal_connect (GTK_OBJECT (adjustment), "changed", + (GtkSignalFunc) gtk_dial_adjustment_changed, + (gpointer) dial); + gtk_signal_connect (GTK_OBJECT (adjustment), "value_changed", + (GtkSignalFunc) gtk_dial_adjustment_value_changed, + (gpointer) dial); + + dial->old_value = adjustment->value; + dial->old_lower = adjustment->lower; + dial->old_upper = adjustment->upper; + + gtk_dial_update (dial); +} +</verb></tscreen> + +<sect2> <tt/gtk_dial_realize()/ + +<p> +Now we come to some new types of functions. First, we have a function +that does the work of creating the X window. Notice that a mask is +passed to the function <tt/gdk_window_new()/ which specifies which fields of +the GdkWindowAttr structure actually have data in them (the remaining +fields wll be given default values). Also worth noting is the way the +event mask of the widget is created. We call +<tt/gtk_widget_get_events()/ to retrieve the event mask that the user +has specified for this widget (with <tt/gtk_widget_set_events()/, and +add the events that we are interested in ourselves. + +<p> +After creating the window, we set its style and background, and put a +pointer to the widget in the user data field of the GdkWindow. This +last step allows GTK to dispatch events for this window to the correct +widget. + +<tscreen><verb> +static void +gtk_dial_realize (GtkWidget *widget) +{ + GtkDial *dial; + GdkWindowAttr attributes; + gint attributes_mask; + + g_return_if_fail (widget != NULL); + g_return_if_fail (GTK_IS_DIAL (widget)); + + GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED); + dial = GTK_DIAL (widget); + + attributes.x = widget->allocation.x; + attributes.y = widget->allocation.y; + attributes.width = widget->allocation.width; + attributes.height = widget->allocation.height; + attributes.wclass = GDK_INPUT_OUTPUT; + attributes.window_type = GDK_WINDOW_CHILD; + attributes.event_mask = gtk_widget_get_events (widget) | + GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK | + GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK | + GDK_POINTER_MOTION_HINT_MASK; + attributes.visual = gtk_widget_get_visual (widget); + attributes.colormap = gtk_widget_get_colormap (widget); + + attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP; + widget->window = gdk_window_new (widget->parent->window, &attributes, attributes_mask); + + widget->style = gtk_style_attach (widget->style, widget->window); + + gdk_window_set_user_data (widget->window, widget); + + gtk_style_set_background (widget->style, widget->window, GTK_STATE_ACTIVE); +} +</verb></tscreen> + +<sect2> Size negotiation + +<p> +Before the first time that the window containing a widget is +displayed, and whenever the layout of the window changes, GTK asks +each child widget for its desired size. This request is handled by the +function, <tt/gtk_dial_size_request()/. Since our widget isn't a +container widget, and has no real constraints on its size, we just +return a reasonable default value. + +<tscreen><verb> +static void +gtk_dial_size_request (GtkWidget *widget, + GtkRequisition *requisition) +{ + requisition->width = DIAL_DEFAULT_SIZE; + requisition->height = DIAL_DEFAULT_SIZE; +} +</verb></tscreen> + +<p> +After all the widgets have requested an ideal size, the layout of the +window is computed and each child widget is notified of its actual +size. Usually, this will at least as large as the requested size, but +if for instance, the user has resized the window, it may occasionally +be smaller than the requested size. The size notification is handled +by the function <tt/gtk_dial_size_allocate()/. Notice that as well as +computing the sizes of some component pieces for future use, this +routine also does the grunt work of moving the widgets X window into +the new position and size. + +<tscreen><verb> +static void +gtk_dial_size_allocate (GtkWidget *widget, + GtkAllocation *allocation) +{ + GtkDial *dial; + + g_return_if_fail (widget != NULL); + g_return_if_fail (GTK_IS_DIAL (widget)); + g_return_if_fail (allocation != NULL); + + widget->allocation = *allocation; + if (GTK_WIDGET_REALIZED (widget)) + { + dial = GTK_DIAL (widget); + + gdk_window_move_resize (widget->window, + allocation->x, allocation->y, + allocation->width, allocation->height); + + dial->radius = MAX(allocation->width,allocation->height) * 0.45; + dial->pointer_width = dial->radius / 5; + } +} +</verb></tscreen>. + +<!-- ----------------------------------------------------------------- --> +<sect2> <tt/gtk_dial_expose()/ + +<p> +As mentioned above, all the drawing of this widget is done in the +handler for expose events. There's not much to remark on here except +the use of the function <tt/gtk_draw_polygon/ to draw the pointer with +three dimensional shading according to the colors stored in the +widget's style. + +<tscreen><verb> +static gint +gtk_dial_expose (GtkWidget *widget, + GdkEventExpose *event) +{ + GtkDial *dial; + GdkPoint points[3]; + gdouble s,c; + gdouble theta; + gint xc, yc; + gint tick_length; + gint i; + + g_return_val_if_fail (widget != NULL, FALSE); + g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE); + g_return_val_if_fail (event != NULL, FALSE); + + if (event->count > 0) + return FALSE; + + dial = GTK_DIAL (widget); + + gdk_window_clear_area (widget->window, + 0, 0, + widget->allocation.width, + widget->allocation.height); + + xc = widget->allocation.width/2; + yc = widget->allocation.height/2; + + /* Draw ticks */ + + for (i=0; i<25; i++) + { + theta = (i*M_PI/18. - M_PI/6.); + s = sin(theta); + c = cos(theta); + + tick_length = (i%6 == 0) ? dial->pointer_width : dial->pointer_width/2; + + gdk_draw_line (widget->window, + widget->style->fg_gc[widget->state], + xc + c*(dial->radius - tick_length), + yc - s*(dial->radius - tick_length), + xc + c*dial->radius, + yc - s*dial->radius); + } + + /* Draw pointer */ + + s = sin(dial->angle); + c = cos(dial->angle); + + + points[0].x = xc + s*dial->pointer_width/2; + points[0].y = yc + c*dial->pointer_width/2; + points[1].x = xc + c*dial->radius; + points[1].y = yc - s*dial->radius; + points[2].x = xc - s*dial->pointer_width/2; + points[2].y = yc - c*dial->pointer_width/2; + + gtk_draw_polygon (widget->style, + widget->window, + GTK_STATE_NORMAL, + GTK_SHADOW_OUT, + points, 3, + TRUE); + + return FALSE; +} +</verb></tscreen> + +<!-- ----------------------------------------------------------------- --> +<sect2> Event handling + +<p> + +The rest of the widget's code handles various types of events, and +isn't too different from what would be found in many GTK +applications. Two types of events can occur - either the user can +click on the widget with the mouse and drag to move the pointer, or +the value of the Adjustment object can change due to some external +circumstance. + +<p> +When the user clicks on the widget, we check to see if the click was +appropriately near the pointer, and if so, store then button that the +user clicked with in the <tt/button/ field of the widget +structure, and grab all mouse events with a call to +<tt/gtk_grab_add()/. Subsequent motion of the mouse causes the +value of the control to be recomputed (by the function +<tt/gtk_dial_update_mouse/). Depending on the policy that has been +set, "value_changed" events are either generated instantly +(<tt/GTK_UPDATE_CONTINUOUS/), after a delay in a timer added with +<tt/gtk_timeout_add()/ (<tt/GTK_UPDATE_DELAYED/), or only when the +button is released (<tt/GTK_UPDATE_DISCONTINUOUS/). + +<tscreen><verb> +static gint +gtk_dial_button_press (GtkWidget *widget, + GdkEventButton *event) +{ + GtkDial *dial; + gint dx, dy; + double s, c; + double d_parallel; + double d_perpendicular; + + g_return_val_if_fail (widget != NULL, FALSE); + g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE); + g_return_val_if_fail (event != NULL, FALSE); + + dial = GTK_DIAL (widget); + + /* Determine if button press was within pointer region - we + do this by computing the parallel and perpendicular distance of + the point where the mouse was pressed from the line passing through + the pointer */ + + dx = event->x - widget->allocation.width / 2; + dy = widget->allocation.height / 2 - event->y; + + s = sin(dial->angle); + c = cos(dial->angle); + + d_parallel = s*dy + c*dx; + d_perpendicular = fabs(s*dx - c*dy); + + if (!dial->button && + (d_perpendicular < dial->pointer_width/2) && + (d_parallel > - dial->pointer_width)) + { + gtk_grab_add (widget); + + dial->button = event->button; + + gtk_dial_update_mouse (dial, event->x, event->y); + } + + return FALSE; +} + +static gint +gtk_dial_button_release (GtkWidget *widget, + GdkEventButton *event) +{ + GtkDial *dial; + + g_return_val_if_fail (widget != NULL, FALSE); + g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE); + g_return_val_if_fail (event != NULL, FALSE); + + dial = GTK_DIAL (widget); + + if (dial->button == event->button) + { + gtk_grab_remove (widget); + + dial->button = 0; + + if (dial->policy == GTK_UPDATE_DELAYED) + gtk_timeout_remove (dial->timer); + + if ((dial->policy != GTK_UPDATE_CONTINUOUS) && + (dial->old_value != dial->adjustment->value)) + gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), "value_changed"); + } + + return FALSE; +} + +static gint +gtk_dial_motion_notify (GtkWidget *widget, + GdkEventMotion *event) +{ + GtkDial *dial; + GdkModifierType mods; + gint x, y, mask; + + g_return_val_if_fail (widget != NULL, FALSE); + g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE); + g_return_val_if_fail (event != NULL, FALSE); + + dial = GTK_DIAL (widget); + + if (dial->button != 0) + { + x = event->x; + y = event->y; + + if (event->is_hint || (event->window != widget->window)) + gdk_window_get_pointer (widget->window, &x, &y, &mods); + + switch (dial->button) + { + case 1: + mask = GDK_BUTTON1_MASK; + break; + case 2: + mask = GDK_BUTTON2_MASK; + break; + case 3: + mask = GDK_BUTTON3_MASK; + break; + default: + mask = 0; + break; + } + + if (mods & mask) + gtk_dial_update_mouse (dial, x,y); + } + + return FALSE; +} + +static gint +gtk_dial_timer (GtkDial *dial) +{ + g_return_val_if_fail (dial != NULL, FALSE); + g_return_val_if_fail (GTK_IS_DIAL (dial), FALSE); + + if (dial->policy == GTK_UPDATE_DELAYED) + gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), "value_changed"); + + return FALSE; +} + +static void +gtk_dial_update_mouse (GtkDial *dial, gint x, gint y) +{ + gint xc, yc; + gfloat old_value; + + g_return_if_fail (dial != NULL); + g_return_if_fail (GTK_IS_DIAL (dial)); + + xc = GTK_WIDGET(dial)->allocation.width / 2; + yc = GTK_WIDGET(dial)->allocation.height / 2; + + old_value = dial->adjustment->value; + dial->angle = atan2(yc-y, x-xc); + + if (dial->angle < -M_PI/2.) + dial->angle += 2*M_PI; + + if (dial->angle < -M_PI/6) + dial->angle = -M_PI/6; + + if (dial->angle > 7.*M_PI/6.) + dial->angle = 7.*M_PI/6.; + + dial->adjustment->value = dial->adjustment->lower + (7.*M_PI/6 - dial->angle) * + (dial->adjustment->upper - dial->adjustment->lower) / (4.*M_PI/3.); + + if (dial->adjustment->value != old_value) + { + if (dial->policy == GTK_UPDATE_CONTINUOUS) + { + gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), "value_changed"); + } + else + { + gtk_widget_draw (GTK_WIDGET(dial), NULL); + + if (dial->policy == GTK_UPDATE_DELAYED) + { + if (dial->timer) + gtk_timeout_remove (dial->timer); + + dial->timer = gtk_timeout_add (SCROLL_DELAY_LENGTH, + (GtkFunction) gtk_dial_timer, + (gpointer) dial); + } + } + } +} +</verb></tscreen> + +<p> +Changes to the Adjustment by external means are communicated to our +widget by the ``changed'' and ``value_changed'' signals. The handlers +for these functions call <tt/gtk_dial_update()/ to validate the +arguments, compute the new pointer angle, and redraw the widget (by +calling <tt/gtk_widget_draw()/). + +<tscreen><verb> +static void +gtk_dial_update (GtkDial *dial) +{ + gfloat new_value; + + g_return_if_fail (dial != NULL); + g_return_if_fail (GTK_IS_DIAL (dial)); + + new_value = dial->adjustment->value; + + if (new_value < dial->adjustment->lower) + new_value = dial->adjustment->lower; + + if (new_value > dial->adjustment->upper) + new_value = dial->adjustment->upper; + + if (new_value != dial->adjustment->value) + { + dial->adjustment->value = new_value; + gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), "value_changed"); + } + + dial->angle = 7.*M_PI/6. - (new_value - dial->adjustment->lower) * 4.*M_PI/3. / + (dial->adjustment->upper - dial->adjustment->lower); + + gtk_widget_draw (GTK_WIDGET(dial), NULL); +} + +static void +gtk_dial_adjustment_changed (GtkAdjustment *adjustment, + gpointer data) +{ + GtkDial *dial; + + g_return_if_fail (adjustment != NULL); + g_return_if_fail (data != NULL); + + dial = GTK_DIAL (data); + + if ((dial->old_value != adjustment->value) || + (dial->old_lower != adjustment->lower) || + (dial->old_upper != adjustment->upper)) + { + gtk_dial_update (dial); + + dial->old_value = adjustment->value; + dial->old_lower = adjustment->lower; + dial->old_upper = adjustment->upper; + } +} + +static void +gtk_dial_adjustment_value_changed (GtkAdjustment *adjustment, + gpointer data) +{ + GtkDial *dial; + + g_return_if_fail (adjustment != NULL); + g_return_if_fail (data != NULL); + + dial = GTK_DIAL (data); + + if (dial->old_value != adjustment->value) + { + gtk_dial_update (dial); + + dial->old_value = adjustment->value; + } +} +</verb></tscreen> + +<!-- ----------------------------------------------------------------- --> +<sect2> Possible Enhancements + +<p> + +The Dial widget as we've described it so far runs about 670 lines of +code. Although that might sound like a fair bit, we've really +accomplished quite a bit with that much code, especially since much of +that length is headers and boilerplate. However, there are quite a few +more enhancements that could be made to this widget: + +<itemize> +<item> If you try this widget out, you'll find that there is some +flashing as the pointer is dragged around. This is because the entire +widget is erased every time the pointer is moved before being +redrawn. Often, the best way to handle this problem is to draw to an +offscreen pixmap, then copy the final results onto the screen in one +step. (The ProgressBar widget draws itself in this fashion.) + +<item> The user should be able to use the up and down arrow keys to +increase and decrease the value. + +<item> It would be nice if the widget had buttons to increase and +decrease the value in small or large steps. Although it would be +possible to use embedded Button widgets for this, we would also like +the buttons to auto-repeat when held down, as the arrows on a +scrollbar do. Most of the code to implement this type of behavior can +be found in the GtkRange widget. + +<item> The Dial widget could be made into a container widget with a +single child widget positioned at the bottom between the buttons +mentioned above. The user could then add their choice of a label or +entry widget to display the current value of the dial. + +</itemize> + +<!-- ----------------------------------------------------------------- --> +<sect1> Learning More + +<p> +Only a small part of the many details involved in creating widgets +could be described above. If you want to write your own widgets, the +best source of examples is the GTK source itself. Ask yourself some +questions about the widget you want to write: is it a Container +widget? does it have its own window? is it a modification of an +existing widget? Then find a similar widget, and start making changes. +Good luck! + +<!-- ***************************************************************** --> +<sect>Scribble, A Simple Example Drawing Program +<!-- ***************************************************************** --> + +<!-- ----------------------------------------------------------------- --> +<sect1> Overview + +<p> +In this section, we will build a simple drawing program. In the +process, we will examine how to handle mouse events, how to draw in a +window, and how to do drawing better by using a backing pixmap. After +creating the simple drawing program, we will extend it by adding +support for XInput devices, such as drawing tablets. GTK provides +support routines which makes getting extended information, such as +pressure and tilt, from such devices quite easy. + +<!-- ----------------------------------------------------------------- --> +<sect1> Event Handling + +<p> +The GTK signals we have already discussed are for high-level actions, +such as a menu item being selected. However, sometimes it is useful to +learn about lower-level occurrences, such as the mouse being moved, or +a key being pressed. There are also GTK signals corresponding to these +low-level <em>events</em>. The handlers for these signals have an +extra parameter which is a pointer to a structure containing +information about the event. For instance, motion events handlers are +passed a pointer to a GdkEventMotion structure which looks (in part) +like: + +<tscreen><verb> +struct _GdkEventMotion +{ + GdkEventType type; + GdkWindow *window; + guint32 time; + gdouble x; + gdouble y; + ... + guint state; + ... +}; +</verb></tscreen> + +<tt/type/ will be set to the event type, in this case +<tt/GDK_MOTION_NOTIFY/, window is the window in which the event +occured. <tt/x/ and <tt/y/ give the coordinates of the event, +and <tt/state/ specifies the modifier state when the event +occurred (that is, it specifies which modifier keys and mouse buttons +were pressed.) It is the bitwise OR of some of the following: + +<tscreen><verb> +GDK_SHIFT_MASK +GDK_LOCK_MASK +GDK_CONTROL_MASK +GDK_MOD1_MASK +GDK_MOD2_MASK +GDK_MOD3_MASK +GDK_MOD4_MASK +GDK_MOD5_MASK +GDK_BUTTON1_MASK +GDK_BUTTON2_MASK +GDK_BUTTON3_MASK +GDK_BUTTON4_MASK +GDK_BUTTON5_MASK +</verb></tscreen> + +<p> +As for other signals, to determine what happens when an event occurs +we call <tt>gtk_signal_connect()</tt>. But we also need let GTK +know which events we want to be notified about. To do this, we call +the function: + +<tscreen><verb> +void gtk_widget_set_events (GtkWidget *widget, + gint events); +</verb></tscreen> + +The second field specifies the events we are interested in. It +is the bitwise OR of constants that specify different types +of events. For future reference the event types are: + +<tscreen><verb> +GDK_EXPOSURE_MASK +GDK_POINTER_MOTION_MASK +GDK_POINTER_MOTION_HINT_MASK +GDK_BUTTON_MOTION_MASK +GDK_BUTTON1_MOTION_MASK +GDK_BUTTON2_MOTION_MASK +GDK_BUTTON3_MOTION_MASK +GDK_BUTTON_PRESS_MASK +GDK_BUTTON_RELEASE_MASK +GDK_KEY_PRESS_MASK +GDK_KEY_RELEASE_MASK +GDK_ENTER_NOTIFY_MASK +GDK_LEAVE_NOTIFY_MASK +GDK_FOCUS_CHANGE_MASK +GDK_STRUCTURE_MASK +GDK_PROPERTY_CHANGE_MASK +GDK_PROXIMITY_IN_MASK +GDK_PROXIMITY_OUT_MASK +</verb></tscreen> + +There are a few subtle points that have to be observed when calling +<tt/gtk_widget_set_events()/. First, it must be called before the X window +for a GTK widget is created. In practical terms, this means you +should call it immediately after creating the widget. Second, the +widget must have an associated X window. For efficiency, many widget +types do not have their own window, but draw in their parent's window. +These widgets are: + +<tscreen><verb> +GtkAlignment +GtkArrow +GtkBin +GtkBox +GtkImage +GtkItem +GtkLabel +GtkPaned +GtkPixmap +GtkScrolledWindow +GtkSeparator +GtkTable +GtkViewport +GtkAspectFrame +GtkFrame +GtkVPaned +GtkHPaned +GtkVBox +GtkHBox +GtkVSeparator +GtkHSeparator +</verb></tscreen> + +To capture events for these widgets, you need to use an EventBox +widget. See the section on +<ref id="sec_The_EventBox_Widget" name="The EventBox Widget"> for +details. + +<p> +For our drawing program, we want to know when the mouse button is +pressed and when the mouse is moved, so we specify +<tt/GDK_POINTER_MOTION_MASK/ and <tt/GDK_BUTTON_PRESS_MASK/. We also +want to know when we need to redraw our window, so we specify +<tt/GDK_EXPOSURE_MASK/. Although we want to be notified via a +Configure event when our window size changes, we don't have to specify +the corresponding <tt/GDK_STRUCTURE_MASK/ flag, because it is +automatically specified for all windows. + +<p> +It turns out, however, that there is a problem with just specifying +<tt/GDK_POINTER_MOTION_MASK/. This will cause the server to add a new +motion event to the event queue every time the user moves the mouse. +Imagine that it takes us 0.1 seconds to handle a motion event, but the +X server queues a new motion event every 0.05 seconds. We will soon +get way behind the users drawing. If the user draws for 5 seconds, +it will take us another 5 seconds to catch up after they release +the mouse button! What we would like is to only get one motion +event for each event we process. The way to do this is to +specify <tt/GDK_POINTER_MOTION_HINT_MASK/. + +<p> +When we specify <tt/GDK_POINTER_MOTION_HINT_MASK/, the server sends +us a motion event the first time the pointer moves after entering +our window, or after a button press or release event. Subsequent +motion events will be suppressed until we explicitely ask for +the position of the pointer using the function: + +<tscreen><verb> +GdkWindow* gdk_window_get_pointer (GdkWindow *window, + gint *x, + gint *y, + GdkModifierType *mask); +</verb></tscreen> + +(There is another function, <tt>gtk_widget_get_pointer()</tt> which +has a simpler interface, but turns out not to be very useful, since +it only retrieves the position of the mouse, not whether the buttons +are pressed.) + +<p> +The code to set the events for our window then looks like: + +<tscreen><verb> + gtk_signal_connect (GTK_OBJECT (drawing_area), "expose_event", + (GtkSignalFunc) expose_event, NULL); + gtk_signal_connect (GTK_OBJECT(drawing_area),"configure_event", + (GtkSignalFunc) configure_event, NULL); + gtk_signal_connect (GTK_OBJECT (drawing_area), "motion_notify_event", + (GtkSignalFunc) motion_notify_event, NULL); + gtk_signal_connect (GTK_OBJECT (drawing_area), "button_press_event", + (GtkSignalFunc) button_press_event, NULL); + + gtk_widget_set_events (drawing_area, GDK_EXPOSURE_MASK + | GDK_LEAVE_NOTIFY_MASK + | GDK_BUTTON_PRESS_MASK + | GDK_POINTER_MOTION_MASK + | GDK_POINTER_MOTION_HINT_MASK); +</verb></tscreen> + +We'll save the "expose_event" and "configure_event" handlers for +later. The "motion_notify_event" and "button_press_event" handlers +pretty simple: + +<tscreen><verb> +static gint +button_press_event (GtkWidget *widget, GdkEventButton *event) +{ + if (event->button == 1 && pixmap != NULL) + draw_brush (widget, event->x, event->y); + + return TRUE; +} + +static gint +motion_notify_event (GtkWidget *widget, GdkEventMotion *event) +{ + int x, y; + GdkModifierType state; + + if (event->is_hint) + gdk_window_get_pointer (event->window, &x, &y, &state); + else + { + x = event->x; + y = event->y; + state = event->state; + } + + if (state & GDK_BUTTON1_MASK && pixmap != NULL) + draw_brush (widget, x, y); + + return TRUE; +} +</verb></tscreen> + +<!-- ----------------------------------------------------------------- --> +<sect1> The DrawingArea Widget, And Drawing + +<p> +We know turn to the process of drawing on the screen. The +widget we use for this is the DrawingArea widget. A drawing area +widget is essentially an X window and nothing more. It is a blank +canvas in which we can draw whatever we like. A drawing area +is created using the call: + +<tscreen><verb> +GtkWidget* gtk_drawing_area_new (void); +</verb></tscreen> + +A default size for the widget can be specified by calling: + +<tscreen><verb> +void gtk_drawing_area_size (GtkDrawingArea *darea, + gint width, + gint height); +</verb></tscreen> + +This default size can be overriden, as is true for all widgets, +by calling <tt>gtk_widget_set_usize()</tt>, and that, in turn, can +be overridden if the user manually resizes the the window containing +the drawing area. + +<p> +It should be noted that when we create a DrawingArea widget, we are, +<em>completely</em> responsible for drawing the contents. If our +window is obscured then uncovered, we get an exposure event and must +redraw what was previously hidden. + +<p> +Having to remember everything that was drawn on the screen so we +can properly redraw it can, to say the least, be a nuisance. In +addition, it can be visually distracting if portions of the +window are cleared, then redrawn step by step. The solution to +this problem is to use an offscreen <em>backing pixmap</em>. +Instead of drawing directly to the screen, we draw to an image +stored in server memory but not displayed, then when the image +changes or new portions of the image are displayed, we copy the +relevant portions onto the screen. + +<p> +To create an offscreen pixmap, we call the function: + +<tscreen><verb> +GdkPixmap* gdk_pixmap_new (GdkWindow *window, + gint width, + gint height, + gint depth); +</verb></tscreen> + +The <tt>window</tt> parameter specifies a GDK window that this pixmap +takes some of its properties from. <tt>width</tt> and <tt>height</tt> +specify the size of the pixmap. <tt>depth</tt> specifies the <em>color +depth</em>, that is the number of bits per pixel, for the new window. +If the depth is specified as <tt>-1</tt>, it will match the depth +of <tt>window</tt>. + +<p> +We create the pixmap in our "configure_event" handler. This event +is generated whenever the window changes size, including when it +is originally created. + +<tscreen><verb> +/* Backing pixmap for drawing area */ +static GdkPixmap *pixmap = NULL; + +/* Create a new backing pixmap of the appropriate size */ +static gint +configure_event (GtkWidget *widget, GdkEventConfigure *event) +{ + if (pixmap) + { + gdk_pixmap_destroy(pixmap); + } + pixmap = gdk_pixmap_new(widget->window, + widget->allocation.width, + widget->allocation.height, + -1); + gdk_draw_rectangle (pixmap, + widget->style->white_gc, + TRUE, + 0, 0, + widget->allocation.width, + widget->allocation.height); + + return TRUE; +} +</verb></tscreen> + +The call to <tt>gdk_draw_rectangle()</tt> clears the pixmap +initially to white. We'll say more about that in a moment. + +<p> +Our exposure event handler then simply copies the relevant portion +of the pixmap onto the screen (we determine the area we need +to redraw by using the event->area field of the exposure event): + +<tscreen><verb> +/* Refill the screen from the backing pixmap */ +static gint +expose_event (GtkWidget *widget, GdkEventExpose *event) +{ + gdk_draw_pixmap(widget->window, + widget->style->fg_gc[GTK_WIDGET_STATE (widget)], + pixmap, + event->area.x, event->area.y, + event->area.x, event->area.y, + event->area.width, event->area.height); + + return FALSE; +} +</verb></tscreen> + +We've now seen how to keep the screen up to date with our pixmap, but +how do we actually draw interesting stuff on our pixmap? There are a +large number of calls in GTK's GDK library for drawing on +<em>drawables</em>. A drawable is simply something that can be drawn +upon. It can be a window, a pixmap, or a bitmap (a black and white +image). We've already seen two such calls above, +<tt>gdk_draw_rectangle()</tt> and <tt>gdk_draw_pixmap()</tt>. The +complete list is: + +<tscreen><verb> +gdk_draw_line () +gdk_draw_rectangle () +gdk_draw_arc () +gdk_draw_polygon () +gdk_draw_string () +gdk_draw_text () +gdk_draw_pixmap () +gdk_draw_bitmap () +gdk_draw_image () +gdk_draw_points () +gdk_draw_segments () +</verb></tscreen> + +See the reference documentation or the header file +<tt><gdk/gdk.h></tt> for further details on these functions. +These functions all share the same first two arguments. The first +argument is the drawable to draw upon, the second argument is a +<em>graphics context</em> (GC). + +<p> +A graphics context encapsulates information about things such as +foreground and background color and line width. GDK has a full set of +functions for creating and modifying graphics contexts, but to keep +things simple we'll just use predefined graphics contexts. Each widget +has an associated style. (Which can be modified in a gtkrc file, see +the section GTK's rc file.) This, among other things, stores a number +of graphics contexts. Some examples of accessing these graphics +contexts are: + +<tscreen><verb> +widget->style->white_gc +widget->style->black_gc +widget->style->fg_gc[GTK_STATE_NORMAL] +widget->style->bg_gc[GTK_WIDGET_STATE(widget)] +</verb></tscreen> + +The fields <tt>fg_gc</tt>, <tt>bg_gc</tt>, <tt>dark_gc</tt>, and +<tt>light_gc</tt> are indexed by a parameter of type +<tt>GtkStateType</tt> which can take on the values: + +<tscreen><verb> +GTK_STATE_NORMAL, +GTK_STATE_ACTIVE, +GTK_STATE_PRELIGHT, +GTK_STATE_SELECTED, +GTK_STATE_INSENSITIVE +</verb></tscreen> + +For instance, the for <tt/GTK_STATE_SELECTED/ the default foreground +color is white and the default background color, dark blue. + +<p> +Our function <tt>draw_brush()</tt>, which does the actual drawing +on the screen, is then: + +<tscreen><verb> +/* Draw a rectangle on the screen */ +static void +draw_brush (GtkWidget *widget, gdouble x, gdouble y) +{ + GdkRectangle update_rect; + + update_rect.x = x - 5; + update_rect.y = y - 5; + update_rect.width = 10; + update_rect.height = 10; + gdk_draw_rectangle (pixmap, + widget->style->black_gc, + TRUE, + update_rect.x, update_rect.y, + update_rect.width, update_rect.height); + gtk_widget_draw (widget, &update_rect); +} +</verb></tscreen> + +After we draw the rectangle representing the brush onto the pixmap, +we call the function: + +<tscreen><verb> +void gtk_widget_draw (GtkWidget *widget, + GdkRectangle *area); +</verb></tscreen> + +which notifies X that the area given by the <tt>area</tt> parameter +needs to be updated. X will eventually generate an expose event +(possibly combining the areas passed in several calls to +<tt>gtk_widget_draw()</tt>) which will cause our expose event handler +to copy the relevant portions to the screen. + +<p> +We have now covered the entire drawing program except for a few +mundane details like creating the main window. The complete +source code is available from the location from which you got +this tutorial, or from: + +<htmlurl url="http://www.msc.cornell.edu/~otaylor/gtk-gimp/tutorial" +name="http://www.msc.cornell.edu/~otaylor/gtk-gimp/tutorial"> + + +<!-- ----------------------------------------------------------------- --> +<sect1> Adding XInput support + +<p> + +It is now possible to buy quite inexpensive input devices such +as drawing tablets, which allow drawing with a much greater +ease of artistic expression than does a mouse. The simplest way +to use such devices is simply as a replacement for the mouse, +but that misses out many of the advantages of these devices, +such as: + +<itemize> +<item> Pressure sensitivity +<item> Tilt reporting +<item> Sub-pixel positioning +<item> Multiple inputs (for example, a stylus with a point and eraser) +</itemize> + +For information about the XInput extension, see the <htmlurl +url="http://www.msc.cornell.edu/~otaylor/xinput/XInput-HOWTO.html" +name="XInput-HOWTO">. + +<p> +If we examine the full definition of, for example, the GdkEventMotion +structure, we see that it has fields to support extended device +information. + +<tscreen><verb> +struct _GdkEventMotion +{ + GdkEventType type; + GdkWindow *window; + guint32 time; + gdouble x; + gdouble y; + gdouble pressure; + gdouble xtilt; + gdouble ytilt; + guint state; + gint16 is_hint; + GdkInputSource source; + guint32 deviceid; +}; +</verb></tscreen> + +<tt/pressure/ gives the pressure as a floating point number between +0 and 1. <tt/xtilt/ and <tt/ytilt/ can take on values between +-1 and 1, corresponding to the degree of tilt in each direction. +<tt/source/ and <tt/deviceid/ specify the device for which the +event occurred in two different ways. <tt/source/ gives some simple +information about the type of device. It can take the enumeration +values. + +<tscreen><verb> +GDK_SOURCE_MOUSE +GDK_SOURCE_PEN +GDK_SOURCE_ERASER +GDK_SOURCE_CURSOR +</verb></tscreen> + +<tt/deviceid/ specifies a unique numeric ID for the device. This can +be used to find out further information about the device using the +<tt/gdk_input_list_devices()/ call (see below). The special value +<tt/GDK_CORE_POINTER/ is used for the core pointer device. (Usually +the mouse.) + +<sect2> Enabling extended device information + +<p> +To let GTK know about our interest in the extended device information, +we merely have to add a single line to our program: + +<tscreen><verb> +gtk_widget_set_extension_events (drawing_area, GDK_EXTENSION_EVENTS_CURSOR); +</verb></tscreen> + +By giving the value <tt/GDK_EXTENSION_EVENTS_CURSOR/ we say that +we are interested in extension events, but only if we don't have +to draw our own cursor. See the section <ref +id="sec_Further_Sophistications" name="Further Sophistications"> below +for more information about drawing the cursor. We could also +give the values <tt/GDK_EXTENSION_EVENTS_ALL/ if we were willing +to draw our own cursor, or <tt/GDK_EXTENSION_EVENTS_NONE/ to revert +back to the default condition. + +<p> +This is not completely the end of the story however. By default, +no extension devices are enabled. We need a mechanism to allow +users to enable and configure their extension devices. GTK provides +the InputDialog widget to automate this process. The following +procedure manages an InputDialog widget. It creates the dialog if +it isn't present, and raises it to the top otherwise. + +<tscreen><verb> +void +input_dialog_destroy (GtkWidget *w, gpointer data) +{ + *((GtkWidget **)data) = NULL; +} + +void +create_input_dialog () +{ + static GtkWidget *inputd = NULL; + + if (!inputd) + { + inputd = gtk_input_dialog_new(); + + gtk_signal_connect (GTK_OBJECT(inputd), "destroy", + (GtkSignalFunc)input_dialog_destroy, &inputd); + gtk_signal_connect_object (GTK_OBJECT(GTK_INPUT_DIALOG(inputd)->close_button), + "clicked", + (GtkSignalFunc)gtk_widget_hide, + GTK_OBJECT(inputd)); + gtk_widget_hide ( GTK_INPUT_DIALOG(inputd)->save_button); + + gtk_widget_show (inputd); + } + else + { + if (!GTK_WIDGET_MAPPED(inputd)) + gtk_widget_show(inputd); + else + gdk_window_raise(inputd->window); + } +} +</verb></tscreen> + +(You might want to take note of the way we handle this dialog. By +connecting to the "destroy" signal, we make sure that we don't keep a +pointer to dialog around after it is destroyed - that could lead to a +segfault.) + +<p> +The InputDialog has two buttons "Close" and "Save", which by default +have no actions assigned to them. In the above function we make +"Close" hide the dialog, hide the "Save" button, since we don't +implement saving of XInput options in this program. + +<sect2> Using extended device information + +<p> +Once we've enabled the device, we can just use the extended +device information in the extra fields of the event structures. +In fact, it is always safe to use this information since these +fields will have reasonable default values even when extended +events are not enabled. + +<p> +Once change we do have to make is to call +<tt/gdk_input_window_get_pointer()/ instead of +<tt/gdk_window_get_pointer/. This is necessary because +<tt/gdk_window_get_pointer/ doesn't return the extended device +information. + +<tscreen><verb> +void gdk_input_window_get_pointer (GdkWindow *window, + guint32 deviceid, + gdouble *x, + gdouble *y, + gdouble *pressure, + gdouble *xtilt, + gdouble *ytilt, + GdkModifierType *mask); +</verb></tscreen> + +When calling this function, we need to specify the device ID as +well as the window. Usually, we'll get the device ID from the +<tt/deviceid/ field of an event structure. Again, this function +will return reasonable values when extension events are not +enabled. (In this case, <tt/event->deviceid/ will have the value +<tt/GDK_CORE_POINTER/). + +So the basic structure of our button-press and motion event handlers, +doesn't change much - we just need to add code to deal with the +extended information. + +<tscreen><verb> +static gint +button_press_event (GtkWidget *widget, GdkEventButton *event) +{ + print_button_press (event->deviceid); + + if (event->button == 1 && pixmap != NULL) + draw_brush (widget, event->source, event->x, event->y, event->pressure); + + return TRUE; +} + +static gint +motion_notify_event (GtkWidget *widget, GdkEventMotion *event) +{ + gdouble x, y; + gdouble pressure; + GdkModifierType state; + + if (event->is_hint) + gdk_input_window_get_pointer (event->window, event->deviceid, + &x, &y, &pressure, NULL, NULL, &state); + else + { + x = event->x; + y = event->y; + pressure = event->pressure; + state = event->state; + } + + if (state & GDK_BUTTON1_MASK && pixmap != NULL) + draw_brush (widget, event->source, x, y, pressure); + + return TRUE; +} +</verb></tscreen> + +We also need to do something with the new information. Our new +<tt/draw_brush()/ function draws with a different color for +each <tt/event->source/ and changes the brush size depending +on the pressure. + +<tscreen><verb> +/* Draw a rectangle on the screen, size depending on pressure, + and color on the type of device */ +static void +draw_brush (GtkWidget *widget, GdkInputSource source, + gdouble x, gdouble y, gdouble pressure) +{ + GdkGC *gc; + GdkRectangle update_rect; + + switch (source) + { + case GDK_SOURCE_MOUSE: + gc = widget->style->dark_gc[GTK_WIDGET_STATE (widget)]; + break; + case GDK_SOURCE_PEN: + gc = widget->style->black_gc; + break; + case GDK_SOURCE_ERASER: + gc = widget->style->white_gc; + break; + default: + gc = widget->style->light_gc[GTK_WIDGET_STATE (widget)]; + } + + update_rect.x = x - 10 * pressure; + update_rect.y = y - 10 * pressure; + update_rect.width = 20 * pressure; + update_rect.height = 20 * pressure; + gdk_draw_rectangle (pixmap, gc, TRUE, + update_rect.x, update_rect.y, + update_rect.width, update_rect.height); + gtk_widget_draw (widget, &update_rect); +} +</verb></tscreen> + +<sect2> Finding out more about a device + +<p> +As an example of how to find out more about a device, our program +will print the name of the device that generates each button +press. To find out the name of a device, we call the function: + +<tscreen><verb> +GList *gdk_input_list_devices (void); +</verb></tscreen> + +which returns a GList (a linked list type from the glib library) +of GdkDeviceInfo structures. The GdkDeviceInfo strucure is defined +as: + +<tscreen><verb> +struct _GdkDeviceInfo +{ + guint32 deviceid; + gchar *name; + GdkInputSource source; + GdkInputMode mode; + gint has_cursor; + gint num_axes; + GdkAxisUse *axes; + gint num_keys; + GdkDeviceKey *keys; +}; +</verb></tscreen> + +Most of these fields are configuration information that you +can ignore unless you are implemented XInput configuration +saving. The we are interested in here is <tt/name/ which is +simply the name that X assigns to the device. The other field +that isn't configuration information is <tt/has_cursor/. If +<tt/has_cursor/ is false, then we we need to draw our own +cursor. But since we've specified <tt/GDK_EXTENSION_EVENTS_CURSOR/, +we don't have to worry about this. + +<p> +Our <tt/print_button_press()/ function simply iterates through +the returned list until it finds a match, then prints out +the name of the device. + +<tscreen><verb> +static void +print_button_press (guint32 deviceid) +{ + GList *tmp_list; + + /* gdk_input_list_devices returns an internal list, so we shouldn't + free it afterwards */ + tmp_list = gdk_input_list_devices(); + + while (tmp_list) + { + GdkDeviceInfo *info = (GdkDeviceInfo *)tmp_list->data; + + if (info->deviceid == deviceid) + { + printf("Button press on device '%s'\n", info->name); + return; + } + + tmp_list = tmp_list->next; + } +} +</verb></tscreen> + +That completes the changes to ``XInputize'' our program. As with +the first version, the complete source is available at the location +from which you got this tutorial, or from: + +<htmlurl url="http://www.msc.cornell.edu/~otaylor/gtk-gimp/tutorial" +name="http://www.msc.cornell.edu/~otaylor/gtk-gimp/tutorial"> + + +<sect2> Further sophistications <label id="sec_Further_Sophistications"> + +<p> +Although our program now supports XInput quite well, it lacks some +features we would want in a full-featured application. First, the user +probably doesn't want to have to configure their device each time they +run the program, so we should allow them to save the device +configuration. This is done by iterating through the return of +<tt/gdk_input_list_devices()/ and writing out the configuration to a +file. + +<p> +To restore the state next time the program is run, GDK provides +functions to change device configuration: + +<tscreen><verb> +gdk_input_set_extension_events() +gdk_input_set_source() +gdk_input_set_mode() +gdk_input_set_axes() +gdk_input_set_key() +</verb></tscreen> + +(The list returned from <tt/gdk_input_list_devices()/ should not be +modified directly.) An example of doing this can be found in the +drawing program gsumi. (Available from <htmlurl +url="http://www.msc.cornell.edu/~otaylor/gsumi/" +name="http://www.msc.cornell.edu/~otaylor/gsumi/">) Eventually, it +would be nice to have a standard way of doing this for all +applications. This probably belongs at a slightly higher level than +GTK, perhaps in the GNOME library. + +<p> +Another major ommission that we have mentioned above is the lack of +cursor drawing. Platforms other than XFree86 currently do not allow +simultaneously using a device as both the core pointer and directly by +an application. See the <url +url="http://www.msc.cornell.edu/~otaylor/xinput/XInput-HOWTO.html" +name="XInput-HOWTO"> for more information about this. This means that +applications that want to support the widest audience need to draw +their own cursor. + +<p> +An application that draws it's own cursor needs to do two things: +determine if the current device needs a cursor drawn or not, and +determine if the current device is in proximity. (If the current +device is a drawing tablet, it's a nice touch to make the cursor +disappear when the stylus is lifted from the tablet. When the +device is touching the stylus, that is called "in proximity.") +The first is done by searching the device list, as we did +to find out the device name. The second is achieved by selecting +"proximity_out" events. An example of drawing one's own cursor is +found in the 'testinput' program found in the GTK distribution. + +<!-- ***************************************************************** --> +<sect>Tips For Writing GTK Applications +<!-- ***************************************************************** --> + +<p> +This section is simply a gathering of wisdom, general style guidelines and hints to +creating good GTK applications. It is totally useless right now cause it's +only a topic sentence :) + +Use GNU autoconf and automake! They are your friends :) I am planning to +make a quick intro on them here. + +<!-- ***************************************************************** --> +<sect>Contributing +<!-- ***************************************************************** --> + +<p> +This document, like so much other great software out there, was created for +free by volunteers. If you are at all knowledgeable about any aspect of GTK +that does not already have documentation, please consider contributing to +this document. +<p> +If you do decide to contribute, please mail your text to Tony Gale, +<tt><htmlurl url="mailto:gale@gimp.org" +name="gale@gimp.org"></tt>. Also, be aware that the entirety of this +document is free, and any addition by yourself must also be free. That is, +people may use any portion of your examples in their programs, and copies +of this document may be distributed at will etc. +<p> +Thank you. + +<!-- ***************************************************************** --> +<sect>Credits +<!-- ***************************************************************** --> +<p> +I would like to thank the following for their contributions to this text. + +<itemize> +<item>Bawer Dagdeviren, <tt><htmlurl url="mailto:chamele0n@geocities.com" +name="chamele0n@geocities.com"></tt> for the menus tutorial. + +<item>Raph Levien, <tt><htmlurl url="mailto:raph@acm.org" +name="raph@acm.org"></tt> +for hello world ala GTK, widget packing, and general all around wisdom. +He's also generously donated a home for this tutorial. + +<item>Peter Mattis, <tt><htmlurl url="mailto:petm@xcf.berkeley.edu" +name="petm@xcf.berkeley.edu"></tt> for the simplest GTK program.. +and the ability to make it :) + +<item>Werner Koch <tt><htmlurl url="mailto:werner.koch@guug.de" +name="werner.koch@guug.de"></tt> for converting the original plain text to +SGML, and the widget class hierarchy. + +<item>Mark Crichton <tt><htmlurl url="mailto:crichton@expert.cc.purdue.edu" +name="crichton@expert.cc.purdue.edu"></tt> for the menu factory code, and +the table packing tutorial. + +<item>Owen Taylor <tt><htmlurl url="mailto:owt1@cornell.edu" +name="owt1@cornell.edu"></tt> for the EventBox widget section (and +the patch to the distro). He's also responsible for the selections code and +tutorial, as well as the sections on writing your own GTK widgets, and the +example application. Thanks a lot Owen for all you help! + +<item>Mark VanderBoom <tt><htmlurl url="mailto:mvboom42@calvin.edu" +name="mvboom42@calvin.edu"></tt> for his wonderful work on the Notebook, +Progress Bar, Dialogs, and File selection widgets. Thanks a lot Mark! +You've been a great help. + +<item>Tim Janik <tt><htmlurl url="mailto:timj@psynet.net" +name="timj@psynet.net"></tt> for his great job on the Lists Widget. +Thanks Tim :) + +<item>Rajat Datta <tt><htmlurl url="mailto:rajat@ix.netcom.com" +name="rajat@ix.netcom.com"</tt> for the excellent job on the Pixmap tutorial. + +<item>Michael K. Johnson <tt><htmlurl url="mailto:johnsonm@redhat.com" +name="johnsonm@redhat.com"></tt> for info and code for popup menus. + +</itemize> +<p> +And to all of you who commented and helped refine this document. +<p> +Thanks. + +<!-- ***************************************************************** --> +<sect> Tutorial Copyright and Permissions Notice +<!-- ***************************************************************** --> + +<p> +The GTK Tutorial is Copyright (C) 1997 Ian Main. + +Copyright (C) 1998 Tony Gale. +<p> +Permission is granted to make and distribute verbatim copies of this +manual provided the copyright notice and this permission notice are +preserved on all copies. +<P>Permission is granted to copy and distribute modified versions of +this document under the conditions for verbatim copying, provided that +this copyright notice is included exactly as in the original, +and that the entire resulting derived work is distributed under +the terms of a permission notice identical to this one. +<P>Permission is granted to copy and distribute translations of this +document into another language, under the above conditions for modified +versions. +<P>If you are intending to incorporate this document into a published +work, please contact the maintainer, and we will make an effort +to ensure that you have the most up to date information available. +<P>There is no guarentee that this document lives up to its intended +purpose. This is simply provided as a free resource. As such, +the authors and maintainers of the information provided within can +not make any guarentee that the information is even accurate. +</article> diff --git a/docs/gtk_tut_it.sgml b/docs/gtk_tut_it.sgml new file mode 100644 index 000000000..9ed24eb86 --- /dev/null +++ b/docs/gtk_tut_it.sgml @@ -0,0 +1,8340 @@ + +<!doctype linuxdoc system> +<article> +<title>GTK Tutorial +<author>Ian Main, <tt><htmlurl url="mailto:slow@intergate.bc.ca" + name="slow@intergate.bc.ca"></tt> + +<date>December 1, 1997 - Traduzione Aggiornata al 19 Gennaio 1998 + +<abstract>Tradotto da Michel Morelli, <tt><htmlurl url="mailto:ziobudda@chiara.dei.unipd.it" name="ziobudda@chiara.dei.unipd.it"></tt>, Daniele Canazza, <tt><htmlurl url="mailto:dcanazz@tin.it" name="dcanazz@tin.it"></tt> e Antonio Schifano, <tt><htmlurl url="mailto:schifano@cli.di.unipi.it" name="schifano@cli.di.unipi.it"></tt> +</abstract> + +<sect>Introduzione +<p> +GTK (GIMP Toolkit) era orginariamente sviluppato come toolkit per il programma +GIMP (General Image Manipulation Program). GTK è costruito sulla base del +kit di disegno di GIMP, il GDK (GIMP Drawing Kit) il quale è costruito a sua +volta attorno alle funzioni della Xlib. E' chiamato ``toolkit di GIMP'' perché +era inizialmente scritto per sviluppare GIMP, ma ora viene utilizzato nello +sviluppo di molti progetti software liberi. Gli autori sono +<itemize> +<item> Peter Mattis <tt><htmlurl url="mailto:petm@xcf.berkeley.edu" + name="petm@xcf.berkeley.edu"></tt> +<item> Spencer Kimball <tt><htmlurl url="mailto:spencer@xcf.berkeley.edu" + name="spencer@xcf.berkeley.edu"></tt> +<item> Josh MacDonald <tt><htmlurl url="mailto:jmacd@xcf.berkeley.edu" + name="jmacd@xcf.berkeley.edu"></tt> +</itemize> + +<p> +GTK è essenzialmente una API (application programmers interface) +orientata agli oggetti. +Anche se scritto completamente in C, è implementato usando l'idea delle +classi e delle funzioni di callback (puntatori a funzioni). + +<p> +C'è anche una terza componente chiamata glib che contiene una serie di +implementazioni differenti di alcune chiamate di funzioni standard e anche +alcune funzioni aggiuntive, per esempio per la manipolazione delle liste +collegate, eccetera. Le funzioni sostitutive sono usate per migliorare la +portabilità di GTK. Alcune delle funzioni implementate qui non sono +disponibili o non sono standard, altre sono uniche come g_strerror(). +Altre contengono miglioramenti alle stesse della libc come g_malloc che ha +delle utility di debugging migliorate. + +<p> +Questo tutorial è un tentativo di documentare il meglio possibile la libreria gtk +e non pretende di essere completo. Questo tutorial suppone una buona conoscenza del +linugaggio C e di come creare programmi in C. Saranno facilitati i lettori che hanno una +precedente esperienza nella programmazione in X. Se il GTK è il primo insieme di widget +che studiate, siete pregati di dirmi come avete trovato questo tutorial e che tipo di problemi +avete avuto. +Notate che c'è anche una versione per il C++ della libreria GTK (chiamata GTK--), quindi +se preferite utilizzare questo linguaggio al posto del C potreste cercare questa versione +e non la GTK normale. +Ci sono poi un ``wrapper'' Objective C e un collegamento a Guile, ma non ne seguo +l'evoluzione. + +<p> +Mi farebbe molto piacere conoscere qualsiasi problema che abbiate avuto nell'imparare il GTK +da questo documento e apprezzerei anche critiche sul come migliorarlo. + +<sect>Iniziamo +<p> +La prima cosa da fare è certamente quella di scaricare il GTK e installarlo. Potete prendere +l'ultima versione dal sito ftp.gimp.org nella directory /pub/gimp. Un'altra possibile sorgente +di informazioni è il sito http://www.gimp.org/gtk. GTK usa il comando GNU autoconf per +autoconfigurarsi. +Una volta estratti i file dall'archivio tar, eseguite configure --help per vedere una lista delle +opzioni del comando configure. + +<p> +Per iniziare la nostra introduzione a GTK, cominceremo con il più semplice programma +possibile . Questo programma crea una finestra con dimensioni (in pixel) di 200x200 e +l'unica possibilità di uscita è di ucciderlo ucciso usando la shell o il Window Manager. + +<tscreen><verb> +#include <gtk/gtk.h> + +int main (int argc, char *argv[]) +{ + GtkWidget *window; + + gtk_init (&argc, &argv); + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_widget_show (window); + + gtk_main (); + + return 0; +} +</verb></tscreen> + +Tutti i programmi certamente includeranno <gtk/gtk.h> che dichiara le variabili, le funzioni, +le strutture, etc. che saranno usate nella tua applicazione GTK. + +<p> +La linea seguente: + +<tscreen><verb> +gtk_init (&argc, &argv); +</verb></tscreen> + +invoca la funzione gtk_init(gint *argc, gchar ***argv) che sarà usata in tutte le +applicazioni GTK. Questa funzione sistema alcune cose al posto nostro, come la visuale +predefinita e la mappa dei colori, e procede poi chiamando gdk_init(gint *argc, gchar ***argv). +Questa funzione inizializza la libreria per l'uso, setta il gestore predefinito dei segnali +e guarda negli argomenti, passati via linea di comando alla tua applicazione, alla ricerca +di uno di questi argomenti: +<itemize> +<item> <tt/--display/ +<item> <tt/--debug-level/ +<item> <tt/--no-xshm/ +<item> <tt/--sync/ +<item> <tt/--show-events/ +<item> <tt/--no-show-events/ +</itemize> +<p> +Rimuove questi argomenti dalla lista degli argomenti passati, lasciando quelli non +riconosciuti a disposizione della tua applicazione che potrà tenerne conto o ignorarli. +In questo modo si crea un set di argomenti standard accettato da tutte le applicazione GTK. + +<p> +Le seguenti 2 linee di codice creano e mostrano la finestra. + +<tscreen><verb> + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_widget_show (window); +</verb></tscreen> + +L'argomento GTK_WINDOW_TOPLEVEL specifica che noi vogliamo che la nostra finestra si +sottometta alle decorazioni del windows manager e alla posizione che quest'ultimo indicherà. +Invece di creare una finestra avente dimensioni 0x0, la dimensione di una finestra senza +figli (altri widget, come i bottoni, etc) è predefinita a 200x200 così che si possa manipolarla. +La funzione gtk_widget_show() fa sì che GTK sappia che abbiamo finito di settare gli +attributi di questo widget e che quindi quest'ultimo può essere visualizzato. + +<p> +L'ultima linea ci fa entrare nel ciclo principale del GTK. + +<tscreen><verb> +gtk_main (); +</verb></tscreen> + +gtk_main() è un'altra chiamata che tu vedrete in tutte le applicazioni GTK. Quando il controllo +raggiunge questo punto, l'applicazione si metterà a dormire aspettando che si verifichino eventi +di X (come la pressione di un bottone o di un tasto), timeout o notifiche di Input/Output dei file +Nel nostro esempio, comunque, tutti gli eventi sono ignorati. + +<sect1>Hello World in GTK +<p> +Ok, ora un programma con un widget (un bottone). E' il classico ``Hello World'' alla GTK. + +<tscreen><verb> + +#include <gtk/gtk.h> + + +/* E' una funzione di ritorno (callback). Gli argomenti passati sono ignorati in questo +* esempio. +* Piu' informazioni sulle callback in seguito. */ + +void hello (GtkWidget *widget, gpointer data) +{ + g_print ("Hello World\n"); +} + +gint delete_event(GtkWidget *widget, gpointer data) + { + g_print ("delete event occured\n"); + /* Se si dà TRUE al manipolatore del segnale ``delete_event'', GTK emettera' il segnale + ``destroy''. Fornire FALSE significa non volere che la finestra sia distrutta. + Cambia FALSE con TRUE e la finestra principale sara' distrutta con un "delete_event" + */ + +/* Un'altra callback */ +void destroy (GtkWidget *widget, gpointer data) +{ + gtk_main_quit (); +} + +int main (int argc, char *argv[]) +{ + /* GtkWidget e' il tipo di dato per i Widget */ + GtkWidget *window; + GtkWidget *button; + + /* Questa e' una chiamata presente in tutte le applicazioni GTK. Gli argomenti della + linea di comando vengono scorsi e restituiti alla applicazione */ + gtk_init (&argc, &argv); + + /* Crea una nuova finestra */ + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + + /* Quando alla finestra viene passato il segnale ``delete_event'' (questo + * segnale viene passato Windows Manager di solito con l'opzione 'close' + * o con la barra del titolo (title bar)) noi chiediamo che la funzione + * delete_event() (definita sopra) venga invocata. + * Il dato passato come argomento alla funzione di ritorno é NULL + * ed é ignorato dalla funzione stessa. */ + gtk_signal_connect (GTK_OBJECT (window), "delete_event", + GTK_SIGNAL_FUNC (destroy), NULL); + + /* Qui connettiamo l'evento ``destroy'' al gestore del segnale. + * Questo evento accade quando noi chiamimo la funzione gtk_widget_destroy() + * sulla finestra o se ritorniamo TRUE dalla callback ``delete_event''. */ + gtk_signal_connect (GTK_OBJECT (window), "destroy", + GTK_SIGNAL_FUNC (destroy), NULL); + + /* Setta il bordo interno della finestra */ + gtk_container_border_width (GTK_CONTAINER (window), 10); + + /* Crea un nuovo bottone avente etichetta (label) uguale a ``Hello World'' */ + button = gtk_button_new_with_label ("Hello World"); + + /* Quando il bottone riceve il segnale ``clicked'', invochera' la funzione + * hello() passando NULL come argomento della funzione. La funzione + * hello() é definita sopra. */ + gtk_signal_connect (GTK_OBJECT (button), "clicked", + GTK_SIGNAL_FUNC (hello), NULL); + + /* Questo farà sì che la finestra venga distrutta dalla chiamata + * gtk_widget_destroy(window) quando il bottone verrà premuto. Ancora, + * questo segnale (``destroy'') puo' arrivare da qui o dal windows + * manager */ + gtk_signal_connect_object (GTK_OBJECT (button), "clicked", + GTK_SIGNAL_FUNC (gtk_widget_destroy), + GTK_OBJECT (window)); + + /* Questo inserisce il bottone nella finestra + * (un contenitore GTK) */ + gtk_container_add (GTK_CONTAINER (window), button); + + /* Il passo finale é il mostrare questo nuovo widget appena creato */ + gtk_widget_show (button); + + /* e la finestra */ + gtk_widget_show (window); + + /* Tutte le applicazioni GTK devono avere la funzione gtk_main(). + * Il controllo finisce qui e attende un evento (come la pressione + * di un tasto o l'evento di un mouse). + gtk_main (); + + return 0; +} +</verb></tscreen> + +<sect1>Compilare hello World +<p> +Per compilare si utilizza : + +<tscreen><verb> +gcc -Wall -g helloworld.c -o hello_world -L/usr/X11R6/lib \ + -lglib -lgdk -lgtk -lX11 -lXext -lm +</verb></tscreen> +<p> +Le librerie sopra (glib, gtk,...) devono essere tutte nel percorso predefinito +delle librerie. Se cosi' non fosse aggiungi ``-L<directory>'' e il gcc +guarderà in questa directory per cercare le librerie di cui necessita. +Per esempio sul mio sistema debian-linux io ho dovuto aggiungere +<tt>-L/usr/X11R6/lib</> per riuscire a far trovare le librerie di X11. + +<p> +L'odine della dichiarazione delle librerie é significativo. Il linker +sa quali funzioni di una libreria ha bisogno prima di processarla. + +<p> +le librerie che noi linkiamo sono: +<itemize> +<item> la libreria glib (-lglib), contiene varie funzioni, ma solo +g_print() é usato in questo esempio. GTK si appoggia a questa +libreria cosi' devi sempre, comunque, linkarla. Vedi comunque la <ref +id="sec_glib" name="glib"> sezione sulla glib per altri dettagli. +<item>La libreria GDK (-lgdk), la copertura della X11. +<item>La libreria GTK (-lgtk), la libreria dei widget, basata sulla GDK. +<item>La libreria xlib(-lX11) la quale è usata dalla GDK. +<item>La libreria Xext(-lXext). Questa contiene il codice per le pixmap a +memoria condivisa e altre estensioni di X. +<item>La libreria matematica (-lm). Questa é usata dalla GTK per vari scopi. +</itemize> + +<sect1>Teoria dei segnali e delle funzioni di ritorno (callback) +<p> +Prima di guardare in dettaglio ``Hello World'', discuteremo gli eventi e le +funzioni di ritorno. GTK è un toolkit guidato dagli eventi, il che significa +che se ne starà a dorimire in gtk_main finché non succederà un evento ed il +controllo passerà alla funzione appropriata. + +<p> +Questo passaggio di controllo è fatto usando l'idea dei segnali. Quando succede un +evento, come la pressione di un bottone del mouse, verrà emesso il segnale appropriato +dal widget che é stato premuto. +Questo è il modo in cui GTK fa molto del suo utile lavoro. Per fare sì che un +bottone esegua una azione, noi prepareremo un gestore del segnale che catturi +questi segnali e chiami la funzione corretta. Questo è fatto usando una +funzione del tipo: + +<tscreen><verb> +gint gtk_signal_connect (GtkObject *object, + gchar *name, + GtkSignalFunc func, + gpointer func_data); +</verb></tscreen> + +<p> +Dove, il primo argomento è il widget che emetterà il segnale, il secondo è il nome +del segnale che si vuole catturare,il terzo è la funzione che verrà invocata +quando il segnale sarà catturato e il quarto è il dato che potr essere passato a +questa funzione. + +<p> +La funzione specificata come terzo argomento è chiamata ``funzione di ritorno (callback)'', +e dovrebbe essere della forma: + +<tscreen><verb> +void callback_func(GtkWidget *widget, gpointer *callback_data); +</verb></tscreen> +<p> +Dove il primo argomento sarà un puntatore al widget che emette il segnale e il +secondo un puntatore al dato passato come ultimo argomento della funzione +gtk_signal_connect() come descritto sopra. + +<p> +Un'altra chiamata usata nell'esempio Hello World è: + +<tscreen><verb> +gint gtk_signal_connect_object (GtkObject *object, + gchar *name, + GtkSignalFunc func, + GtkObject *slot_object); +</verb></tscreen> +<p> +gtk_signal_connect_object() è uguale a gtk_signal_connect() eccetto che la +funzione di callback usa solo un argomento, un puntatore ad un'oggetto GTK. +Cosi' quando usa questa funzione per connettere i segnali, la callback +potrebbe essere della forma : + +<tscreen><verb> + void callback_func (GtkObject *object); +</verb></tscreen> +<p> +Dove object è di solito un widget. Noi, generalmente, non assegnamo una callback per +gtk_signal_connect_object. Queste sono invocate ,usualmente, per chiamare +una funzione GTK che accetta un widget singolo o un oggetto come argomento, +come nel caso dell'esempio Hello World. + +Lo scopo di avere due funzioni per connettere i segnali è semplicemente quello di +permettere alla funzione di callback di avere un numero di argomenti diverso. +Molte funzioni della libreria GTK accettano solo un singolo puntatore ad un widget +GTK come argomento, così per queste si può usare la funzione gtk_signal_connect_object(), +mentre per le vostre funzioni potreste aver bisogno di passare dati supplementari alle +funzioni di ritorno. + +<sect1>Attraverso Hello World passo per passo +<p> +Ora che conosciamo la teoria che vi è dietro, iniziamo ad essere più chiari +camminando attraverso il programma di Hello World. + +<p> +Questa è la funzione di callback che sarà invocata quando il bottone è clickato. +Noi, in questo esempio, ignoriamo sia il widget che i dati passati, ma non è +difficile farci invece qualcosa. Il prossimo esempio userà l'argomento passato +per dire quale bottone è stato premuto. + +<tscreen><verb> +void hello (GtkWidget *widget, gpointer *data) +{ + g_print ("Hello World\n"); +} +</verb></tscreen> + +<p> +Questa callback è un po' speciale. L'evento ``delete'' avviene quanto il Window Manager +manda questo evento all'applicazione. Qui abbiamo una scelta da fare: cosa fare di questo evento. +Possiamo ignorarlo, creare qualche tipo di risposta, o semplicemente terminare +l'applicazione. + +Il valore che si restituisce in questa callback fa sì che la GTK sappia cosa fare. +Restituire FALSE significa che noi non vogliamo che il segnale ``destroy'' sia emesso, +quindi far sì che la nostra applicazione continui a procedere. Ritornare TRUE vuole dire +far emettere il segnale ``destroy'' il quale chiamerà il gestore del segnale ``destroy'' +(o meglio : la nostra funzione di callback). + +<tscreen><verb> + gint delete_event(GtkWidget *widget, gpointer data) + { + g_print ("delete event occured\n"); + + return (FALSE); + } +</verb></tscreen> + +<p> +Questa è un'altra funzione di callback la quale fa uscire dal programma chiamando +gtk_main_quit(). Non c'è molto da dire al riguardo, è abbastanza auto-esplicativa. + +<tscreen><verb> +void destroy (GtkWidget *widget, gpointer *data) +{ + gtk_main_quit (); +} +</verb></tscreen> +<p> +Ritengo che conosciate la funzione main()... si, come tutte le altre applicazioni +anche le applicazioni GTK hanno questa funzione. + +<tscreen><verb> +int main (int argc, char *argv[]) +{ +</verb></tscreen> + +<p> +Questa parte dichiara un puntatore ad una struttura di tipo GtkWidget. Queste sono +usate sotto per creare una finestra ed un bottone. + +<tscreen><verb> + GtkWidget *window; + GtkWidget *button; +</verb></tscreen> +<p> +Qui vi è ancora la nostra gtk_init. Come prima questa inizializza il toolkit e +analizza gli argomenti trovati nella linea di comando_ Tutti gli argomenti riconosciuti +nella linea di comando sono rimossi dalla lista degli argomenti e vengono così modificati +argc e argv per far sì che sembri che questi non siano mai esisitie permettere alla +tua applicazione di analizzare gli argomenti rimasti. + +<tscreen><verb> + gtk_init (&argc, &argv); +</verb></tscreen> +<p> +Crea una nuova finestra. Questo viene spiegato abbastanza approfonditamente più avanti. +Viene allocata la memoria per la struttura GtkWidget *window così che si punti ad una struttura +valida. In questo modo si predispone la nuova finestra, ma non la si visualizza fino a sotto dove, quasi +alla fine del nostro programma, invochiamo gtk_widget_show(window). +<tscreen><verb> + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); +</verb></tscreen> +<p> +Questo è un esempio di come connettere un gestore dei segnali con un oggetto, in questo +caso la finestra. Qui viene catturato il segnale ``destroy''. Questo è emesso quando usiamo +il Window Manager per uccidere la finestra (e noi restituiamo TRUE dal gestore di ``delete_event'') +o quando emettiamo la chiamata gtk_widget_destroy() passando l'oggetto finestra +come oggetto da distruggere. Sistemando le cose così, trattiamo entrambi i casi con una singola +chiamata. Qui è giusto invocare la funzione destroy() definita sopra con NULL come argomento, +la quale termina l'applicazione GTK per noi. +Questo ci permetterà di utilizzare il Window Manager per uccidere il programma. +<!-- fino a qui --> +<p> +GTK_OBJECT e GTK_SIGNAL_FUNC sono macro che interpretano il casting e il controllo di tipo per noi, +così da rendere piu' leggibile il codice. + +<tscreen><verb> + gtk_signal_connect (GTK_OBJECT (window), "destroy", + GTK_SIGNAL_FUNC (destroy), NULL); +</verb></tscreen> +<p> +La prossima funzione è usata per settare un attributo di un oggetto contenitore. Questo +sistema la finestra così da avere un'area vuota all'interno della finestrra larga 10 pixel dove +non potrà andare nessun widget. Ci sono altre funzioni simili che vedremo nella +sezione <ref id="sec_setting_widget_attributes" name="Settare gli attributi del Widget."> + +<p> +E ancora, GTK_CONTAINER è una macro per interpretare il casting di tipo. + +<tscreen><verb> + gtk_container_border_width (GTK_CONTAINER (window), 10); +</verb></tscreen> +<p> +Questa chiamata crea un nuovo bottone. Alloca spazio in memoria per un nuovo GtkWidget, +inizializzandolo e facendo sì che il puntatore a bottone punti ad esso. +Quando sarà visualizzato, avrà etichetta ``Hello World''. + +<tscreen><verb> + button = gtk_button_new_with_label ("Hello World"); +</verb></tscreen> +<p> +Qui prendiamo il bottone e gli facciamo fare qualcosa di utile. +Gli colleghiamo un un gestore di segnale in modo che quando emetterà il +segnale ``clicked'', verrà invocata la nostra funzione hello(). Il dato passato +alla funzione è ignorato, cosicché alla funzione di callback hello() passiamo +semplicemente NULL. Evidentemente il segnale ``clicked'' viene emesso quando +premiamo il bottone con il mouse. + +<tscreen><verb> + gtk_signal_connect (GTK_OBJECT (button), "clicked", + GTK_SIGNAL_FUNC (hello), NULL); +</verb></tscreen> +<p> +Usiamo questo bottone anche per uscire dal programma. Questo illustrera' +come il segnale ``destroy'' può arrivare sia dal Window Manager che dal nostro programma. +Quando il bottone è ``clicked'', come sopra, chiamera' la funzione di callback +hello() e poi questa nell'ordine in cui sono definite. Si possono avere +tante funzioni di callback, quante sono necessarie, e saranno eseguite nell'ordine in cui +sono connesse. Visto che la funzione gtk_widget_destroy() accetta come argomento solo un +GtkWidget *widget, usiamo la funzione gtk_signal_connect_object() +al posto della semplice gtk_signal_connect(). + +<tscreen><verb> + gtk_signal_connect_object (GTK_OBJECT (button), "clicked", + GTK_SIGNAL_FUNC (gtk_widget_destroy), + GTK_OBJECT (window)); +</verb></tscreen> +<p> +Questa é una chiamata di ``impacchettamento'' che sarà spiegata più avanti. +Ma è molto facile da capire. Semplicemente dice alla libreria GTK che il +bottone è da mettere nella finestra dove sarà visualizzato. + +<tscreen><verb> + gtk_container_add (GTK_CONTAINER (window), button); +</verb></tscreen> +<p> +A questo punto abbiamo predisposto tutto quello che ci eravamo prefissati. +Con tutti i gestori di segnale a posto e il bottone messo nella finestra in cui +dovrebbe essere, possiamo dire a GTK di mostrare gli oggetti sullo schermo. +L'oggetto finestra viene mostrato per ultimo così che la finestra completa di tutti +i suoi oggetti sarà mostrata in una volta sola, invece di vedere +prima la finestra spoglia e poi la comparsa del bottone all'interno di essa. +Per quanto, con questi semplici esempi, questo l'avrai già notato. +<tscreen><verb> + gtk_widget_show (button); + + gtk_widget_show (window); +</verb></tscreen> +<p> +E naturalmente chiamiamo gtk_main(), la quale aspetta l'arrivo degli eventi +dal server X e chiamerà l'oggetto interessato per fargli emettere il segnale +adeguato. +<tscreen><verb> + gtk_main (); +</verb></tscreen> +E il return finale. Il controllo ritorna qui dopo che viene invocata gtk_quit(). + +<tscreen><verb> + return 0; +</verb></tscreen> +<p> +Ora, quando premiamo il bottone del mouse su un bottone GTK, questo oggetto +emette il segnale ``clicked''. Per poter utilizzare queste informazioni, il nostro +programma predispone un gestore di segnale per catturare quel segnale, il quale +avvia la funzione da noi scelta. Nel nostro esempio, quando il bottone creato viene +clickato , la funzione hello() è invocata con un argomento NULL, dopoodiché +viene invocato il successivo gestore di questo segnale. Questo chiama la funziona +gtk_widget_destroy(), passandole l'oggetto-finestra (window) come argomento, che +distruggerà la finestra. Questo fa sì che la finestra emetta il segnale +``destroy'' che viene catturato e che fa invocare la funzione di ritorno +destroy(), che semplicemente esce dal programma GTK. + +<p> +Un'altro modo in cui possono andare le cose è l'uso del window manager per uccidere +la finestra. Questo causera' l'emissione del segnale ``delete_event'' che +automaticamente chiamerà il gestore del segnale ``delete_event''. Se qui noi +restituiamo il valore FALSE, la finestra non verrà toccata e tutto procederà come +se nulla fosse successo. Dare invece il valore TRUE causerà l'emissione da parte +di GTK del segnale ``destroy'' il quale, a sua volta, invocherà la callback ``destroy'', +uscendo dall'applicazione. + +<p> +Nota che questi segnali non sono gli stessi del sistema Unix e che non sono +implementati usando quei segnali, anche se la terminologia è praticamente identica. + +<sect>Proseguiamo +<p> +<sect1>Tipi di Dato +<p> +Ci sono alcune cose che avrete probabilmente notato nei precedenti esempi che +hanno bisogno di una spiegazione. I gint, gchar ecc. che vedete sono tipi di dato +riferiti rispettivamente a int e char. Questo viene fatto per rimediare alla brutta +dipendenza dalle dimensioni di semplici tipi di dato quando si fanno dei calcoli. +Un buon esempio è ``gint32'' il quale sarà un tipo di dato riferito ad un intero a +32 bit per tutte le piattaforme x86 e ad un 64 bit per gli alpha. +I tipi di dato sono ben spiegati più avanti ed intuitivi. Sono definiti in +glib/glib.h (il quale viene incluso da gtk.h). + +<p> +Noterete anche la possibilità di utilizzare un GtkWidget quando la funzione richiede +un GtkObject. GTK è una libreria orienta agli oggetti ed un widget è un oggetto. + +<sect1>Altri Dettagli sui Segnali +<p> +Diamo un'altra occhiata alla dichiarazione della funzione gtk_signal_connect. + +<tscreen><verb> +gint gtk_signal_connect (GtkObject *object, gchar *name, + GtkSignalFunc func, gpointer func_data); +</verb></tscreen> +Notate il valore di ritorno definito come gint? questo è un identificatore per +la tua funzione di callback. Come detto sopra, si possono avere più funzioni di +ritorno per ogni segnale e per ogni ogetto a seconda delle necessità. ed ognuna sarà +eseguita in sequenza, nell'ordine in cui sono state collegate. Questo identificatore +ti permette di rimuovere una funzione dalla lista delle funzioni di ritorno tramite +la seguente chiamata +<tscreen><verb> +void gtk_signal_disconnect (GtkObject *object, + gint id); +</verb></tscreen> +Così passando il widget da cui vuoi rimuovere il gestore di segnale, e +l'identificativo restituito da una delle funzioni signal_connect, puoi rimuovere +il gestore di segnale che desideri da quella del widget. + +<p> +Un'altra funzione per rimuovere tutti i segnali di un widget in una volta sola è: + +<tscreen><verb> +gtk_signal_handlers_destroy (GtkObject *object); +</verb></tscreen> +<p> +Questa chiamata è abbastanza auto esplicativa. Semplicemente rimuove tutti i segnali +collegati al widget che passi alla funzione come argomento. + +<sect1>Miglioriamo Hello World + +<p> +Diamo un'occhiata ad una migliorata versione di Hello World con altri esempi sulle +callback. Questo anche ci introdurrà al nostro prossimo argomento, +l'impacchettamento dei widget. + +<tscreen><verb> +#include <gtk/gtk.h> + +/* La nostra funzione di callback migliorata. I dati passati a questa + * vengono stampati su stdout. */ +void callback (GtkWidget *widget, gpointer *data) +{ + g_print ("Hello again - %s was pressed\n", (char *) data); +} + +/* Un'altra callback */ +void delete_event (GtkWidget *widget, gpointer *data) +{ + gtk_main_quit (); +} + +int main (int argc, char *argv[]) +{ + /* GtkWidget e' il tipo di dato per i widget */ + GtkWidget *window; + GtkWidget *button; + GtkWidget *box1; + + /* Questa funzione e' invocata in tutte le applicazioni GTK, gli + argomenti sono analizzati e restituiti all'applicazione. */ + gtk_init (&argc, &argv); + + /* Crea una nuova finestra */ + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + + /* Questa e' una nuova chiamata. Assegna "Hello Buttons" come titolo + della nostra finestra */ + gtk_window_set_title (GTK_WINDOW (window), "Hello Buttons!"); + + /* Qui settiamo il gestore per il segnale "delete_event" che + immediatamente esce dalla applicazione. + gtk_signal_connect (GTK_OBJECT (window), "delete_event", + GTK_SIGNAL_FUNC (delete_event), NULL); + + + /* predispone il bordo della finestra */ + gtk_container_border_width (GTK_CONTAINER (window), 10); + + /* creiamo una scatola dove mettere tutti i widget. Questa è descritta + dettagliatamente nella sezione "packing". La scatola non è realmente + visibile, è solamente usata per sistemare i widget. */ + box1 = gtk_hbox_new(FALSE, 0); + + /* Inseriamo la scatola nella finestra */ + gtk_container_add (GTK_CONTAINER (window), box1); + + /* Creiamo un nuovo bottone con etichetta "Button 1" */ + button = gtk_button_new_with_label ("Button 1"); + + /* Quando il bottone e' premuto, noi invocheremo la funzione di callback, + con un puntatore alla stringa "button 1" come proprio argomento) */ + gtk_signal_connect (GTK_OBJECT (button), "clicked", + GTK_SIGNAL_FUNC (callback), (gpointer) "button 1"); + + /* invece di aggiungerlo alla finestra, lo inseriamo nella scatola invisibile, + la quale e' stata inserita nella finstra. */ + gtk_box_pack_start(GTK_BOX(box1), button, TRUE, TRUE, 0); + + /* Ricordati sempre questo passo. Dice a GTK che la preparazione di questo + bottone e' finita e che quindi puo' essere mostrato. */ + gtk_widget_show(button); + + /* Facciamo la stessa cosa per il secondo bottone. */ + button = gtk_button_new_with_label ("Button 2"); + + /* Chiamiamo la stessa funzione ma passandogli un argomento differente, + gli passiamo un puntatore alla stringa "button 2" */ + gtk_signal_connect (GTK_OBJECT (button), "clicked", + GTK_SIGNAL_FUNC (callback), (gpointer) "button 2"); + + gtk_box_pack_start(GTK_BOX(box1), button, TRUE, TRUE, 0); + + /* L'ordine nel quale i bottoni sono visualizzati non e' realmente importante, + ma io ti raccomando di mostrare per ultima la finestra cosi' che tutto + sia visualizzato in una volta sola */ + gtk_widget_show(button); + + gtk_widget_show(box1); + + gtk_widget_show (window); + + /* e ora ci mettiamo in gtk_main e aspettiamo che il diverimento inizi. + gtk_main (); + + return 0; +} +</verb></tscreen> +<p> +Compilate questo programma usando gli stessi argomenti di link del nostro primo +esempio. Noterete che questa volta non c'è un modo semplice per uscire dal programma, +si deve usare il nostro window manager o la linea di comando per uccidere +l'applicazione. +Un buon esercizio per il lettore è quello di inserire un tezo bottone ``quit'' che +faccia uscire dal programma. Potete anche divertirvi con le opzioni di +gtk_box_pack_start() mentre leggete il prossimo capitolo. Provate a ridimensionare +la finestra ed a osservare cosa succede. + +<p> +Solo una piccola nota, c'è un'altra definizione di gtk_window_new() - +GTK_WINDOW_DIALOG. Questa interagisce con il window manager in un modo un po' +diverso, e dovrebbe essere usata per finestre temporanee. + +<sect>Come ``Impacchettare'' i Widget +<p> +Nel momento in cui si crea un'applicazione, normalmente si avrà la necessità di mettere più +di un unico bottone all'interno di una finestra. Il nostro primo esempio ``Hello World'' +usava un solo oggetto, cosicché abbiamo potuto usare semplicemente una chiamata +a gtk_container_add per impacchettare il widget nella finestra. Quando invece si vuole +inserire più di un unico widget in una finestra, come si fa a controllare dove vengono +posizionati i propri oggetti? E' qui che entra in gioco il meccanismo dell'``impacchettamento''. +<sect1>Teoria delle Scatole per Impacchettamento +<p> +La maggior parte dell'impacchettamento viene effettuata creando delle scatole +come nell'esempio più sopra. Le scatole sono dei contenitori invisibili di +widget che possiamo usare per imballarci i nostri oggetti e che esistono in +due varietà: in particolare si possono avere scatole orizzontali (hbox) e +verticali (vbox). +Quando si impacchentano degli oggetti in una scatola orizzontale, gli oggetti vengono inseriti +orizzontalmente da sinistra a destra oppure da destra a sinistra a seconda della +chiamata di funzione che si usa. In una scatola verticale, gli oggetti vengono inseriti +dall'alto in basso o viceversa. Si può usare qualsiasi combinazione di scatole +all'interno o a fianco di altre scatole, fino ad ottenere l'effetto desiderato. +<p> +Per creare una nuova scatola orizzontale, si usa una chiamata a gtk_hbox_new(), mentre +per le scatole verticali si usa gtk_vbox_new(). Per inserire i widget +all'interno di questi contenitori si usano le funzioni gtk_box_pack_start() e +gtk_box_pack_end(). La funzione gtk_box_pack_start() comincerà dall'alto verso il +basso in una vbox e da sinistra a destra in una hbox. gtk_box_pack_end() fa l'opposto, +impacchettando dal basso verso l'alto in una vbox e da destra a sinistra in una hbox. +Queste funzioni ci permettono di giustificare a destra o a sinistra i nostri +widget, e possono essere mescolate in qualsiasi modo per ottenere l'effetto desiderato. +Useremo gtk_box_pack_start() nella maggior parte dei nostri esempi. Un oggetto può +essere costituito da un altro contenitore o da un oggetto grafico. Infatti, molti +oggetti grafici sono a loro volta dei contenitori, compreso il bottone, anche se +tipicamente all'interno del bottone mettiamo solo una etichetta. +<p> + +Usando queste chiamate, GTK riesce a capire dove si vogliono piazzare i propri +widget, in modo di essere poi in grado di effettuare il ridimensionamento +automatico e altre cose interessanti. Esiste poi un insieme di opzioni che riguardano +il modo in cui i propri oggetti grafici dovrebbero essere impacchettati. Come +si può immaginare, questo metodo dà una buona flessibilità nella creazione e +nella disposizione dei propri widget. +<sect1>Dettagli sulle Scatole +<p> +A causa di questa flessibilità, le scatole per impacchettamento del GTK +possono, di primo acchito, creare un po' di disorientamento. Sono infatti disponibili +molte opzioni, e non è immediato il modo in cui si combinano l'una con l'altra. +Alla fine però, si possono ottenere essenzialmente cinque diversi stili. + +<p> +<? +<IMG ALIGN="center" SRC="packbox1.gif" +VSPACE="15" HSPACE="10" ALT="Box Packing Example Image" WIDTH="528" +HEIGHT="235"> +> + + +Ogni linea contiene una scatola orizzontale (hbox) con diversi bottoni. +La chiamata a gtk_box_pack è una scorciatoia per la chiamata di impacchettamento +di ognuno dei bottoni nella hbox. Ognuno dei bottoni viene impacchettato nella +hbox nello stesso modo (cioè, con gli stessi argomenti per la funzione gtk_box_pack_start ()). +<p> +Questa è la dichiarazione della funzione gtk_box_pack_start. + +<tscreen><verb> +void gtk_box_pack_start (GtkBox *box, + GtkWidget *child, + gint expand, + gint fill, + gint padding); +</verb></tscreen> +Il primo argomento è la scatola nella quale si stanno inscatolando i +widget, il secondo è il widget stesso. Gli oggetti per ora saranno +bottoni, quindi quello che faremo sarà impacchettare bottoni in scatole. +<p> +L'argomento ``expand'' in gtk_box_pack_start() o gtk_box_pack_end() controlla +se gli oggetti devono essere sistemati nella scatola in modo da riempire tutto +lo spazio in diponibile presente nella scatola, in modo che la scatola si espanda fino +ad occupare tutta l'area assegnatale (valore TRUE). +La scatola può anche essere rimpiciolita in modo da contenere esattamente i +widget (valore FALSE). Assegnare a expand il valore FALSE permette di giustificare +a destra o sinistra i propri oggetti. In caso contrario, tutti gli ogetti si espandono +fino ad adattarsi alla scatola, e il medesimo effetto si può ottenere usando solo una +delle funzioni gtk_box_pack_start o pack_end. +<p> +L'argomento ``fill'' delle funzioni gtk_box_pack stabilisce se lo spazio disponibile +nella scatola deve essere allocato agli oggetti (TRUE) o se deve essere mantenuto +come riempimento attorno a questi oggetti (FALSE). Questo argomento ha effetto +solo se a expand è assegnato il valore TRUE. +<p> +Quando si crea una nuova scatola, la funzione ha questo aspetto: + +<tscreen><verb> +GtkWidget * gtk_hbox_new (gint homogeneous, + gint spacing); +</verb></tscreen> + +L'argomento homogeneous di gtk_hbox_new (la stesso per gtk_vbox_new) +determina se ogni oggetto nella scatola deve avere la stessa dimensione (cioè +la stessa ampiezza in una hbox o la stessa altezza in una vbox). Se è settato, +l'argomento expand delle routine gtk_box_pack è sempre attivato. +<p> +Qual è la differenza fra la spaziatura (che è stabilita quando la scatola +viene creata) e il riempimento (che viene stabilito quando gli elementi vengono +impacchettati)? La spaziatura viene inserita fra gli oggetti, mentre il +riempimento viene aggiuno a ciascuno dei lati dell'oggetti. La seguente figura +dovrebbe chiarire meglio questo punto: + +<? +<IMG ALIGN="center" SRC="packbox2.gif" +VSPACE="15" HSPACE="10" ALT="Box Packing Example Image" WIDTH="509" +HEIGHT="213"> +> + + +Di seguito è riportato il codice usato per creare le immagini precedenti. +L'ho commentato in modo piuttosto pesante, in modo che non dovreste avere +problemi nel seguirlo. Compilatelo voi stessi e preovate a giocarci un po'. + +<sect1>Programma Dimostrativo di Impacchettamento +<p> + +<tscreen><verb> +#include "gtk/gtk.h" + +void +delete_event (GtkWidget *widget, gpointer *data) +{ + gtk_main_quit (); +} + +/* Costruisco una nuova hbox riempita con bottoni-etichette. Gli + * argomenti per le varabili che ci interessano sono passati + * in questa funzione. Non mostriamo la scatola, ma mostriamo + * tutto quello che c'è dentro. */ +GtkWidget *make_box (gint homogeneous, gint spacing, + gint expand, gint fill, gint padding) +{ + GtkWidget *box; + GtkWidget *button; + char padstr[80]; + + /* costruisco una nuova hbox con i valori appropriati di + * homogeneous e spacing */ + box = gtk_hbox_new (homogeneous, spacing); + + /* costruisco una serie di bottoni con i valori appropriati */ + button = gtk_button_new_with_label ("gtk_box_pack"); + gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding); + gtk_widget_show (button); + + button = gtk_button_new_with_label ("(box,"); + gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding); + gtk_widget_show (button); + + button = gtk_button_new_with_label ("button,"); + gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding); + gtk_widget_show (button); + + /* costruisco un bottone con l'etichetta che dipende dal valore di + * expand. */ + if (expand == TRUE) + button = gtk_button_new_with_label ("TRUE,"); + else + button = gtk_button_new_with_label ("FALSE,"); + + gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding); + gtk_widget_show (button); + + /* Questo è la stessa cosa della creazione del bottone per "expand" + * più sopra, ma usa la forma breve. */ + button = gtk_button_new_with_label (fill ? "TRUE," : "FALSE,"); + gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding); + gtk_widget_show (button); + + sprintf (padstr, "%d);", padding); + + button = gtk_button_new_with_label (padstr); + gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding); + gtk_widget_show (button); + + return box; +} + +int +main (int argc, char *argv[]) +{ + GtkWidget *window; + GtkWidget *button; + GtkWidget *box1; + GtkWidget *box2; + GtkWidget *separator; + GtkWidget *label; + GtkWidget *quitbox; + int which; + + /* La nostra inizializzazione, non dimenticatela! :) */ + gtk_init (&argc, &argv); + + if (argc != 2) { + fprintf (stderr, "uso: packbox num, dove num è 1, 2, o 3.\n"); + /* questo fa solo un po' di pulizia in GTK, ed esce con un valore 1. */ + gtk_exit (1); + } + + which = atoi (argv[1]); + + /* Creiamo la nostra finestra */ + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + + /* Ci si dovrebbe sempre ricordare di connettere il segnale di destroy + * alla finestra principale. Ciò è molto importante per avere un funzionamento + * corretto dal punto di vista intuitivo */ + gtk_signal_connect (GTK_OBJECT (window), "delete_event", + GTK_SIGNAL_FUNC (delete_event), NULL); + gtk_container_border_width (GTK_CONTAINER (window), 10); + + /* Creiamo una scatola verticale (vbox) in cui impacchettare quelle + * orizzontali. Questo ci permette di impilare le scatole orizzontali + * piene di bottoni una sull'altra in questa vbox. */ + + box1 = gtk_vbox_new (FALSE, 0); + + /* Decide quale esempio si deve mostrare. Corrispondono alle figure precedenti */ + switch (which) { + case 1: + /* creare una nuova etichetta. */ + label = gtk_label_new ("gtk_hbox_new (FALSE, 0);"); + + /* allineare l'etichetta al lato sinistro. Discuteremo questa e altre + * funzioni nella sezione dedicata agli attributi degli oggetti grafici. */ + gtk_misc_set_alignment (GTK_MISC (label), 0, 0); + + /* Impacchettare l'etichetta nella scatola verticale (vbox box1). + * Ricordare che gli oggetti che vengono aggiunti in una vbox vengono + * impacchettati uno sopra all'altro in ordine. */ + gtk_box_pack_start (GTK_BOX (box1), label, FALSE, FALSE, 0); + + /* mostrare l'etichetta */ + gtk_widget_show (label); + + /* chiamare la nostra funzione make_box - homogeneous = FALSE, + * spacing = 0, expand = FALSE, fill = FALSE, padding = 0 */ + box2 = make_box (FALSE, 0, FALSE, FALSE, 0); + gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0); + gtk_widget_show (box2); + + /* chiamare la nostra funzione make_box - homogeneous = FALSE, spacing = 0, + * expand = FALSE, fill = FALSE, padding = 0 */ + box2 = make_box (FALSE, 0, TRUE, FALSE, 0); + gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0); + gtk_widget_show (box2); + + /* Gli argomenti sono: homogeneous, spacing, expand, fill, padding */ + box2 = make_box (FALSE, 0, TRUE, TRUE, 0); + gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0); + gtk_widget_show (box2); + + /* Questo crea un separatore. Li conosceremo meglio in seguito, + * comunque sono piuttosto semplici. */ + separator = gtk_hseparator_new (); + + /* Impacchetta il separatore nella vbox. Ricordare che stiamo impacchettando + * ognuno di questi oggetti in una vbox, cosicché essi verranno + * impacchettati verticalmente. */ + gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 5); + gtk_widget_show (separator); + + /* crea un'altra nuova etichetta e mostrala. */ + label = gtk_label_new ("gtk_hbox_new (TRUE, 0);"); + gtk_misc_set_alignment (GTK_MISC (label), 0, 0); + gtk_box_pack_start (GTK_BOX (box1), label, FALSE, FALSE, 0); + gtk_widget_show (label); + + /* Gli argomenti sono: homogeneous, spacing, expand, fill, padding */ + box2 = make_box (TRUE, 0, TRUE, FALSE, 0); + gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0); + gtk_widget_show (box2); + + /* Gli argomenti sono: homogeneous, spacing, expand, fill, padding */ + box2 = make_box (TRUE, 0, TRUE, TRUE, 0); + gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0); + gtk_widget_show (box2); + + /* ancora un nuovo separatore. */ + separator = gtk_hseparator_new (); + /* Gli ultimi 3 argumenti per gtk_box_pack_start sono: expand, fill, padding. */ + gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 5); + gtk_widget_show (separator); + + break; + + case 2: + + /* creare una nuova etichetta, ricordare che box1 è la vbox creata + * vicino all'inizio di main() */ + label = gtk_label_new ("gtk_hbox_new (FALSE, 10);"); + gtk_misc_set_alignment (GTK_MISC (label), 0, 0); + gtk_box_pack_start (GTK_BOX (box1), label, FALSE, FALSE, 0); + gtk_widget_show (label); + + /* Gli argomenti sono: homogeneous, spacing, expand, fill, padding */ + box2 = make_box (FALSE, 10, TRUE, FALSE, 0); + gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0); + gtk_widget_show (box2); + + /* Gli argomenti sono: homogeneous, spacing, expand, fill, padding */ + box2 = make_box (FALSE, 10, TRUE, TRUE, 0); + gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0); + gtk_widget_show (box2); + + separator = gtk_hseparator_new (); + /* Gli ultimi tre arcomenti di gtk_box_pack_start sono: expand, fill, padding. */ + gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 5); + gtk_widget_show (separator); + + label = gtk_label_new ("gtk_hbox_new (FALSE, 0);"); + gtk_misc_set_alignment (GTK_MISC (label), 0, 0); + gtk_box_pack_start (GTK_BOX (box1), label, FALSE, FALSE, 0); + gtk_widget_show (label); + + /* Gli argomenti sono: homogeneous, spacing, expand, fill, padding */ + box2 = make_box (FALSE, 0, TRUE, FALSE, 10); + gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0); + gtk_widget_show (box2); + + /* Gli argomenti sono: homogeneous, spacing, expand, fill, padding */ + box2 = make_box (FALSE, 0, TRUE, TRUE, 10); + gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0); + gtk_widget_show (box2); + + separator = gtk_hseparator_new (); + /* Gli ultimi tre argomenti di gtk_box_pack_start sono: expand, fill, padding. */ + gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 5); + gtk_widget_show (separator); + break; + + case 3: + + /* Questo dimostra la possibilità di usare use gtk_box_pack_end() per + * giustificare gli oggetti a destra. Per prima cosa creiamo una + + * nuova scatola come prima. */ + box2 = make_box (FALSE, 0, FALSE, FALSE, 0); + /* creiamo l'etichetta che sarà aggiunta alla fine. */ + label = gtk_label_new ("end"); + /* impacchettiamola usando gtk_box_pack_end(), così che viene inserita + * sul lato destro della hbox creata nella chiamata a the make_box(). */ + gtk_box_pack_end (GTK_BOX (box2), label, FALSE, FALSE, 0); + /* mostriamo l'etichetta. */ + gtk_widget_show (label); + + /* impacchettiamo box2 in box1 (the vbox, ricordate? :) */ + gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0); + gtk_widget_show (box2); + + /* un separatore per il fondo */ + separator = gtk_hseparator_new (); + /* Questo assegna esplicitamente al separatore l'ampiezza di 400 pixel + * e l'altezza di 5 pixel. Ciò fa sì che la hbox che abbiamo creato sia + * anche essa larga 400 pixel, e che l'etichetta finale sia separata dalle + * altre etichette nella hbox. In caso contrario, tutti gli oggetti nella + * hbox sarebbero impacchettati il più vicino possibile. */ + gtk_widget_set_usize (separator, 400, 5); + /* impacchetta il separatore nella vbox (box1) creata vicino all'inizio + * di main() */ + gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 5); + gtk_widget_show (separator); + } + + /* Creare un'altra nuova hbox.. ricordate che ne possiamo usare quante ne vogliamo! */ + quitbox = gtk_hbox_new (FALSE, 0); + + /* Il nostro bottone di uscita. */ + button = gtk_button_new_with_label ("Quit"); + + + /* Configuriamo il segnale per distruggere la finestra. Ricordate che + * ciò manderà alla finestra il segnale "destroy", che verrà catturato + * dal nostro gestore di segnali che abbiamo definito in precedenza. */ + gtk_signal_connect_object (GTK_OBJECT (button), "clicked", + GTK_SIGNAL_FUNC (gtk_widget_destroy), + GTK_OBJECT (window)); + /* impacchetta il bottone in quitbox. + * Gli ultimi tre argomenti di gtk_box_pack_start sono: expand, fill, padding. */ + gtk_box_pack_start (GTK_BOX (quitbox), button, TRUE, FALSE, 0); + /* impacchetta quitbox nella vbox (box1) */ + gtk_box_pack_start (GTK_BOX (box1), quitbox, FALSE, FALSE, 0); + + /* impacchetta la vbox (box1), che ora contiene tutti i nostri oggetti, + * nella finestra principale. */ + gtk_container_add (GTK_CONTAINER (window), box1); + + /* e mostra tutto quel che rimane */ + gtk_widget_show (button); + gtk_widget_show (quitbox); + + gtk_widget_show (box1); + /* Mostriamo la finestra alla fine in modo che tutto spunti fuori assieme. */ + gtk_widget_show (window); + + /* E, naturalmente, la nostra funzione main. */ + gtk_main (); + + /* Il controllo ritorna a questo punto quando viene chiamata gtk_main_quit(), + * ma non quando si usa gtk_exit. */ + + return 0; +} +</verb></tscreen> + +<p> +<sect1>Impacchettamento con uso di Tabelle +<p> +Diamo ora un'occhiata ad un altro modo di impacchettare - le Tabelle. +In certe situazioni, possono risultare estremamente utili. + +Usando le tabelle, creiamo una griglia in cui possiamo piazzare gli oggetti. +Gli oggetti possono occupare tanti spazi quanti ne specifichiamo. + +Naturalmente, la prima cosa da vedere è la funzione gtk_table_new: + +<tscreen><verb> +GtkWidget* gtk_table_new (gint rows, + gint columns, + gint homogeneous); +</verb></tscreen> +<p> +Il primo argomento rappresenta il numero di righe da mettere nella tabella, +mentre il secondo è ovviamente il numero di colonne. + +L'argomento homogeneous ha a che fare con il modo in cui le caselle della tabella +sono dimensionate. Se homogeneous ha il valore TRUE, le caselle sono ridimensionate +fino alla dimensione del più grande oggetto contenuto nella tabelle. Se è FALSE, la +dimensione delle caselleè decisa dal più alto oggetto in una certa riga e dal più +largo oggetto in una stessa colonna. + +Le righe e le colonne sono disposte a partire da 0 fino a n, dove n è il numero +che era stato specificato nella chiamata a gtk_table_new. Così, se specificate +rows = 2 e columns = 2, lo schema avrà questo aspetto: + +<tscreen><verb> + 0 1 2 +0+----------+----------+ + | | | +1+----------+----------+ + | | | +2+----------+----------+ +</verb></tscreen> +<p> +Notate che il sistema di coordinate ha origine nel vertice in alto a sinistra. Per +mettere un oggetto in una tabella, usate la seguente funzione: + +<tscreen><verb> +void gtk_table_attach (GtkTable *table, + GtkWidget *child, + gint left_attach, + gint right_attach, + gint top_attach, + gint bottom_attach, + gint xoptions, + gint yoptions, + gint xpadding, + gint ypadding); +</verb></tscreen> +<p> +In cui il primo argomento (``table'') è la tabella che avete creato e il secondo +(``child'') è l'oggetto che volete piazzare nella tabella. + +Gli argomenti ``attach'' (right, left, top, bottom) specificano dove mettere l'oggetto +e quante caselle adoperare. Se volete mettere un bottone nella casella in basso a destra +nella nostra tabella 2x2, e volete che esso riempia SOLO quella casella, dovete porre +left_attach = 1, right_attach = 2, top_attach = 1, bottom_attach = 2. + +Se invece volete che un oggetto si prenda tutta la riga più in alto nella nostra tabella +2x2, dovreste usare left_attach = 0, right_attach =2, top_attach = 0, +bottom_attach = 1. + +Gli argomenti ``xoptions'' e ``yoptions'' sono usati per specificare le opzioni di impacchettamento; +di essi si può fare l'OR in modo di ottenere opzioni multiple. + +Le opzioni sono: +<itemize> +<item>GTK_FILL - Se la parte di tabella in cui si vuole inserire il widget è più +grande dell'oggetto, e se si specifica GTK_FILL, l'oggetto viene espanso fino ad +occupare tutto lo spazio disponibile. + +<item>GTK_SHRINK - Se si alloca all'oggetto nella tabella meno spazio del necessario +(di solito succede quando l'utente ridimensiona la finestra), allora normalmente +l'oggetto verrebbe spinto fuori dal fondo della finestra fino a sparire. +Se invece si specifica GTK_SHRINK is specified, gli oggetti si rimpiccioliscono +assieme alla tabella. + +<item>GTK_EXPAND - Questo fa sì che la tabella si espanda fino ad occupare tutto lo +spazio che rimane nella finestra. +</itemize> + +Il riempimento funziona come nelle scatole, con la creazione di un'area vuota +attorno all'oggetto la cui dimensione viene specificata in pixel. + +La funzione gtk_table_attach() ha UN MUCCHIO di opzioni. Quindi, ecco una scorciatoia: + +<tscreen><verb> +void gtk_table_attach_defaults (GtkTable *table, + GtkWidget *widget, + gint left_attach, + gint right_attach, + gint top_attach, + gint bottom_attach); +</verb></tscreen> + +Le xoptions e yoptions vengono posti per difetto a GTK_FILL | GTK_EXPAND, e sia xpadding +che ypadding vengono posti a 0. Il resto degli argomenti sono identici a quelli della funzione +precedente. + +Ci sono poi le funzioni gtk_table_set_row_spacing() and gtk_table_set_col_spacing(). +Queste mettono dello spazio fra le righe (o colonne)in corrispondenza di una specifica +riga (o colonna). + +<tscreen><verb> +void gtk_table_set_row_spacing (GtkTable *table, + gint row, + gint spacing); +</verb></tscreen> +e +<tscreen><verb> +void gtk_table_set_col_spacing (GtkTable *table, + gint column, + gint spacing); +</verb></tscreen> + +Notate che per le colonne lo spazio viene posto alla destra della colonna, mentre +per le righe lo spazio viene posto al di sotto della riga. + +Si può poi inserire una spaziatura identica fra tutte le righe e/o colonne usando: + +<tscreen><verb> +void gtk_table_set_row_spacings (GtkTable *table, + gint spacing); +</verb></tscreen> +<p> +e +<tscreen><verb> +void gtk_table_set_col_spacings (GtkTable *table, + gint spacing); +</verb></tscreen> +<p> +Notate che con queste chiamate, all'ultima riga e all'ultima colonna +non viene assegnata alcuna spaziatura. + +<sect1>Esempio di Impacchettamento con Tabelle +<p> +Per il momento, si prega di fare riferimento all'esempio di tabella in +testgtk.c distribuito con i sorgenti di gtk. + + +<sect>Panoramica sui Widget +<p> +<p> +La procedura generale di creazione di un widget in GTK prevede i seguenti passi: +<enum> +<item> gtk_*_new - una delle varie funzioni che servono per greare un nuovo widget. +In questa sezione le vedremo tutte in dettaglio. + +<item> Connettere tutti i segnali che si vogliono usare alle funzione gestione appropriate. + +<item> Assegnare gli attributi all'oggetto. + +<item> Impacchettare l'oggetto in un contenitore usando la chiamate appropriata, +per esempio gtk_container_add() o gtk_box_pack_start(). + +<item> Mostrare l'oggetto con gtk_widget_show(). +</enum> +<p> +gtk_widget_show() fa sì che GTK sappia che abbiamo terminato di assegnare gli +attributi dell'oggetto grafico, e che è pronto per essere visualizzato. +Si può anche usare la funzione gtk_widget_hide per farlo sparire di nuovo. +L'ordine in cui mostrate gli oggetti grafici non è importante, ma io suggerisco +di mostrare per ultima la finestra, in modo che questa spunti fuori già completa, +invece di vedere i singoli oggetti che arrivano sullo schermo a mano a mano che si +formano. I figli di un oggetto grafico (anche una finestra è un oggetto grafico) non +vengono infatti mostrati finché la finestra stessa non viene mostrata usando la +funzione gtk_widget_show(). + + +<sect1> Casting +<p> +Noterete andando avanti che GTK usa un sistema di casting di tipo. Questa operazione +viene sempre effettuata usando delle macro che allo stesso tempo controllano la +possibilità di effettuare il cast sull'elemento dato e lo effettuano realmente. +Alcune macro che avrete modo di incontrare sono: + +<itemize> +<item> GTK_WIDGET(widget) +<item> GTK_OBJECT(object) +<item> GTK_SIGNAL_FUNC(function) +<item> GTK_CONTAINER(container) +<item> GTK_WINDOW(window) +<item> GTK_BOX(box) +</itemize> + +Tutte queste funzioni sono usate per fare il cast di argomenti di funzione. Le vedrete +negli esempi, e capirete se è il caso di usarle semplicemente guardando alle +dichiarazioni delle funzioni. + +Come potrete vedere più sotto nella gerarchia delle classi, tutti i GtkWidgets +sono derivati dalla classe base GtkObject. Ciò significa che potete usare un +widget in ogni posto in cui una funzione richiede un oggetto - semplicemente +usate la macro GTK_OBJECT(). + +Per esempio: + +<tscreen><verb> +gtk_signal_connect(GTK_OBJECT(button), "clicked", + GTK_SIGNAL_FUNC(callback_function), callback_data); +</verb></tscreen> + +Questo fa il cast del bottone in un oggetto e fornisce alla chiamata di ritorno +un cast al puntatore a funzione. + +Molti oggetti grafici sono anche contenitori. Se guardate alla gerarchia delle +classi più sotto, vedrete che molti oggetti grafici sono derivati dalla classe +GtkContainer. Ognuna di queste classi può essere usata, con la macro GTK_CONTAINER, +come argomento per funzioni che richiedono un contenitore. + +Sfortunatamente, in questo tutorial non si parlerà in modo estensivo di queste macro, +ma raccomando di dare un'occhiata ai file header di GTK. Può essere una cosa molto +educativa. Infatti, non è difficile imparare come funziona un oggetto solo guardando +le dichiarazioni delle funzioni. + +<p> +<sect1>Gerarchia degli Oggetti Grafici +<p> +Ecco, per vostro riferimento, la gerarchia delle classi usata per implementare gli +oggetti grafici. + +<tscreen><verb> + GtkObject + +-- GtkData + | \-- GtkAdjustment + | + \-- GtkWidget + +-- GtkContainer + | +-- GtkBin + | | +-- GtkAlignment + | | +-- GtkFrame + | | | *-- GtkAspectFrame + | | | + | | +-- GtkItem + | | | +-- GtkListItem + | | | +-- GtkMenuItem + | | | | +-- GtkCheckMenuItem + | | | | *-- GtkRadioMenuItem + | | | | + | | | *-- GtkTreeItem + | | | + | | +-- GtkViewport + | | \-- GtkWindow + | | +-- GtkDialog + | | \-- GtkFileSelection + | | + | +-- GtkBox + | | +-- GtkHBox + | | \-- GtkVBox + | | +-- GtkColorSelection + | | \-- GtkCurve + | | + | +-- GtkButton + | | +-- GtkOptionMenu + | | \-- GtkToggleButton + | | \-- GtkCheckButton + | | \-- GtkRadioButton + | | + | +-- GtkList + | +-- GtkMenuShell + | | +-- GtkMenu + | | \-- GtkMenuBar + | | + | +-- GtkNotebook + | +-- GtkScrolledWindow + | +-- GtkTable + | \-- GtkTree + | + +-- GtkDrawingArea + +-- GtkEntry + +-- GtkMisc + | +-- GtkArrow + | +-- GtkImage + | +-- GtkLabel + | \-- GtkPixmap + | + +-- GtkPreview + +-- GtkProgressBar + +-- GtkRange + | +-- GtkScale + | | +-- GtkHScale + | | \-- GtkVScale + | | + | \-- GtkScrollbar + | +-- GtkHScrollbar + | \-- GtkVScrollbar + | + +-- GtkRuler + | +-- GtkHRuler + | \-- GtkVRuler + | + \-- GtkSeparator + +-- GtkHSeparator + \-- GtkVSeparator + +</verb></tscreen> +<p> + +<sect1>Oggetti senza Finestre +<p> +Gli oggetti seguenti non hanno una finestra associata. Se volete catturare +degli eventi, dovrete usare l'oggetto GtkEventBox. Vedete anche la sezione su +<ref id="sec_The_EventBox_Widget" name="Il Widget EventBox"> + +<tscreen><verb> +GtkAlignment +GtkArrow +GtkBin +GtkBox +GtkImage +GtkItem +GtkLabel +GtkPaned +GtkPixmap +GtkScrolledWindow +GtkSeparator +GtkTable +GtkViewport +GtkAspectFrame +GtkFrame +GtkVPaned +GtkHPaned +GtkVBox +GtkHBox +GtkVSeparator +GtkHSeparator +</verb></tscreen> +<p> +Proseguiremo la nostra esplorazione di GTK esaminando uno alla volta tutti +gli oggetti, creando qualche semplice funzione per mostrarli. Un'altra +buona sorgente è il programma testgtk.c che viene fornito con GTK. Potete +trovarlo in gtk/testgtk.c. + +<sect>Il Widget Bottone (Button) +<p> +<sect1>Bottoni Normali +<p> +Ormai abbiamo visto tutto quello che c'è da vedere riguardo all'oggetto +``bottone''. E' piuttosto semplice, ma ci sono due modi per crare un bottone. +Potete usare gtk_button_new_with_label() per creare un bottone con una +etichetta, o usare gtk_button_new() per creare un bottone vuoto. In tal caso è poi +vostro compito impacchettare un'etichetta o una pixmap sul bottone creato. +Per fare ciò, create una nuova scatola, e poi impacchettateci i vostri +oggetti usando la solita gtk_box_pack_start, e infine usate la funzione +gtk_container_add per impacchettare la scatola nel bottone. +<p> +Ecco un esempio di utilizzo di gtk_button_new per creare un bottone con +un'immagine ed un'etichetta su di sè. Ho separato il codice usato per +creare la scatola in modo che lo possiate usare nei vostri programmi. + +<tscreen><verb> +#include <gtk/gtk.h> + + +/* crea una nuova hbox contenente un'immagine ed un'etichetta + * e ritorna la scatola creata. */ + +GtkWidget *xpm_label_box (GtkWidget *parent, gchar *xpm_filename, gchar *label_text) +{ + GtkWidget *box1; + GtkWidget *label; + GtkWidget *pixmapwid; + GdkPixmap *pixmap; + GdkBitmap *mask; + GtkStyle *style; + + /* creare una scatola per una xpm ed una etichetta */ + box1 = gtk_hbox_new (FALSE, 0); + gtk_container_border_width (GTK_CONTAINER (box1), 2); + + /* ottengo lo stile del bottone. Penso che sia per avere il colore + * dello sfondo. Se qualcuno sa il vero motivo, è pregato di dirmelo. */ + style = gtk_widget_get_style(parent); + + /* e ora via con le faccende dell'xpm stuff. Carichiamo l'xpm*/ + pixmap = gdk_pixmap_create_from_xpm (parent->window, &mask, + &style->bg[GTK_STATE_NORMAL], + xpm_filename); + pixmapwid = gtk_pixmap_new (pixmap, mask); + + /* creiamo l'etichetta per il bottone */ + label = gtk_label_new (label_text); + + /* impacchettiamo la pixmap e l'etichetta nella scatola */ + gtk_box_pack_start (GTK_BOX (box1), + pixmapwid, FALSE, FALSE, 3); + + gtk_box_pack_start (GTK_BOX (box1), label, FALSE, FALSE, 3); + + gtk_widget_show(pixmapwid); + gtk_widget_show(label); + + return (box1); +} + +/* la nostra solita funzione di callback */ +void callback (GtkWidget *widget, gpointer *data) +{ + g_print ("Hello again - %s was pressed\n", (char *) data); +} + + +int main (int argc, char *argv[]) +{ + /* GtkWidget è il tipo per contenere gli oggetti */ + GtkWidget *window; + GtkWidget *button; + GtkWidget *box1; + + gtk_init (&argc, &argv); + + /* creiamo una nuova finestra */ + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + + gtk_window_set_title (GTK_WINDOW (window), "Pixmap'd Buttons!"); + + /* E' una buona idea fare questo per tutte le finestre. */ + gtk_signal_connect (GTK_OBJECT (window), "destroy", + GTK_SIGNAL_FUNC (gtk_exit), NULL); + + + /* assegnamo lo spessore del bordo della finestra */ + gtk_container_border_width (GTK_CONTAINER (window), 10); + + /* creiamo un nuovo bottone */ + button = gtk_button_new (); + + /* Ormai dovreste esservi abituati a vedere la maggior parte di + * queste funzioni */ + gtk_signal_connect (GTK_OBJECT (button), "clicked", + GTK_SIGNAL_FUNC (callback), (gpointer) "cool button"); + + /* questa chiama la nostra funzione di creazione di scatole */ + box1 = xpm_label_box(window, "info.xpm", "cool button"); + + /* impacchetta e mostra tutti i nostri oggetti */ + gtk_widget_show(box1); + + gtk_container_add (GTK_CONTAINER (button), box1); + + gtk_widget_show(button); + + gtk_container_add (GTK_CONTAINER (window), button); + + gtk_widget_show (window); + + /* mettiti in gtk_main e aspetta che cominci il divertimento! */ + gtk_main (); + + return 0; +} +</verb></tscreen> +La funzione xpm_label_box può essere usata per impacchettare delle xpm +e delle etichette su qualsiasi oggetto che può essere un contenitore. + +<sect1> Bottoni a Commutazione (Toggle Buttons) +<p> +I bottoni a commutazione sono molto simili ai bottoni normali, tranne che per il +fatto che essi si trovano sempre in uno di due stati, che si alternano ad ogni +click. Possono trovarsi nello stato ``premuto'', e quando li si ripreme, tornano +ad essere sollevati. Ri-clickandoli, torneranno giù. + +I bottoni a commutazione sono la base per i bottoni di controllo (check button) e +per i radio-bottoni, e quindi molte delle chiamate disponibili per i bottoni +a commutazione vengono ereditati dai radio-bottoni e dai bottoni di controllo. +Ma vedremo questi aspetti nel momento in cui li incontreremo. + +Creare un nuovo bottone a commutazione: + +<tscreen><verb> +GtkWidget* gtk_toggle_button_new (void); + +GtkWidget* gtk_toggle_button_new_with_label (gchar *label); +</verb></tscreen> +<p> +Come potete immaginare, queste funzioni lavorano in modo identico che per +i bottoni normali. La prima crea un bottone a commutazione vuoto e la seconda un +bottone con un'etichetta. +<p> +Per ottenere lo stato dei widget a commutazione, compresi i radio-bottoni e i +bottoni di controllo, si può usare una macro come mostrato nell'esempio +più sotto. In questo modo lo stato dell'oggetto commutabile viene valutato in +una funzione di ritorno. Il segnale emesso dai bottoni a commutazione +(toggle button, il radio button o il check button) che ci interessa è il segnale +``toggled''. Per controllare lo stato di questi bottoni, create un gestore di +segnali che catturi il ``toggled'', e usate la macro per determinare +il suo stato. La funzione di callback avrà un aspetto più o meno così: + +<tscreen><verb> +void toggle_button_callback (GtkWidget *widget, gpointer data) + { + if (GTK_TOGGLE_BUTTON (widget)->active) + { + /* Se il programma si è arrivato a questo punto, il bottone + * a commutazione è sollevato */ + + } else { + + /* il bottone è abbassato */ + } + } + </verb></tscreen> + +<!-- + +COMMENTED! + +<tscreen><verb> +guint gtk_toggle_button_get_type (void); +</verb></tscreen> +<p> +No idea... they all have this, but I dunno what it is :) + + +<tscreen><verb> +void gtk_toggle_button_set_mode (GtkToggleButton *toggle_button, + gint draw_indicator); +</verb></tscreen> +<p> +No idea. +--> + +<tscreen><verb> +void gtk_toggle_button_set_state (GtkToggleButton *toggle_button, + gint state); +</verb></tscreen> +<p> +La chiamata qui sopra può essere usata per fare l'assegnazione dello stato +del bottone a commutazione e dei suoi figli, il radio-bottone e il bottone di +controllo. Passando come primo argomento a questa funzione il vostro bottone e +come secondo argomento il valore TRUE o FALSE, si può specificare se il +bottone deve essere sollevato (rilasciato) o abbassato (premuto). Il valore +di difetto è sollevato, cioè FALSE. + +Notate che quando usate la funzione gtk_toggle_button_set_state(), e lo +stato viene cambiato, si ha il risultato che il bottone emette il segnale +``clicked''. + +<tscreen><verb> +void gtk_toggle_button_toggled (GtkToggleButton *toggle_button); +</verb></tscreen> +<p> +Questa funzione semplicemente commuta il bottone, ed emette il segnale ``toggled''. + +<sect1> Bottoni di Controllo (Check Buttons) +<p> +I bottoni di controllo ereditano molte proprietà e funzioni dal bottone a commutazione, +ma hanno un aspetto un po' diverso. Invece di essere bottoni contenenti del testo, +si tratta di quadratini con del testo alla propria destra. Questi bottoni sono +spesso usati nelle applicazioni per commutare fra lo stato attivato e disattivato delle +opzioni. + +Le due funzioni di creazione sono analoghe a quelle del bottone normale.. + +<tscreen><verb> +GtkWidget* gtk_check_button_new (void); + +GtkWidget* gtk_check_button_new_with_label (gchar *label); +</verb></tscreen> + +La funzione new_with_label crea un bottone di controllo con una etichetta +a fianco di esso. + +Per controllare lo stato del check button si opera in modo identico al bottone +a commutazione. + +<sect1> Radio-Bottoni (Radio Buttons) +<p> +I radio-bottoni sono simili ai bottoni di controllo, tranne che per il +fatto che sono sempre raggruppati in modo che solo uno alla volta di essi +può essere selezionato (premuto). Tornano utili quando nella propria applicazione +si ha bisogno di selezionare una opzione da una breve lista. + +La creazione di un nuovo radio-bottone si fa con una di queste chiamate: + +<tscreen><verb> +GtkWidget* gtk_radio_button_new (GSList *group); + +GtkWidget* gtk_radio_button_new_with_label (GSList *group, + gchar *label); +</verb></tscreen> +<p> +Avrete notato l'argomento in più che c'è in queste chiamate. Queste hanno +infatti bisogno dela specificazione di un ``gruppo'' per svolgere il loro compito. +Per il primo bottone di un gruppo si deve passare come primo argomento il valore +NULL. Dopodiché potete creare un gruppo usando la funzione: + +<tscreen><verb> +GSList* gtk_radio_button_group (GtkRadioButton *radio_button); +</verb></tscreen> + +<p> +A questo punto potete passare questo gruppo ad ogni chiamata successiva a +gtk_radio_button_new o new_with_label. E' anche una buona idea specificare +esplicitamente quale dei bottoni dovrà essere quello premuto per difetto, +usando: + +<tscreen><verb> +void gtk_toggle_button_set_state (GtkToggleButton *toggle_button, + gint state); +</verb></tscreen> +<p> +Questa funzione è descritta nella sezione sui bottoni a commutazione, e funziona +nello stesso identico modo. + +<p> +[Inserirò un esempio di come usare questi oggetti, penso che sarebbe molto +utile] + + +<sect> Alcuni Widget +<p> +<sect1> L'Etichetta (Label) +<p> +Le etichette sono molto usate in GTK, e sono relativamente semplici. Le +etichette non emettono segnali, dal momento che non hanno una finestra +X a loro assegnata. Se avete la necessità di avere dei segnali o di fare +delle operazioni di clipping, potete usare il widget EventBox. + +Per creare una nuova etichetta, si usa: + +<tscreen><verb> +GtkWidget* gtk_label_new (char *str); +</verb></tscreen> + +In cui l'unico argomento è la stringa che si vuole sia mostrata. + +Per cambiare il testo dell'etichetta dopo che è stata creata, si usa +la funzione: + +<tscreen><verb> +void gtk_label_set (GtkLabel *label, + char *str); +</verb></tscreen> +<p> +in cui il primo argomento è l'etichetta creata in precedenza (di cui si +fa il cast usando la macro GTK_LABEL()), mentre il secondo è la nuova +stringa. + +Nel caso, lo spazio necessario per la nuova stringa verrà regolato automaticamente. + +Per ottenere la stringa corrente si usa: + +<tscreen><verb> +void gtk_label_get (GtkLabel *label, + char **str); +</verb></tscreen> + +in cui il primo argomento è l'etichetta che avete creato, e il secondo +è il valore di ritorno per la stringa. + + +<sect1>Il Widget Suggerimenti (Tooltips) +<p> +I suggerimenti sono piccole stringhe di testo che spuntano quando lasciate il +puntatore su un bottone o un altro widget per qualche secondo. Sono piuttosto +semplici da usare, per cui ne darò la spiegazione senza corredarla di esempi. +Se volede vedere un po' di codice, date un'occhiata al programma testgtk.c +distribuito con GTK. +<p> +Con alcuni widget (per esempio con l'etichetta) i suggerimenti non funzionano. +<p> +La prima chiamata che si usa per creare un nuovo tooltip è la seguente. +In una data funzione, è necessario chiamarla una sola volta: il GtkTooltip +che viene ritornato da questa funzione può essere usato per creare suggerimenti +multipli. + +<tscreen><verb> +GtkTooltips *gtk_tooltips_new (void); +</verb></tscreen> + +Una volta creato un nuovo suggerimento e il widget su cui lo volete usare, +basta usare la seguente chiamata per fare l'assegnazione: + +<tscreen><verb> +void gtk_tooltips_set_tips (GtkTooltips *tooltips, + GtkWidget *widget, + gchar *tips_text); +</verb></tscreen> + +Il primo argomento è il suggerimento che era già stato creato, che è seguito +dal widget da cui volete che spunti il suggerimento e dal testo che volete +venga mostrato. +<p> +Ecco un piccolo esempio: + +<tscreen><verb> +GtkTooltips *tooltips; +GtkWidget *button; +... +tooltips = gtk_tooltips_new (); +button = gtk_button_new_with_label ("button 1"); +... +gtk_tooltips_set_tips (tooltips, button, "This is button 1"); +</verb></tscreen> + +Ci sono anche altre funzioni che si usano con i suggerimenti. Eccone una lista +con una breve descrizione di quello che fanno. + +<tscreen><verb> +void gtk_tooltips_destroy (GtkTooltips *tooltips); +</verb></tscreen> + +Distrugge un suggerimento esistente. + +<tscreen><verb> +void gtk_tooltips_enable (GtkTooltips *tooltips); +</verb></tscreen> + +Abilita un gruppo di suggerimenti disbilitato. + +<tscreen><verb> +void gtk_tooltips_disable (GtkTooltips *tooltips); +</verb></tscreen> + +Disabilita un gruppo di suggerimenti abilitato. + +<tscreen><verb> +void gtk_tooltips_set_delay (GtkTooltips *tooltips, + gint delay); + +</verb></tscreen> +Stabilisce quanti millisecondi si deve mantenere il puntatore sopra al +widget prima che venga mostrato il suggerimento. Il valore di difetto +è di 1000 millisecondi. + +<tscreen><verb> +void gtk_tooltips_set_tips (GtkTooltips *tooltips, + GtkWidget *widget, + gchar *tips_text); +</verb></tscreen> + +Cambia il testo di un suggerimento già esistente. + +<tscreen><verb> +void gtk_tooltips_set_colors (GtkTooltips *tooltips, + GdkColor *background, + GdkColor *foreground); +</verb></tscreen> + +Assegna i colori di primo piano e di sfondo dei suggerimenti. (Non ho idea +di come si specifichino i colori). +<p> +E questo è tutto riguardo alle funzioni relative ai suggerimenti. Più +di quanto avreste mai voluto sapere :) + +<sect1> La Barra di Avanzamento (Progress Bar) +<p> +Le barre di avanzamento sono usate per mostrare lo stato di una operazione. Come potete +vedere nel frammento di codice qui sotto, sono piuttosto semplici da usare. +Ma prima vediamo come cominciare con la chiamata per creare una nuova progrss +bar. + +<tscreen><verb> +GtkWidget *gtk_progress_bar_new (void); +</verb></tscreen> + +Ora che la barra di avanzamento è stata creata, possiamo usarla.. + +<tscreen><verb> +void gtk_progress_bar_update (GtkProgressBar *pbar, gfloat percentage); +</verb></tscreen> + +Il primo argomento è la barra di avanzamento su cui volete lavorare, e il secondo +è la quantità 'completato', cioè la quantità di riempimento della progress +bar fra 0 e 100% (un numero reale fra 0 e 1). + +Le barre di avanzamento sono usate di solito con funzioni di timeout o altre di +questo tipo (vedi alla sezione <ref id="sec_timeouts" name="Timeouts, +I/O and Idle Functions">) per dare l'illusione del multitasking. Tutte +usano la funzione gtk_progress_bar_update nello stesso modo. + +Ecco un esempio di barra di avanzamento, in cui l'aggiornamento avviene usando +dei timeout. Questo codice vi mostra anche come riinizializzare le +barre di avanzamento. + +<tscreen><verb> +#include <gtk/gtk.h> + +static int ptimer = 0; +int pstat = TRUE; + +/* Questa funzione incrementa e aggiorna la barra di avanzamento, e la rimette + a zero se pstat è FALSE */ +gint progress (gpointer data) +{ + gfloat pvalue; + + /* ottiene il valore corrente della status bar */ + pvalue = GTK_PROGRESS_BAR (data)->percentage; + + if ((pvalue >= 1.0) || (pstat == FALSE)) { + pvalue = 0.0; + pstat = TRUE; + } + pvalue += 0.01; + + gtk_progress_bar_update (GTK_PROGRESS_BAR (data), pvalue); + + return TRUE; +} + +/* Questa funzione segnala la riinizializzazione della + barra di avanzamento */ +void progress_r (void) +{ + pstat = FALSE; +} + +void destroy (GtkWidget *widget, gpointer *data) +{ + gtk_main_quit (); +} + +int main (int argc, char *argv[]) +{ + GtkWidget *window; + GtkWidget *button; + GtkWidget *label; + GtkWidget *table; + GtkWidget *pbar; + + gtk_init (&argc, &argv); + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + + gtk_signal_connect (GTK_OBJECT (window), "delete_event", + GTK_SIGNAL_FUNC (destroy), NULL); + + gtk_container_border_width (GTK_CONTAINER (window), 10); + + table = gtk_table_new(3,2,TRUE); + gtk_container_add (GTK_CONTAINER (window), table); + + label = gtk_label_new ("Progress Bar Example"); + gtk_table_attach_defaults(GTK_TABLE(table), label, 0,2,0,1); + gtk_widget_show(label); + /* Crea una nuova barra di avanzamento, impacchettala nella tabella + e mostrala */ + pbar = gtk_progress_bar_new (); + gtk_table_attach_defaults(GTK_TABLE(table), pbar, 0,2,1,2); + gtk_widget_show (pbar); + + /* Attiva un timeout che gestisca l'aggiornamento automatico della barra */ + ptimer = gtk_timeout_add (100, progress, pbar); + + /* Questo bottone segnala alla barra che deve essere resettata */ + button = gtk_button_new_with_label ("Reset"); + gtk_signal_connect (GTK_OBJECT (button), "clicked", + GTK_SIGNAL_FUNC (progress_r), NULL); + gtk_table_attach_defaults(GTK_TABLE(table), button, 0,1,2,3); + gtk_widget_show(button); + + button = gtk_button_new_with_label ("Cancel"); + gtk_signal_connect (GTK_OBJECT (button), "clicked", + GTK_SIGNAL_FUNC (destroy), NULL); + + gtk_table_attach_defaults(GTK_TABLE(table), button, 1,2,2,3); + gtk_widget_show (button); + + gtk_widget_show(table); + gtk_widget_show(window); + + gtk_main (); + + return 0; +} +</verb></tscreen> + +In questo programmino ci sono quattro aree che riguardano il modo di +uso generale delle Barre di Avanzamento; le vediamo ora nell'ordine. + +<tscreen><verb> +pbar = gtk_progress_bar_new (); +</verb></tscreen> + +Questo codice crea una nuova barra ciamata pbar. + +<tscreen><verb> +ptimer = gtk_timeout_add (100, progress, pbar); +</verb></tscreen> + +Questo codice usa dei timeout per abilitare degli intervalli di tempo uguali. +Per usare le barre di avanzamento non è però necessario servirsi di timeout. + +<tscreen><verb> +pvalue = GTK_PROGRESS_BAR (data)->percentage; +</verb></tscreen> + +Qui si assegna a pvalue il valore corrente della percentuale di avanzamento. + +<tscreen><verb> +gtk_progress_bar_update (GTK_PROGRESS_BAR (data), pvalue); +</verb></tscreen> + +Infine, questo codice aggiorna la barra di avanzamento con il valore di pvalue. + +Questo è tutto quanto c'è da sapere sulle barre di avanzamento, divertitevi. + +<sect1> Dialoghi +<p> + +Il widget ``Dialogo'' è molto semplice: si tratta in realtà di una finestra +con alcuni elementi pre-impacchettati. La struttura di un dialogo è la +seguente: + +<tscreen><verb> +struct GtkDialog +{ + GtkWindow window; + + GtkWidget *vbox; + GtkWidget *action_area; +}; +</verb></tscreen> + +Come potete vedere, crea semplicemente una finestra vi inserisce una vbox +in cima, poi un separatore e infine una hbox come ``area di azione''. + +Un Dialogo può essere utilizzato per messaggi per l'utente e +altri scopi simili. E' un widget molto essenziale, che ha una sola funzione, +e precisamente: + +<tscreen><verb> +GtkWidget* gtk_dialog_new (void); +</verb></tscreen> + +Per cui, per creare una nuova finestra di dialogo, uate: + +<tscreen><verb> +GtkWidget window; +window = gtk_dialog_new (); +</verb></tscreen> + +Questa funzione crea una finestra di dialogo, dopodiché sta a voi +utilizzarla. Potete mettere un bottone nella action_area facendo +qualcosa del tipo: + +<tscreen><verb> +button = ... +gtk_box_pack_start (GTK_BOX (GTK_DIALOG (window)->action_area), button, + TRUE, TRUE, 0); +gtk_widget_show (button); +</verb></tscreen> + +Potreste anche aggiungere, ad esempio, un'etichetta all'area della vbox, +con qualcosa di questo genere: + +<tscreen><verb> +label = gtk_label_new ("Dialogs are groovy"); +gtk_box_pack_start (GTK_BOX (GTK_DIALOG (window)->vbox), label, TRUE, + TRUE, 0); +gtk_widget_show (label); +</verb></tscreen> + +Per provare a usare una finestra di dialogo, potreste provare a mettere +due bottoni nella action_area, per esempio un bottone ``Cancella'' ed un +bottone ``OK'' e un'etichetta nella vbox che chieda qualcosa all'utente o +segnali un errore. Poi potreste collegare un diverso segnale a ciascun +bottone ed eseguire l'operazione che l'utente che viene scelta dall'utente. + + +<sect1> Pixmaps +<p> + +Le Pixmap sono strutture dati che contengono immagini. Queste immagini +possono poi essere utilizzate in varie occasioni, per esempio come +icone sul desktop X-Window o come cusori. Una bitmap è una pixmap a due +colori. + +Per usare una pixmap in GTK, dobbiamo in primo luogo creare una struttura +GdkPixmap utilizzando le routine disponibili nello strato GDK. Una Pixmap +può essere creata a partire da dati presenti in memoria o letti da un file. +Vedremo ora una ad una le chiamate utilizzate per creare una pixmap. + +<tscreen><verb> +GdkPixmap *gdk_bitmap_create_from_data( GdkWindow *window, + gchar *data, + gint width, + gint height ); +</verb></tscreen> +<p> +Si usa questa routine per creare una pixmap ad un solo piano (2 colori) da +dati disponibili in memoria. Ogni bit nei dati indica lo stato acceso o +spento di un pixel. L'altezza (height) e la larghezza (width) sono espresse + in pixel. GdkWindow è un puntatore alla finestra corrente, dal momento che +le risorse di una pixmap hanno significato solo nel contesto dello schermo +in cui deve essere mostrata. + +<tscreen><verb> +GdkPixmap* gdk_pixmap_create_from_data( GdkWindow *window, + gchar *data, + gint width, + gint height, + gint depth, + GdkColor *fg, + GdkColor *bg ); +</verb></tscreen> + +Questa è usata per creare una pixmap con la profondità data (depth, ossia +numero di colori) usando i dati specificati. fg e bg indicano i colori da +usare per il primo piano e per lo sfondo. + +<tscreen><verb> +GdkPixmap* gdk_pixmap_create_from_xpm( GdkWindow *window, + GdkBitmap **mask, + GdkColor *transparent_color, + const gchar *filename ); +</verb></tscreen> + +Il formato XPM è una rappresentazione di pixmap leggibile per X Window. E' una +rappresentazione molto diffusa, e sono disponibili parecchi programmi per creare +immagini in questo formato. Il file specificato da ``filename'' deve contenere +un'immagine in questo formato, che viene caricato nella struttura pixmap. +La maschera (mask) specifica quali pixel della pixmap devono essere opachi. +Tutti gli altri pixel sono colorati usando il colore specificato da +transparent_color. Più sotto mostreremo un esempio di uso di questa funzione. + +<tscreen><verb> +GdkPixmap* gdk_pixmap_create_from_xpm_d (GdkWindow *window, + GdkBitmap **mask, + GdkColor *transparent_color, + gchar **data); +</verb></tscreen> + +Si possono incorporare piccole immagini all'interno di un programma sotto +forma di dati in formato XPM. In questo modo, invece di leggerli da un file, +si possono usare questi dati per creare una pixmap. Un esempio di questo tipo +di dati è + +<tscreen><verb> +/* XPM */ +static const char * xpm_data[] = { +"16 16 3 1", +" c None", +". c #000000000000", +"X c #FFFFFFFFFFFF", +" ", +" ...... ", +" .XXX.X. ", +" .XXX.XX. ", +" .XXX.XXX. ", +" .XXX..... ", +" .XXXXXXX. ", +" .XXXXXXX. ", +" .XXXXXXX. ", +" .XXXXXXX. ", +" .XXXXXXX. ", +" .XXXXXXX. ", +" .XXXXXXX. ", +" ......... ", +" ", +" "}; +</verb></tscreen> + +<tscreen><verb> +void gdk_pixmap_destroy( GdkPixmap *pixmap ); +</verb></tscreen> +<p> +Quando abbiamo finito di usare una pixmap e pensiamo di non doverla riutilizzare +presto, è una buona idea liberare queste risorse usando la funzione +dk_pixmap_destroy. Le pixmap devono essere considerate una risorsa preziosa. + +Quando abbiamo creato una pixmap, possiamo mostrarla come un widget GTK. +E' necessario creare un widget pixmap che contenga una pixmap GDK. Questa +operazione viene compiuta usando + +<tscreen><verb> +GtkWidget* gtk_pixmap_new( GdkPixmap *pixmap, + GdkBitmap *mask ); +</verb></tscreen> +<p> +Le altre chiamate per i widget pixmap sono + +<tscreen><verb> +guint gtk_pixmap_get_type( void ); +void gtk_pixmap_set( GtkPixmap *pixmap, + GdkPixmap *val, + GdkBitmap *mask); +void gtk_pixmap_get( GtkPixmap *pixmap, + GdkPixmap **val, + GdkBitmap **mask); +</verb></tscreen> +<p> +La funzione gtk_pixmap_set viene usata per cambiare la pixmap che viene +gestita correntemente dal widget. +gtk_pixmap_set is used to change the pixmap that the widget is currently +managing. ``val'' è la pixmap che è stata creata usando il GDK. +Segue un esempio di uso di una pixmap in un bottone. + +<tscreen><verb> + +#include <gtk/gtk.h> + + +/* dat XPM dell'icona Apri File */ +static const char * xpm_data[] = { +"16 16 3 1", +" c None", +". c #000000000000", +"X c #FFFFFFFFFFFF", +" ", +" ...... ", +" .XXX.X. ", +" .XXX.XX. ", +" .XXX.XXX. ", +" .XXX..... ", +" .XXXXXXX. ", +" .XXXXXXX. ", +" .XXXXXXX. ", +" .XXXXXXX. ", +" .XXXXXXX. ", +" .XXXXXXX. ", +" .XXXXXXX. ", +" ......... ", +" ", +" "}; + + +/* quando invocata (con il segnale delete_event), termina l'applicazione. */ +void close_application( GtkWidget *widget, gpointer *data ) { + gtk_main_quit(); +} + + +/* invocata se il bottone è clickato. Stampa semplicemente un messaggio */ +void button_clicked( GtkWidget *widget, gpointer *data ) { + printf( "button clicked\n" ); +} + + + + +int main( int argc, char *argv[] ) +{ + /* i widget sono memorizzati nel tipo GtkWidget */ + GtkWidget *window, *pixmapwid, *button; + GdkPixmap *pixmap; + GdkBitmap *mask; + GtkStyle *style; + + /* crea la finestra principale, e collega il segnale delete_event + alla terminazione dell'applicazione */ + gtk_init( &argc, &argv ); + window = gtk_window_new( GTK_WINDOW_TOPLEVEL ); + gtk_signal_connect( GTK_OBJECT (window), "delete_event", + GTK_SIGNAL_FUNC (close_application), NULL ); + gtk_container_border_width( GTK_CONTAINER (window), 10 ); + gtk_widget_show( window ); + + /* la pixmap proviene da gdk */ + style = gtk_widget_get_style( window ); + pixmap = gdk_pixmap_create_from_xpm_d( window->window, &mask, + &style->bg[GTK_STATE_NORMAL], + (gchar **)xpm_data ); + + /* un widget pixmap per contenere la pixmap */ + pixmapwid = gtk_pixmap_new( pixmap, mask ); + gtk_widget_show( pixmapwid ); + + /* un bottone per contenere il widget pixmap */ + button = gtk_button_new(); + gtk_container_add( GTK_CONTAINER(button), pixmapwid ); + gtk_container_add( GTK_CONTAINER(window), button ); + gtk_widget_show( button ); + + gtk_signal_connect( GTK_OBJECT(button), "clicked", + GTK_SIGNAL_FUNC(button_clicked), NULL ); + + /* mostra la finestra */ + gtk_main (); + + return 0; +} +</verb></tscreen> + + +Per caricare una pixmap da un file XPM chiamato icon0.xpm che si trova +nella direttorio corrente, avremmo creato la pixmap in questo modo: + +<tscreen><verb> + /* carica una pixmap da un file */ + pixmap = gdk_pixmap_create_from_xpm( window->window, &mask, + &style->bg[GTK_STATE_NORMAL], + "./icon0.xpm" ); + pixmapwid = gtk_pixmap_new( pixmap, mask ); + gtk_widget_show( pixmapwid ); + gtk_container_add( GTK_CONTAINER(window), pixmapwid ); +</verb></tscreen> + + +Usare le Sagome +<p> +Uno degli svantaggi di usare le pixmap è costituito dal fatto che l'oggetto +mostrato è sempre rettangolare, a prescindere dall'immagine. Ci piacerebbe +invece poter crare dei desktop e delle immagini con forme più naturali. Per +esempio, per l'interfaccia di un gioco, potremmo volere avere dei pulsanti +circolari. Il modo per ottenere questo effetto è di usare delle finestre +sagomate. + +Una finestra sagomata è semplicemente una pixmap in cui i pixel dello +sfondo sono trasparenti. In questo modo, se l'immagine di sfondo è +multicolore, possiamo evitare di sovrascriverla con un bordo rettangolare +attorno all'icona. Il prossimo esempio mostra una carriola sul desktop. + +<tscreen><verb> + +#include <gtk/gtk.h> + + + +/* XPM */ +static char * WheelbarrowFull_xpm[] = { +"48 48 64 1", +" c None", +". c #DF7DCF3CC71B", +"X c #965875D669A6", +"o c #71C671C671C6", +"O c #A699A289A699", +"+ c #965892489658", +"@ c #8E38410330C2", +"# c #D75C7DF769A6", +"$ c #F7DECF3CC71B", +"% c #96588A288E38", +"& c #A69992489E79", +"* c #8E3886178E38", +"= c #104008200820", +"- c #596510401040", +"; c #C71B30C230C2", +": c #C71B9A699658", +"> c #618561856185", +", c #20811C712081", +"< c #104000000000", +"1 c #861720812081", +"2 c #DF7D4D344103", +"3 c #79E769A671C6", +"4 c #861782078617", +"5 c #41033CF34103", +"6 c #000000000000", +"7 c #49241C711040", +"8 c #492445144924", +"9 c #082008200820", +"0 c #69A618611861", +"q c #B6DA71C65144", +"w c #410330C238E3", +"e c #CF3CBAEAB6DA", +"r c #71C6451430C2", +"t c #EFBEDB6CD75C", +"y c #28A208200820", +"u c #186110401040", +"i c #596528A21861", +"p c #71C661855965", +"a c #A69996589658", +"s c #30C228A230C2", +"d c #BEFBA289AEBA", +"f c #596545145144", +"g c #30C230C230C2", +"h c #8E3882078617", +"j c #208118612081", +"k c #38E30C300820", +"l c #30C2208128A2", +"z c #38E328A238E3", +"x c #514438E34924", +"c c #618555555965", +"v c #30C2208130C2", +"b c #38E328A230C2", +"n c #28A228A228A2", +"m c #41032CB228A2", +"M c #104010401040", +"N c #492438E34103", +"B c #28A2208128A2", +"V c #A699596538E3", +"C c #30C21C711040", +"Z c #30C218611040", +"A c #965865955965", +"S c #618534D32081", +"D c #38E31C711040", +"F c #082000000820", +" ", +" .XoO ", +" +@#$%o& ", +" *=-;#::o+ ", +" >,<12#:34 ", +" 45671#:X3 ", +" +89<02qwo ", +"e* >,67;ro ", +"ty> 459@>+&& ", +"$2u+ ><ipas8* ", +"%$;=* *3:.Xa.dfg> ", +"Oh$;ya *3d.a8j,Xe.d3g8+ ", +" Oh$;ka *3d$a8lz,,xxc:.e3g54 ", +" Oh$;kO *pd$%svbzz,sxxxxfX..&wn> ", +" Oh$@mO *3dthwlsslszjzxxxxxxx3:td8M4 ", +" Oh$@g& *3d$XNlvvvlllm,mNwxxxxxxxfa.:,B* ", +" Oh$@,Od.czlllllzlmmqV@V#V@fxxxxxxxf:%j5& ", +" Oh$1hd5lllslllCCZrV#r#:#2AxxxxxxxxxcdwM* ", +" OXq6c.%8vvvllZZiqqApA:mq:Xxcpcxxxxxfdc9* ", +" 2r<6gde3bllZZrVi7S@SV77A::qApxxxxxxfdcM ", +" :,q-6MN.dfmZZrrSS:#riirDSAX@Af5xxxxxfevo", +" +A26jguXtAZZZC7iDiCCrVVii7Cmmmxxxxxx%3g", +" *#16jszN..3DZZZZrCVSA2rZrV7Dmmwxxxx&en", +" p2yFvzssXe:fCZZCiiD7iiZDiDSSZwwxx8e*>", +" OA1<jzxwwc:$d%NDZZZZCCCZCCZZCmxxfd.B ", +" 3206Bwxxszx%et.eaAp77m77mmmf3&eeeg* ", +" @26MvzxNzvlbwfpdettttttttttt.c,n& ", +" *;16=lsNwwNwgsvslbwwvccc3pcfu<o ", +" p;<69BvwwsszslllbBlllllllu<5+ ", +" OS0y6FBlvvvzvzss,u=Blllj=54 ", +" c1-699Blvlllllu7k96MMMg4 ", +" *10y8n6FjvllllB<166668 ", +" S-kg+>666<M<996-y6n<8* ", +" p71=4 m69996kD8Z-66698&& ", +" &i0ycm6n4 ogk17,0<6666g ", +" N-k-<> >=01-kuu666> ", +" ,6ky& &46-10ul,66, ", +" Ou0<> o66y<ulw<66& ", +" *kk5 >66By7=xu664 ", +" <<M4 466lj<Mxu66o ", +" *>> +66uv,zN666* ", +" 566,xxj669 ", +" 4666FF666> ", +" >966666M ", +" oM6668+ ", +" *4 ", +" ", +" "}; + + +/* quando invocata (con il segnale delete_event), termina l'applicazione. */ +void close_application( GtkWidget *widget, gpointer *data ) { + gtk_main_quit(); +} + + +int main (int argc, char *argv[]) +{ + /* il tipo di dato per i widget è GtkWidget */ + GtkWidget *window, *pixmap, *fixed; + GdkPixmap *gdk_pixmap; + GdkBitmap *mask; + GtkStyle *style; + GdkGC *gc; + + /* crea la finestra principale e collega il segnale delete_event per + terminare l'applicazione. Notare che non mettiamo un titolo + alla finestra. */ + gtk_init (&argc, &argv); + window = gtk_window_new( GTK_WINDOW_POPUP ); + gtk_signal_connect (GTK_OBJECT (window), "delete_event", + GTK_SIGNAL_FUNC (close_application), NULL); + gtk_widget_show (window); + + /* ora occupiamoci della pixmap e del widget pixmap */ + style = gtk_widget_get_default_style(); + gc = style->black_gc; + gdk_pixmap = gdk_pixmap_create_from_xpm_d( window->window, &mask, + &style->bg[GTK_STATE_NORMAL], + WheelbarrowFull_xpm ); + pixmap = gtk_pixmap_new( gdk_pixmap, mask ); + gtk_widget_show( pixmap ); + + /* Per mostrare la pixmap, usiamo un widget "fixed" in cui metterla */ + fixed = gtk_fixed_new(); + gtk_widget_set_usize( fixed, 200, 200 ); + gtk_fixed_put( GTK_FIXED(fixed), pixmap, 0, 0 ); + gtk_container_add( GTK_CONTAINER(window), fixed ); + gtk_widget_show( fixed ); + + /* Questa maschera tutto tranne l'immagine stessa */ + gtk_widget_shape_combine_mask( window, mask, 0, 0 ); + + /* mostra la finestra */ + gtk_widget_set_uposition( window, 20, 400 ); + gtk_widget_show( window ); + gtk_main (); + + return 0; +} +</verb></tscreen> +<p> +Per rendere sensibile l'immagine della carriola, potremmo collegare +il segnale di pressione del bottone in modo che venga compiuta una certa +azione. Le prossime linee renderebbero l'immagine sensibile alla pressione +di un bottone del mouse che fa sì che l'applicazione termini. + +<tscreen><verb> +gtk_widget_set_events( window, + gtk_widget_get_events( window ) | + GDK_BUTTON_PRESS_MASK ); + +gtk_signal_connect( GTK_OBJECT(window), "button_press_event", + GTK_SIGNAL_FUNC(close_application), NULL ); +</verb></tscreen> + + +<sect> Widget Contenitore + +<sect1> Il widget Blocco Note (Notebook) +<p> +Il widget Blocco note è un insieme di pagine sovrapposte l'una con l'altra, +ognuna contente cose diverse. Questo widget è diventato molto comune nella +programmazione delle interfacce utente ed è un buon metodo per mostrare informazioni +tra loro correlate ma che debbano essere mostrate separatamente. + +<p> +La prima funzione da invocare che si deve conoscere, come si può intuire, è usata +per creare un nuovo Blocco Note. + +<tscreen><verb> +GtkWidget* gtk_notebook_new (void); +</verb></tscreen> + +Una volta che il notebook è sato creato, ci sono 12 funzioni che possono +operare sul widget notebook. Guardiamole individualmente. + +La prima che vediamo riguarda come posizionare l'indicatore di pagina. +Questi inidicatori di pagina o ``linguette'' (come possono anche essere chiamati) +possono essere posizionati in quattro posti: alto, basso, sinistra.destra. + +<tscreen><verb> +void gtk_notebook_set_tab_pos (GtkNotebook *notebook, GtkPositionType pos); +</verb></tscreen> + +GtkPositionType sarà uno dei seguenti valori (molto autoesplicativi) +<itemize> +<item> GTK_POS_LEFT +<item> GTK_POS_RIGHT +<item> GTK_POS_TOP +<item> GTK_POS_BOTTOM +</itemize> + +GTK_POS_TOP e' il valore predefinito. + +Ora vediamo come aggiugere le pagine al Blocco Note. Ci sono 3 modi per farlo. Diamo +un'occhiata ai primi due insieme, viste che sono molto simili. + +<tscreen><verb> +void gtk_notebook_append_page (GtkNotebook *notebook, GtkWidget *child, GtkWidget *tab_label); + +void gtk_notebook_prepend_page (GtkNotebook *notebook, GtkWidget *child, GtkWidget *tab_label); +</verb></tscreen> + +Queste funzioni aggiungono pagine al notebook inserendole rispettivamente alla fine +(append) o all'inizio (prepend). *child è il widget che è posto nella pagina del +notebook e *tab_label e la intestazione della pagina stessa. + +L'ultima funzione per aggiungere una pagina al notebook contiene tutte le proprietà +delle precedenti due, ma permette di specificare dove posizionare la pagina che +si vuole inserire. + +<tscreen><verb> +void gtk_notebook_insert_page (GtkNotebook *notebook, GtkWidget *child, GtkWidget *tab_label, gint position); +</verb></tscreen> + +I parametri sono gli stessi di _append_ e _prepend_ tranne che per il parametro in +più: ``position''. +Questo parametro viene usato per specificare in che posizione ineserire la pagina. + +Ora che conosciamo come aggiungere le pagine, vediamo come poter toglierne una. + +<tscreen><verb> +void gtk_notebook_remove_page (GtkNotebook *notebook, gint page_num); +</verb></tscreen> + +Questa funzione prende il numero della pagina specificata dal campo page_num e +rimuove la pagina corrispondente dal Blocco Note. + +Per trovare qual'è la pagina corrente nel notebook bisogna usare la funzione: + +<tscreen><verb> +gint gtk_notebook_current_page (GtkNotebook *notebook); +</verb></tscreen> + +Le prossime due funzioni sono semplicemente delle chiamate che muovono la pagina del +notebook avanti o indietro. Semplicemente forniscono le chiamate alle rispettive +funzioni del widget notebook su si può operare. NB: quando un notebook è +correntemente sull'ultima pagina e viene invocata la funzione gtk_notebook_next_page, +il notebook ritornerà automaticamente alla prima pagina. Logicamente succede anche +il contrario quando invochi gtk_notebook_prev_page e ti trovi sulla prima pagina. + +<tscreen><verb> +void gtk_notebook_next_page (GtkNoteBook *notebook); +void gtk_notebook_prev_page (GtkNoteBook *notebook); +</verb></tscreen> + +La prossima funzione stabilisce la pagina ``attiva''. Se si vuole che la pagina +principale del notebook sia per esempio la 5 (ad esempio) si può usare questa +funzione. +Se non si usa questa funzione la pagina principale sarà la 1. + +<tscreen><verb> +void gtk_notebook_set_page (GtkNotebook *notebook, gint page_num); +</verb></tscreen> + +Le prossime due funzioni aggiungono o rimuovono, rispettivamente, le intestazioni e +i bordi delle pagine. + +<tscreen><verb> +void gtk_notebook_set_show_tabs (GtkNotebook *notebook, gint show_tabs); +void gtk_notebook_set_show_border (GtkNotebook *notebook, gint show_border); +</verb></tscreen> + +show_tabs e show_border posso avere come valore TRUE o FALSE (0 or 1). + +Diamo ora una occhiata ad un esempio. Si tratta di una espansione del codice preso +dal file testgtk.c che è compreso in tutte le distribuzioni, e mostra +tutte le 13 funzioni. Questo piccolo programma crea una finestra con un notebook +e 6 bottoni. Il notebook contiene 11 pagine, aggiunte nei 3 modi differenti (alla +fine, all'inizio o in qualsiasi posizione). I bottoni permettono di girare le +intestazioni, aggiungere/rimuovere le intestazioni e i bordi, rimuovere una +pagina, cambiare la pagina avanti e indietro e uscire dal programma. + +<tscreen><verb> + +#include <gtk/gtk.h> + +/* Queta funzione ruota le posizione delle linguette delle pagine */ +void rotate_book (GtkButton *button, GtkNotebook *notebook) +{ + gtk_notebook_set_tab_pos (notebook, (notebook->tab_pos +1) %4); +} + +/* Aggiunge e rimuove le linguette e i bordi */ +void tabsborder_book (GtkButton *button, GtkNotebook *notebook) +{ + gint tval = FALSE; + gint bval = FALSE; + if (notebook->show_tabs == 0) + tval = TRUE; + if (notebook->show_border == 0) + bval = TRUE; + + gtk_notebook_set_show_tabs (notebook, tval); + gtk_notebook_set_show_border (notebook, bval); +} + +/* Rimuove una pagina */ +void remove_book (GtkButton *button, GtkNotebook *notebook) +{ + gint page; + + page = gtk_notebook_current_page(notebook); + gtk_notebook_remove_page (notebook, page); + /* E' necessario fare un refresh del widget -- + Questo forza il widget a ridisegnarsi. */ + gtk_widget_draw(GTK_WIDGET(notebook), NULL); +} + +void delete (GtkWidget *widget, gpointer *data) +{ + gtk_main_quit (); +} + +int main (int argc, char *argv[]) +{ + GtkWidget *window; + GtkWidget *button; + GtkWidget *table; + GtkWidget *notebook; + GtkWidget *frame; + GtkWidget *label; + GtkWidget *checkbutton; + int i; + char bufferf[32]; + char bufferl[32]; + + gtk_init (&argc, &argv); + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + + gtk_signal_connect (GTK_OBJECT (window), "destroy", + GTK_SIGNAL_FUNC (destroy), NULL); + + gtk_container_border_width (GTK_CONTAINER (window), 10); + + table = gtk_table_new(2,6,TRUE); + gtk_container_add (GTK_CONTAINER (window), table); + + /* Crea un nuovo notebook, e tabilisce la posizione delle linguette */ + notebook = gtk_notebook_new (); + gtk_notebook_set_tab_pos (GTK_NOTEBOOK (notebook), GTK_POS_TOP); + gtk_table_attach_defaults(GTK_TABLE(table), notebook, 0,6,0,1); + gtk_widget_show(notebook); + + /* appende una parte delle pagine */ + for (i=0; i < 5; i++) { + sprintf(bufferf, "Append Frame %d", i+1); + sprintf(bufferl, "Page %d", i+1); + + frame = gtk_frame_new (bufferf); + gtk_container_border_width (GTK_CONTAINER (frame), 10); + gtk_widget_set_usize (frame, 100, 75); + gtk_widget_show (frame); + + label = gtk_label_new (bufferf); + gtk_container_add (GTK_CONTAINER (frame), label); + gtk_widget_show (label); + + label = gtk_label_new (bufferl); + gtk_notebook_append_page (GTK_NOTEBOOK (notebook), frame, label); + } + + + /* Ora aggiungiamo una pagina in una certa posizione */ + checkbutton = gtk_check_button_new_with_label ("Check me please!"); + gtk_widget_set_usize(checkbutton, 100, 75); + gtk_widget_show (checkbutton); + + label = gtk_label_new ("Add spot"); + gtk_container_add (GTK_CONTAINER (checkbutton), label); + gtk_widget_show (label); + label = gtk_label_new ("Add page"); + gtk_notebook_insert_page (GTK_NOTEBOOK (notebook), checkbutton, label, 2); + + /* Ora finalmente aggiungiamo le pagine all'inizio */ + for (i=0; i < 5; i++) { + sprintf(bufferf, "Prepend Frame %d", i+1); + sprintf(bufferl, "PPage %d", i+1); + + frame = gtk_frame_new (bufferf); + gtk_container_border_width (GTK_CONTAINER (frame), 10); + gtk_widget_set_usize (frame, 100, 75); + gtk_widget_show (frame); + + label = gtk_label_new (bufferf); + gtk_container_add (GTK_CONTAINER (frame), label); + gtk_widget_show (label); + + label = gtk_label_new (bufferl); + gtk_notebook_prepend_page (GTK_NOTEBOOK(notebook), frame, label); + } + + /* Stabilisce quale sarà la prima pagina che sarà visualizzata. */ + gtk_notebook_set_page (GTK_NOTEBOOK(notebook), 3); + + + /* Crea un set di bottoni */ + button = gtk_button_new_with_label ("close"); + gtk_signal_connect_object (GTK_OBJECT (button), "clicked", + GTK_SIGNAL_FUNC (destroy), NULL); + gtk_table_attach_defaults(GTK_TABLE(table), button, 0,1,1,2); + gtk_widget_show(button); + + button = gtk_button_new_with_label ("next page"); + gtk_signal_connect_object (GTK_OBJECT (button), "clicked", + (GtkSignalFunc) gtk_notebook_next_page, + GTK_OBJECT (notebook)); + gtk_table_attach_defaults(GTK_TABLE(table), button, 1,2,1,2); + gtk_widget_show(button); + + button = gtk_button_new_with_label ("prev page"); + gtk_signal_connect_object (GTK_OBJECT (button), "clicked", + (GtkSignalFunc) gtk_notebook_prev_page, + GTK_OBJECT (notebook)); + gtk_table_attach_defaults(GTK_TABLE(table), button, 2,3,1,2); + gtk_widget_show(button); + + button = gtk_button_new_with_label ("tab position"); + gtk_signal_connect_object (GTK_OBJECT (button), "clicked", + (GtkSignalFunc) rotate_book, GTK_OBJECT(notebook)); + gtk_table_attach_defaults(GTK_TABLE(table), button, 3,4,1,2); + gtk_widget_show(button); + + button = gtk_button_new_with_label ("tabs/border on/off"); + gtk_signal_connect_object (GTK_OBJECT (button), "clicked", + (GtkSignalFunc) tabsborder_book, + GTK_OBJECT (notebook)); + gtk_table_attach_defaults(GTK_TABLE(table), button, 4,5,1,2); + gtk_widget_show(button); + + button = gtk_button_new_with_label ("remove page"); + gtk_signal_connect_object (GTK_OBJECT (button), "clicked", + (GtkSignalFunc) remove_book, + GTK_OBJECT(notebook)); + gtk_table_attach_defaults(GTK_TABLE(table), button, 5,6,1,2); + gtk_widget_show(button); + + gtk_widget_show(table); + gtk_widget_show(window); + + gtk_main (); + + return 0; +} +</verb></tscreen> +<p> +E speriamo che questo vi aiuti a creare i Blocco Note per le vostre applicazioni GTK! + +<sect1> Finestre Scorribili (Scrolled Windows) +<p> +Le Finestre Scorribili sono usate per creare areee scorribili in una vera finestra. +Si può inserire qualsiasi tipo di widget in questo tipo di finestra, e possono poi +essere accessibili a prescindere dalle dimensioni usando le barre di scorrimento. + +La funzione seguente è usata per creare una nuova scrolled window. + +<tscreen><verb> +GtkWidget* gtk_scrolled_window_new (GtkAdjustment *hadjustment, + GtkAdjustment *vadjustment); +</verb></tscreen> +<p> +Il primo argomento è l'aggiustamento (di quanto scendere ogni +volta) orizzontale e il secondo è quello verticale. A questi si assegna +quasi sempre il valore NULL. + +<tscreen><verb> +void gtk_scrolled_window_set_policy (GtkScrolledWindow *scrolled_window, + GtkPolicyType hscrollbar_policy, + GtkPolicyType vscrollbar_policy); +</verb></tscreen> + +Questa funzione stabilisce la politica da usare nella barra di scorrimento. Il primo +argomento è la finestra scorribile interessata. Il secondo stabilisce la politica +per la barra di scorrimento orizzontale e il terzo è quello per la politca verticale. + +La politica può essere GTK_POLICY AUTOMATIC o GTK_POLICY_ALWAYS. +GTK_POLICY_AUTOMATIC decide automaticamente se la barra di scorrimento deve essere +visualizzata, mentre con GTK_POLICY_ALWAYS la barra verrà sempre mostrata. + +<tscreen><verb> +#include <gtk/gtk.h> + +void destroy(GtkWidget *widget, gpointer *data) +{ + gtk_main_quit(); +} + +int main (int argc, char *argv[]) +{ + static GtkWidget *window; + GtkWidget *scrolled_window; + GtkWidget *table; + GtkWidget *button; + char buffer[32]; + int i, j; + + gtk_init (&argc, &argv); + + /* Crea una nuove finestra di dialogo in cui la scrolled window sarà + inserita. Una finestra di dialogo è semplicemente come una + finestra normale, ma ha anche un vbox e un separatore orizzontale + già inseriti per difetto. E'un modo semplice per + creare finestre di dialogo. */ + window = gtk_dialog_new (); + gtk_signal_connect (GTK_OBJECT (window), "destroy", + (GtkSignalFunc) destroy, NULL); + gtk_window_set_title (GTK_WINDOW (window), "dialog"); + gtk_container_border_width (GTK_CONTAINER (window), 0); + + /* crea una nuova finestra scorribile. */ + scrolled_window = gtk_scrolled_window_new (NULL, NULL); + + gtk_container_border_width (GTK_CONTAINER (scrolled_window), 10); + + /* la politica è GTK_POLICY AUTOMATIC per lo scorrimento orizzontale e + GTK_POLICY_ALWAYS per quello verticale. */ + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window), + GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS); + + /* La finestra di dialogo è creata con un vbox già inserito.*/ + gtk_box_pack_start (GTK_BOX (GTK_DIALOG(window)->vbox), scrolled_window, + TRUE, TRUE, 0); + gtk_widget_show (scrolled_window); + + /* crea una tablella di10 x 10. */ + table = gtk_table_new (10, 10, FALSE); + + /* setta lo spazio tra ogni cella di 10 pixel sia verticale sia orizzontale*/ + gtk_table_set_row_spacings (GTK_TABLE (table), 10); + gtk_table_set_col_spacings (GTK_TABLE (table), 10); + + /* inserisce la tabella nella finestra scorribile*/ + gtk_container_add (GTK_CONTAINER (scrolled_window), table); + gtk_widget_show (table); + + /* questo semplicemente crea una griglia di bottoni nella tabelle per + dimostrare il comportamento della finestra scorribile */ + for (i = 0; i < 10; i++) + for (j = 0; j < 10; j++) { + sprintf (buffer, "button (%d,%d)\n", i, j); + button = gtk_toggle_button_new_with_label (buffer); + gtk_table_attach_defaults (GTK_TABLE (table), button, + i, i+1, j, j+1); + gtk_widget_show (button); + } + + /* Aggiunge un bottone "close" alla fine della finestra */ + button = gtk_button_new_with_label ("close"); + gtk_signal_connect_object (GTK_OBJECT (button), "clicked", + (GtkSignalFunc) gtk_widget_destroy, + GTK_OBJECT (window)); + + /* questo fa sì che questo bottone sia quello predefinito */ + + GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT); + gtk_box_pack_start (GTK_BOX (GTK_DIALOG (window)->action_area), button, TRUE, TRUE, 0); + + /* Questo ottiene il bottone predefinito. Premendo semplicemente l'"enter" il + bottone si avvierà */ + gtk_widget_grab_default (button); + gtk_widget_show (button); + + gtk_widget_show (window); + + gtk_main(); + + return(0); +} +</verb></tscreen> +<p> +Prova a giocare con il ridemensionamento della finestra. Noterete la reazione della +barra di scorrimento. Potete anche usare la funzione gtk_widget_set_usize() per +assegnare la dimensione predefinita della finestra o di un widget. +<!-- (ndMichel: questa chiamata non funziona per i bottoni!) --> + + +<sect> Il Widgets Lista +<p> +Il widget GtkList serve come contenitore verticale per altri widget che +devono essere di tipo GtkListItem. + +Un widget GtkList possiede una sua propria finestra per ricevere eventi +e un suo proprio colore di sfondo che di solito è bianco. Dal momento +che è direttamente derivato dal widget GtkContainer, può essere trattato +come tale usando la macro GTK_CONTAINER(List); si veda il widget GtkContainer +per ulteriori dettagli. +Per usare il widget GtkList in tutte le sue potenzialità, si dovrebbe essere +già familiari con l'uso della GList e delle relative funzioni g_list_*(). + +All'interno della definizione della struttura del widget GtkList c'è un +campo che sarà per noi di grande interesse, cioè: + +<tscreen><verb> +struct _GtkList +{ + ... + GList *selection; + guint selection_mode; + ... +}; +</verb></tscreen> + +Il campo ``selection'' in un GtkList punta a una lista collegata di tutti +gli elementi che sono selezionati correntemente, oppure a NULL se la +selezione è vuota. Quindi, per avere informazioni sulla selezione corrente, +leggiamo il campo GTK_LIST()->selection, senza però modificarlo dal momento +che i campi interni debbono essere gestiti dalle funzioni gtk_list_*(). + +Le modalità di selezione in una GtkList, e quindi il contenuto di +GTK_LIST()->selection, sono determinate dal campo selection_mode: + +selection_mode può assumere uno dei seguenti valori: +<itemize> +<item> GTK_SELECTION_SINGLE - La selezione può essere o NULL oppure + un puntatore GList* per un singolo elemento + selezionato. + +<item> GTK_SELECTION_BROWSE - La selezione è null se la lista non contiene + alcun widget o se ha solo widget non sensibili, + oppure può contenere un puntatore a una struttura + GList, e quindi esattamente un elemento di lista. + +<item> GTK_SELECTION_MULTIPLE - La selezione è ``NULL'' se non è selezionato + alcun elemento di lista, oppure un puntatore GList al + primo elemento selezionato. Quello, a sua volta, punta + a una struttura GList per il secondo elemento selezionato + e così via. + +<item> GTK_SELECTION_EXTENDED - La selezione è sempre NULL. +</itemize> +<p> +Il valore per difetto è GTK_SELECTION_MULTIPLE. + +<sect1> Segnali +<p> +<tscreen><verb> +void GtkList::selection_changed (GtkList *LIST) +</verb></tscreen> + +Questo segnale verrà invocato ogni volta che il campo di +selezione di una GtkList è cambiato. Questo accade quando +un figlio della GtkList viene selezionato o deselezionato. + +<tscreen><verb> +void GtkList::select_child (GtkList *LIST, GtkWidget *CHILD) +</verb></tscreen> + +Questo segnale viene invocato quando un fuglio di una GtkList +sta per essere selezionato. Questo accade principalmente in +occasione di chiamate a gtk_list_select_item() e gtk_list_select_child(), +di pressioni di bottoni e a volte può venir fatto scattare indirettamente +in altre occasioni, in cui vengono aggiunti o rimossi dei figli +dalla GtkList. + +<tscreen><verb> +void GtkList::unselect_child (GtkList *LIST, GtkWidget *CHILD) +</verb></tscreen> + +Questo segnale viene invocato quando un figlio della GtkList sta +per essere deselezionato. Ciò accade principalmente in occasione +di chiamate a gtk_list_unselect_item() e gtk_list_unselect_child(), +di pressioni di bottoni, e a volte può venir fatto scattare indirettamente +in altre occasioni, in cui vengono aggiunti o rimossi dei figli +dalla GtkList. + +<sect1> Funzioni +<p> +<tscreen><verb> +guint gtk_list_get_type (void) +</verb></tscreen> + +Restituisce l'identificatore di tipo `GtkList'. + +<tscreen><verb> +GtkWidget* gtk_list_new (void) +</verb></tscreen> + +Crea un nuovo oggetto `GtkList'. Il nuovo widget viene +restituito sotto forma di un puntoatore ad un oggetto +`GtkWidgetì'. In caso di fallimento, viene ritornato NULL. + +<tscreen><verb> +void gtk_list_insert_items (GtkList *LIST, GList *ITEMS, gint POSITION) +</verb></tscreen> + +Inserisce degli elementi di lista nella LIST, a partire da +POSITION. ITEMS ITEMS è una lista doppiamente collegata, in +cui ci si aspetta che i puntatori di ogni nodo puntino a +un GtkListItem appena creato. I nodi GList di ITEMS vengono +assunti dalla LIST. + +<tscreen><verb> +void gtk_list_append_items (GtkList *LIST, GList *ITEMS) +</verb></tscreen> + +Inserisce elementi di lista proprio come gtk_list_insert_items(), +ma alla fine della LIST. I nodi GList di ITEMS vengono +assunti dalla LIST. + +<tscreen><verb> +void gtk_list_prepend_items (GtkList *LIST, GList *ITEMS) +</verb></tscreen> + +Inserisce elementi di lista proprio come gtk_list_insert_items(), +ma al principio della LIST. I nodi GList di ITEMS vengono +assunti dalla LIST. + +<tscreen><verb> +void gtk_list_remove_items (GtkList *LIST, GList *ITEMS) +</verb></tscreen> + +Rimuove degli elementi di lista dalla LIST. ITEMS è una lista +doppiamente collegata in cui ci si aspetta che i puntatori di +ogni nodo puntino a un figlio diretto di LIST. E' poi responsabilità +del chiamante di fare una chiamata a g_list_free(ITEMS). E' anche +necessario che il chiamante distrugga lui stesso gli elementi della +lista. + +<tscreen><verb> +void gtk_list_clear_items (GtkList *LIST, gint START, gint END) +</verb></tscreen> + +Rimuove e distrugge elementi di lista da LIST. Un widget ne è +interessato se la sua posizione corrente all'interno di LIST è compreso +fra START ed END. + +<tscreen><verb> +void gtk_list_select_item (GtkList *LIST, gint ITEM) +</verb></tscreen> + +Invoca il segnale GtkList::select_child per un elemento di lista +specificato dalla sua posizione corrente all'interno di LIST. + +<tscreen><verb> +void gtk_list_unselect_item (GtkList *LIST, gint ITEM) +</verb></tscreen> + +Invoca il segnale GtkList::unselect_child per un elemento di lista +specificato dalla sua posizione corrente all'interno di LIST. + +<tscreen><verb> +void gtk_list_select_child (GtkList *LIST, GtkWidget *CHILD) +</verb></tscreen> + +Invoca il segnale GtkList::select_child per uno specifico CHILD. + +<tscreen><verb> +void gtk_list_unselect_child (GtkList *LIST, GtkWidget *CHILD) +</verb></tscreen> + +Invoca il segnale GtkList::unselect_child per uno specifico CHILD. + +<tscreen><verb> +gint gtk_list_child_position (GtkList *LIST, GtkWidget *CHILD) +</verb></tscreen> + +Restituisce la posizione di CHILD all'interno di LIST. In caso di fallimento, +viene restituito `-1'. + +<tscreen><verb> +void gtk_list_set_selection_mode (GtkList *LIST, GtkSelectionMode MODE) +</verb></tscreen> + +Assegna a LIST il modo di selezione MODE, che può essere uno fra +GTK_SELECTION_SINGLE, GTK_SELECTION_BROWSE, GTK_SELECTION_MULTIPLE o +GTK_SELECTION_EXTENDED. + +<tscreen><verb> +GtkList* GTK_LIST (gpointer OBJ) +</verb></tscreen> + +Fa il cast di un generico puntatore a `GtkList*'. Per maggiori +informazioni vedere Standard Macros::. + +<tscreen><verb> +GtkListClass* GTK_LIST_CLASS (gpointer CLASS) +</verb></tscreen> + +Fa il cast di un generico puntatore a `GtkListClass*'. Per maggiori +informazioni vedere Standard Macros::. + +<tscreen><verb> +gint GTK_IS_LIST (gpointer OBJ) +</verb></tscreen> + +Determina se un generico puntatore si riferisce ad un oggetto `GtkList'. +Per maggiori informazioni vedere Standard Macros::. + + +<sect1> Esempio +<p> +Diamo di seguito un programma di esempio che stamperà i campbiamenti +della selezione di una GtkList, e vi lascia ``imprigionare'' gli elementi +di una lista selezionandoli con il pulsante destro del mouse: + +<tscreen><verb> +/* compilate questo programma con: + * $ gcc -I/usr/local/include/ -lgtk -lgdk -lglib -lX11 -lm -Wall main.c + */ + +/* includiamo i file header di gtk+ + * includiamo stdio.h, ne abbiamo bisogno per printf() + */ +#include <gtk/gtk.h> +#include <stdio.h> + +/* Questa e' la nostra stringa di identificazione dei dati per assegnarli + * ad elementi di lista + */ +const gchar *list_item_data_key="list_item_data"; + + +/* prototipi per i gestori di segnale che connetteremo + * al widget GtkList + */ +static void sigh_print_selection (GtkWidget *gtklist, + gpointer func_data); +static void sigh_button_event (GtkWidget *gtklist, + GdkEventButton *event, + GtkWidget *frame); + + +/* funzione main per predisporre l'interfaccia utente */ + +gint main (int argc, gchar *argv[]) +{ + GtkWidget *separator; + GtkWidget *window; + GtkWidget *vbox; + GtkWidget *scrolled_window; + GtkWidget *frame; + GtkWidget *gtklist; + GtkWidget *button; + GtkWidget *list_item; + GList *dlist; + guint i; + gchar buffer[64]; + + + /* inizializza gtk+ (e di conseguenza gdk) */ + + gtk_init(&argc, &argv); + + + /* crea una finestra in cui mettere tutti i widget + * connette gtk_main_quit() al segnale "destroy" della finestra + * per gestire le richieste di chiusura finestra del window manager + */ + window=gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_window_set_title(GTK_WINDOW(window), "GtkList Example"); + gtk_signal_connect(GTK_OBJECT(window), + "destroy", + GTK_SIGNAL_FUNC(gtk_main_quit), + NULL); + + + /* all'interno della finestra abbiamo bisogno di una scatola + * in cui mettere i widget verticalmente */ + vbox=gtk_vbox_new(FALSE, 5); + gtk_container_border_width(GTK_CONTAINER(vbox), 5); + gtk_container_add(GTK_CONTAINER(window), vbox); + gtk_widget_show(vbox); + + /* questa è la finestra scorribile in cui mettere il widget GtkList */ + scrolled_window=gtk_scrolled_window_new(NULL, NULL); + gtk_widget_set_usize(scrolled_window, 250, 150); + gtk_container_add(GTK_CONTAINER(vbox), scrolled_window); + gtk_widget_show(scrolled_window); + + /* crea il widget GtkList + * connette il gestore di segnale sigh_print_selection() + * al segnale "selection_changed" della GtkList, per stampare + * gli elementi selezionati ogni volta che la selezione cambia + */ + gtklist=gtk_list_new(); + gtk_container_add(GTK_CONTAINER(scrolled_window), gtklist); + gtk_widget_show(gtklist); + gtk_signal_connect(GTK_OBJECT(gtklist), + "selection_changed", + GTK_SIGNAL_FUNC(sigh_print_selection), + NULL); + + /* creiamo una "Prigione" (Prison) in cui mettere gli elementi di lista ;) + */ + frame=gtk_frame_new("Prison"); + gtk_widget_set_usize(frame, 200, 50); + gtk_container_border_width(GTK_CONTAINER(frame), 5); + gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_OUT); + gtk_container_add(GTK_CONTAINER(vbox), frame); + gtk_widget_show(frame); + + /* connette il gestore di segnale sigh_button_event() alla GtkList + * il quale gestira' l'"imprigionamento" degli elementi di lista + */ + gtk_signal_connect(GTK_OBJECT(gtklist), + "button_release_event", + GTK_SIGNAL_FUNC(sigh_button_event), + frame); + + /* crea un separatore + */ + separator=gtk_hseparator_new(); + gtk_container_add(GTK_CONTAINER(vbox), separator); + gtk_widget_show(separator); + + /* infine creiamo un bottone e connettiamone il segnale "clicked" + * alla distruzione della finestra + */ + button=gtk_button_new_with_label("Close"); + gtk_container_add(GTK_CONTAINER(vbox), button); + gtk_widget_show(button); + gtk_signal_connect_object(GTK_OBJECT(button), + "clicked", + GTK_SIGNAL_FUNC(gtk_widget_destroy), + GTK_OBJECT(window)); + + + /* a questo punto creiamo 5 elementi di lista, ognuno con la + * propria etichetta, e li aggiungiamo alla GtkList usando + * gtk_container_add(). Inoltre, recuperiamo la stringa di testo + * dall'etichetta e la associamo, per ogni elemento, a + * list_item_data_key + */ + for (i=0; i<5; i++) { + GtkWidget *label; + gchar *string; + + sprintf(buffer, "ListItemContainer with Label #%d", i); + label=gtk_label_new(buffer); + list_item=gtk_list_item_new(); + gtk_container_add(GTK_CONTAINER(list_item), label); + gtk_widget_show(label); + gtk_container_add(GTK_CONTAINER(gtklist), list_item); + gtk_widget_show(list_item); + gtk_label_get(GTK_LABEL(label), &string); + gtk_object_set_data(GTK_OBJECT(list_item), + list_item_data_key, + string); + } + + /* qui creiamo altre 5 etichette, questa volta usando + * per la creazione gtk_list_item_new_with_label(). + * Non possiamo recuperare la stringa di testo dall'etichetta + * dal momento che non disponiamo di puntatori alle etichette, + * quindi associamo semplicemente il list_item_data_key di ogni + * elemento di lista con la medesima stringa di testo. + * Per aggiungere elementi di lista, li mettiamo tutti in una lista + * doppiamente collegata (GList), e quindi li aggiungiamo con una + * unica chiamata a gtk_list_append_items(). + * Dal momento che usiamo g_list_prepend() per mettere gli elementi + * nella lista doppiamente collegata, il loro ordine sara' discendente + * (invece che ascendente come sarebbe se usassimo g_list_append()) + */ + dlist=NULL; + for (; i<10; i++) { + sprintf(buffer, "List Item with Label %d", i); + list_item=gtk_list_item_new_with_label(buffer); + dlist=g_list_prepend(dlist, list_item); + gtk_widget_show(list_item); + gtk_object_set_data(GTK_OBJECT(list_item), + list_item_data_key, + "ListItem with integrated Label"); + } + gtk_list_append_items(GTK_LIST(gtklist), dlist); + + /* e finalmente vogliamo vedere la finestra, non e' vero? ;) + */ + gtk_widget_show(window); + + /* lancia il ciclo principale di gtk + */ + gtk_main(); + + /* si arriva a questo punto dopo la chiamata di gtk_main_quit(), + * il che accade quando viene distrutta la finestra principale + */ + return 0; +} + +/* questo e' il gestore di segnale che e' stato connesso all'evento di + * pressione/rilascio del bottone della GtkList + */ +void +sigh_button_event (GtkWidget *gtklist, + GdkEventButton *event, + GtkWidget *frame) +{ + /* facciamo qualcosa solo nel caso di rilascio del terzo bottone + * (quello piu' a destra) + */ + if (event->type==GDK_BUTTON_RELEASE && + event->button==3) { + GList *dlist, *free_list; + GtkWidget *new_prisoner; + + /* recuperiamo l'elemento di lista selezionato correntemente, + * che sara' il nostro prossimo prigioniero ;) + */ + dlist=GTK_LIST(gtklist)->selection; + if (dlist) + new_prisoner=GTK_WIDGET(dlist->data); + else + new_prisoner=NULL; + + /* cerchiamo elementi di lista gia' imprigionati, + * li rimetteremo nella lista. + * Ricordare di liberare la lista doppiamente collegata + * che viene restituita da gtk_container_children() + */ + dlist=gtk_container_children(GTK_CONTAINER(frame)); + free_list=dlist; + while (dlist) { + GtkWidget *list_item; + + list_item=dlist->data; + + gtk_widget_reparent(list_item, gtklist); + + dlist=dlist->next; + } + g_list_free(free_list); + + /* se abbiamo un nuovo prigioniero, lo rimuoviamo + * dalla GtkList e lo mettiamo nella cornice della + * "Prigione". Dobbiamo prima deselezionare l'elemento + */ + if (new_prisoner) { + GList static_dlist; + + static_dlist.data=new_prisoner; + static_dlist.next=NULL; + static_dlist.prev=NULL; + + gtk_list_unselect_child(GTK_LIST(gtklist), + new_prisoner); + gtk_widget_reparent(new_prisoner, frame); + } + } +} + +/* questo e' il gestore di segnaleche viene chiamato de la + * GtkList emette il segnale "selection_changed" + */ +void +sigh_print_selection (GtkWidget *gtklist, + gpointer func_data) +{ + GList *dlist; + + /* recuperiamo la lista doppiamente collegata degli + * elementi selezionati della GtkList, ricordate di + * trattarla come sola lettura + */ + dlist=GTK_LIST(gtklist)->selection; + + /* se non ci sono elementi selezionati non c'e' altro da + * fare che dirlo all'utente + */ + if (!dlist) { + g_print("Selection cleared\n"); + return; + } + /* ok, abbiamo una selezione e quindi lo scriviamo + */ + g_print("The selection is a "); + + /* ottieniamo l'elemento di lista dalla lista doppiamente + * collegata e poi richiediamo i dati associati con + * list_item_data_key. Poi semplicemente li stampiamo + */ + while (dlist) { + GtkObject *list_item; + gchar *item_data_string; + + list_item=GTK_OBJECT(dlist->data); + item_data_string=gtk_object_get_data(list_item, + list_item_data_key); + g_print("%s ", item_data_string); + + dlist=dlist->next; + } + g_print("\n"); +} +</verb></tscreen> + +<sect1> Il Widget Elemento di Lista (List Item) +<p> +Il widget GtkListItem è progettato allo scopo di essere un contenitore +collegato ad un figlio, per fornire le funzioni per la selezione e deselezione +allo stesso modo in cui il widget GtkList ne ha bisogno per i propri figli. + +Un GtkListItem ha la sua propria finestra per ricevere eventi, e ha il suo +proprio colore di sfondo, che di solito è bianco. + +Dal momento che questo widget deriva direttamente da GtkItem, può essere +trattato come tale usando la macro GTK_ITEM(ListItem), vedere il widget +GtkItem per ulteriori informazioni. +Di solito un GtkListItem ha solo un'etichetta per identificare per esempio +un nome di file all'interno di una GtkList -- per cui viene fornita la +funzione appropriata gtk_list_item_new_with_label(). Si può ottenere lo +stesso effetto creando una GtkLabel da sola, assegnando al suo allineamento +i valori xalign=0 e yalign=0.5, aggiungendo successivamente un contenitore +alla GtkListItem. + +Dal momento che non si è obbligati a mettere una GtkLabel, si può anche +aggiungere una GtkVBox una GtkArrow ecc. alla GtkListItem. + +<sect1> Segnali +<p> +Un GtkListItem non crea alcun nuovo segnale di per se, ma eredita +i segnali di GtkItem. Per ulteriori informazioni, vedere GtkItem::. + + +<sect1> Funzioni +<p> + +<tscreen><verb> +guint gtk_list_item_get_type (void) +</verb></tscreen> + +Restituisce l'identificatore di tipo `GtkListItem'. + +<tscreen><verb> +GtkWidget* gtk_list_item_new (void) +</verb></tscreen> + +Crea un nuovo oggetto `GtkListItem'. Il nuovo widget viene restituito +sottoforma di un puntatore ad un oggetto `GtkWidget'. In caso di +fallimento, viene restituito `NULL'. + +<tscreen><verb> +GtkWidget* gtk_list_item_new_with_label (gchar *LABEL) +</verb></tscreen> + +Cre un nuovo oggetto `GtkListItem', avente come unico figlio +un GtkLabel. Il nuovo widget viene restituito +sottoforma di un puntatore ad un oggetto `GtkWidget'. In caso di +fallimento, viene restituito `NULL'. + +<tscreen><verb> +void gtk_list_item_select (GtkListItem *LIST_ITEM) +</verb></tscreen> + +Questa funzione è essenzialmente un wrapper per una chiamata a +gtk_item_select (GTK_ITEM (list_item)) che emetterà il segnale +GtkItem::select. +Vedere GtkItem:: per maggiori informazioni. + +<tscreen><verb> +void gtk_list_item_deselect (GtkListItem *LIST_ITEM) +</verb></tscreen> + +Questa funzione è essenzialmente un wrapper per una chiamata a +gtk_item_deselect (GTK_ITEM (list_item)) che emetterà il segnale +GtkItem::deselect. +Vedere GtkItem:: per maggiori informazioni. + +<tscreen><verb> +GtkListItem* GTK_LIST_ITEM (gpointer OBJ) +</verb></tscreen> + +Effettua il cast di un puntatore generico a `GtkListItem*'. Vedere +Standard Macros:: per maggiorni informazioni. + +<tscreen><verb> +GtkListItemClass* GTK_LIST_ITEM_CLASS (gpointer CLASS) +</verb></tscreen> + +Effettua il cast di un puntatore generico a `GtkListItemClass*'. Vedere +Standard Macros:: per maggiorni informazioni. + +<tscreen><verb> +gint GTK_IS_LIST_ITEM (gpointer OBJ) +</verb></tscreen> + +Determina se un puntatore generico si riferisce ad un oggetto +`GtkListItem'. Vedere Standard Macros:: per maggiorni informazioni. + +<sect1> Esempio +<p> +Come esempio su questo argomento, si veda quello relativo alla GtkList, +che riguarda anche l'uso del GtkListItem. + +<sect> Selezione di File (File Selections) +<p> + +Il widget Selezione di File è un modo rapido e semplice per mostrare una +finestra di dialogo `File'. Questa si presenta completa di bottoni Ok, +Cancel e Help, un buon modo per tagliare i tempi di programmazione. + +Per creare una nuova finestra di selezione file usate: + +<tscreen><verb> +GtkWidget* gtk_file_selection_new (gchar *title); +</verb></tscreen> + +Per assegnare il nome del file, ad esempio per predisporre una certa +directory o per dare un certo nome di file per difetto, usate la seguente +funzione: + +<tscreen><verb> +void gtk_file_selection_set_filename (GtkFileSelection *filesel, gchar *filename); +</verb></tscreen> + +Per recuperare il testo che l'utente ha inserito o che ha selezionato con +il mouse, si usa la funzione: + +<tscreen><verb> +gchar* gtk_file_selection_get_filename (GtkFileSelection *filesel); +</verb></tscreen> + +Ci sono anche dei puntatori ai widget che sono contenuti all'interno +del widget di selezione file. Si tratta di: + +<itemize> +<item>dir_list +<item>file_list +<item>selection_entry +<item>selection_text +<item>main_vbox +<item>ok_button +<item>cancel_button +<item>help_button +</itemize> + +Molto probabilmente potreste voler usare i puntatori a ok_button, +cancel_button e help_button per segnalarne l'uso. + +Ecco un esempio rubato da testgtk.c, nodificato per essere eseguito da +solo. Come potrete vedere, non c'è molto più che la creazione di un +widget di selezione file. In questo esempio, il bottone Help non fa nulla +mentre è mostrato allo schermo, dal momento che non c'è alcun segnale +collegato con esso. + +<tscreen><verb> +#include <gtk/gtk.h> + +/* Recupera il nome di file selezionato e stampalo a console */ +void file_ok_sel (GtkWidget *w, GtkFileSelection *fs) +{ + g_print ("%s\n", gtk_file_selection_get_filename (GTK_FILE_SELECTION (fs))); +} + +void destroy (GtkWidget *widget, gpointer *data) +{ + gtk_main_quit (); +} + +int main (int argc, char *argv[]) +{ + GtkWidget *filew; + + gtk_init (&argc, &argv); + + /* Crea un nuovo widget di selezione file */ + filew = gtk_file_selection_new ("File selection"); + + gtk_signal_connect (GTK_OBJECT (filew), "destroy", + (GtkSignalFunc) destroy, &filew); + /* Connette ok_button alla funzione file_ok_sel */ + gtk_signal_connect (GTK_OBJECT (GTK_FILE_SELECTION (filew)->ok_button), + "clicked", (GtkSignalFunc) file_ok_sel, filew ); + + /* Connette cancel_button alla funzione di distruzione del widget */ + gtk_signal_connect_object (GTK_OBJECT (GTK_FILE_SELECTION (filew)->cancel_button), + "clicked", (GtkSignalFunc) gtk_widget_destroy, + GTK_OBJECT (filew)); + + /* Preassegnamo un nome di file, come se stessimo dando un valore per difetto in + dialogo di tipo `` salva con nome '' */ + gtk_file_selection_set_filename (GTK_FILE_SELECTION(filew), + "penguin.png"); + + gtk_widget_show(filew); + gtk_main (); + return 0; +} +</verb></tscreen> + +<sect>Il Widget Menù (Menu Widgets) +<p> +Ci sono due modi per creare dei menù, quello facile e quello difficile. +Ognuno è più adatto per certe circostanze, ma di solito si può usare il +modo semplice, cioé menu_factory (la ``fabbrica dei menù''). Il modo +``difficile'' è di crearsi tutti i menù usando direttamente le chiamate. +Quello semplice è di usare le chiamate di tipo gtk_menu_factory. Anche se +è un modo molto più semplice, ci sono svantaggi e vantaggi per ciascuno +dei due approcci. + +La menufactory è molto più semplice da usare e per aggiungere dei nuovi +menù, anche se scriversi un po' di funzioni per creare dei menù con il +metodo manuale può dare risultati molto migliori dal punto di vista +dell'usabilità. Con la menufactory, non è possibile mettere immagini o +segni '/' nei menù. +<p> +<sect1>Creazione Manuale di Menù +<p> +Seguendo la tradizionale arte dell'insegnamento, partiamo dal modo +difficile. <tt>:)</> +<p> +Diamo un'occhiata alle funzioni usate per creare dei menù. +Con questa prima funzione si crea un nuovo menù: + +<tscreen><verb> +GtkWidget *gtk_menu_bar_new() +</verb></tscreen> + +Questa funzione crea una nuova barra di menù. Per impacchettarla in una +finestra o si usa la funzione gtk_container_add, oppure, per impacchettarla +in una scatola, le funzioni box_pack - come con i bottoni. + +<tscreen><verb> +GtkWidget *gtk_menu_new(); +</verb></tscreen> + +Questa funzione restituisce un puntatore ad un nuovo menù, non viene mai +realmente mostrato (con gtk_widget_show), serve solo per contenere gli +elementi del menù. Spero che il tutto risulti più chiaro quando dare +un'occhiata all'esempio più sotto. +<p> +Le prossime due chiamate sono usate per creare degli elementi che poi +vengono impacchettati nel menù. + +<tscreen><verb> +GtkWidget *gtk_menu_item_new() +</verb></tscreen> + +e + +<tscreen><verb> +GtkWidget *gtk_menu_item_new_with_label(const char *label) +</verb></tscreen> + +Queste chiamate sono usate per creare i menu che devono essere mostrati. +Ricordate la differenza che esiste fra un ``menù'' come quelli creati con +gtk_menu_new e un ``elemento di menù'' (menu item) come quelli creati con +la funzione creata con gtk_menu_item_new. L'elemento di menù sarà un bottone +vero e proprio con una azione associata, mentre un menù è solo un contenitore +che li raccoglie. + +<tscreen><verb> +gtk_menu_item_append() + +gtk_menu_item_set_submenu() +</verb></tscreen> + +Le funzioni gtk_menu_item_new_with_label e gtk_menu_item_new si comportano esattamente come +vi aspettereste dopo aver visto le funzioni che riguardano i bottoni. La prima +crea un elemento di menù con un'etichetta già applicata, mentre la seconda crea +un nuovo elemento di menù vuoto. +<p> +Ecco i passi necessari per creare una barra di menù con i relativi menù collegati: +<itemize> +<item> Create un nuovo menù con gtk_menu_new() +<item> Create un elementoa di menù con using gtk_menu_item_new(). Questo rappresenta + la base del menù, e il testo che appare qui sarà sulla barra stessa. +<item> Usate delle chiamate multiple a gtk_menu_item_new() per ognuno degli + elementi che volete mettere nel vostro menù. Usate inoltre gtk_menu_item_append() + per mettere assieme ognuno di questi nuovo elementi. Si crea così una lista di + elementi di menù. +<item> Usate gtk_menu_item_set_submenu() per attaccare gli elementi di menù + creati all'elemento di menù base (quello creato nel secondo passaggio). +<item> Create una nuova barra di menù usando gtk_menu_bar_new. Questo passo + necessita di essere effettuato una sola volta quando si crea una serie di + menù su una serie di menù su una sola barra. +<item> Usate gtk_menu_bar_append per mettere il menù base sulla barra dei menù. +</itemize> +<p> +Creare un menù a comparsa è più o meno la stessa cosa. La differenza è che il +il menù non viene attivato ``automaticamente'' da una barra, bensì esplicitamente +con la chiamata alla funzione gtk_menu_popup() da un evento di pressione di +un pulsante. +Seguite questi passaggi: +<itemize> +<item>Create una funzione di gestione di un evento. Essa deve seguire il prototipo +<tscreen> +static gint handler(GtkWidget *widget, GdkEvent *event); +</tscreen> +e usare l'evento per scoprire dove il menu deve essere fatto comparire. +<item>Nel gestore di evento, se questo è la pressione di un bottone, trattate +<tt>event</tt> come l'evento relativo ad un bottone (cosa che in effetti è) +e usatelo come mostrato nel codice di esempio per passare informazioni a +gtk_menu_popup(). +<item>Collegate il gestore di evento a un widget con +<tscreen> +gtk_signal_connect_object(GTK_OBJECT(widget), "event", + GTK_SIGNAL_FUNC (handler), GTK_OBJECT(menu)); +</tscreen> +in cui <tt>widget</tt> è il widget a cui state effettuando il collegamento, e +<tt>handler</tt> è la funzione di gestione, mentre <tt>menu</tt> è un menù +creato con gtk_menu_new(). Quest'ultimo può essere un menù che viene anche +attivato da una barra di menù, come mostrato nel codice di esempio. +</itemize> +<p> +<sect1>Esempio di Menù Manuale +<p> +Per la teoria dovrebbe essere abbastanza. Diamo un'occhiata ad un esempio che +ci aiuti a chiarire le cose. + +<tscreen><verb> + +#include <gtk/gtk.h> + +static gint button_press (GtkWidget *, GdkEvent *); +static void menuitem_response (gchar *); + + +int main (int argc, char *argv[]) +{ + + GtkWidget *window; + GtkWidget *menu; + GtkWidget *menu_bar; + GtkWidget *root_menu; + GtkWidget *menu_items; + GtkWidget *vbox; + GtkWidget *button; + char buf[128]; + int i; + + gtk_init (&argc, &argv); + + /* crea una nuova finestra */ + window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_window_set_title(GTK_WINDOW (window), "GTK Menu Test"); + gtk_signal_connect(GTK_OBJECT (window), "delete_event", + (GtkSignalFunc) gtk_exit, NULL); + + /* Inizializziamo il menù, e ricordate: mai applicare + * gtk_show_widget() al widget menù!! + * Questo è il menù che contiene gli elementi, quello che + * spunta quando si fa click sul "Menù radice" nell'applicazione */ + menu = gtk_menu_new(); + + /* Questo è il menù radice, e l'etichetta sarà il nome del menù che + * verrà mostrato sulla barra dei menù. Non ci sarà alcun gestore di + * segnale collegato, dal momento che non fa altro che mostrare il resto + * del menù quando viene premuto. */ + root_menu = gtk_menu_item_new_with_label("Root Menu"); + + gtk_widget_show(root_menu); + + /* Ora creiamo un ciclo che crea tre elementi di menu per "test-menu". + * Notete la chiamata a gtk_menu_append. In questo punto aggiungiamo una + * lista di elementi al nostro menù. Normalmente, dovremmo poi catturare + * il segnale di attivazione per ognuno degli elementi del menu, e creare + * una funzione di ritorno per ciascuno di essi, ma qui non li mettiamo per + * brevità. */ + + for(i = 0; i < 3; i++) + { + /* Copia i nomi in buf. */ + sprintf(buf, "Test-undermenu - %d", i); + + /* Crea un nuovo elemento di menù con un nome... */ + menu_items = gtk_menu_item_new_with_label(buf); + + /* ...e aggiungilo al menù. */ + gtk_menu_append(GTK_MENU (menu), menu_items); + + /* Fa qualcosa di interessante quando si seleziona l'elemento */ + gtk_signal_connect_object(GTK_OBJECT(menu_items), "activate", + GTK_SIGNAL_FUNC(menuitem_response), (gpointer) g_strdup(buf)); + + /* Mostra il widget */ + gtk_widget_show(menu_items); + } + + /* Ora specifichiamo che vogliamo che il menù che abbiamo appena creato + * sia il menù radice *// + gtk_menu_item_set_submenu(GTK_MENU_ITEM (root_menu), menu); + + /* Una vbox in cui mettere un menù ed un bottone: */ + vbox = gtk_vbox_new(FALSE, 0); + gtk_container_add(GTK_CONTAINER(window), vbox); + gtk_widget_show(vbox); + + /* Crea una barra dei menù per metterci i menù e l'aggiunge alla finestra principale */ + menu_bar = gtk_menu_bar_new(); + gtk_box_pack_start(GTK_BOX(vbox), menu_bar, FALSE, FALSE, 2); + gtk_widget_show(menu_bar); + + /* Crea un bottone a cui collegare un menù */ + button = gtk_button_new_with_label("press me"); + gtk_signal_connect_object(GTK_OBJECT(button), "event", + GTK_SIGNAL_FUNC (button_press), GTK_OBJECT(menu)); + gtk_box_pack_end(GTK_BOX(vbox), button, TRUE, TRUE, 2); + gtk_widget_show(button); + + /* E finalmente attacchiamo l'elemento di menù alla barra dei menù -- questo + * è l'elemento di menù "radice" di cui parlavo */ + gtk_menu_bar_append(GTK_MENU_BAR (menu_bar), root_menu); + + /* La finestra va mostrata sempre come ultimo passo in modo che sia già + * completa di tutti i suoi elementi. */ + gtk_widget_show(window); + + gtk_main (); + + return 0; +} + + + +/* Risponde alla pressione di un bottone impostando un menù che + * viene passato come widget. + * Notate che l'argomento "widget" si riferisce al menù impostato + * e NON al bottone premuto. + */ + +static gint button_press (GtkWidget *widget, GdkEvent *event) +{ + + if (event->type == GDK_BUTTON_PRESS) { + GdkEventButton *bevent = (GdkEventButton *) event; + gtk_menu_popup (GTK_MENU(widget), NULL, NULL, NULL, NULL, + bevent->button, bevent->time); + /* Riferisce al codice chiamante che abbiamo trattato l'evento; + * la faccenda finisce qui. */ + return TRUE; + } + + /* Riferisce al codice chiamante che abbiamo trattato l'evento; passa avanti. */ + return FALSE; +} + + +/* Stampa una stringa quando viene selezionato un elemento di menù */ + +static void menuitem_response (gchar *string) +{ + printf("%s\n", string); +} +</verb></tscreen> + +Si può anche fare in modo che un elemento di menù sia insensibile e, usando +una tabella di acelleratori, collegare dei tasti a delle funzioni di menù. +<p> +<sect1>Usare GtkMenuFactory +<p> +Ora che vi abbiamo mostrato il modo difficile, ecco invece come si fa usando +le chiamate di gtk_menu_factory. +<p> +<sect1>Esempio di Menu Factory +<p> +Ecco un esempio di utilizzo della ``Fabbrica'' di Menù di GTK (Menu Factory). +Questo è il primo file, menus.h. Teniemo dei file menus.c e main.c separati +a causa delle variabili globali usate nel file menus.c. + +<tscreen><verb> +#ifndef __MENUS_H__ +#define __MENUS_H__ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +void get_main_menu (GtkWidget **menubar, GtkAcceleratorTable **table); +void menus_create(GtkMenuEntry *entries, int nmenu_entries); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __MENUS_H__ */ +</verb></tscreen> +<p> +Ed ecco il file menus.c. + +<tscreen><verb> + +#include <gtk/gtk.h> +#include <strings.h> + +#include "main.h" + + +static void menus_remove_accel(GtkWidget * widget, gchar * signal_name, gchar * path); +static gint menus_install_accel(GtkWidget * widget, gchar * signal_name, gchar key, gchar modifiers, gchar * path); +void menus_init(void); +void menus_create(GtkMenuEntry * entries, int nmenu_entries); + +/* Questa è la struttuta GtkMenuEntry, che viene usata per creare dei nuovi + * menù. Il primo membro à la stringa di definizione del menù. Il secondo + * è il tasto acceleratore predefinito, usato per accedere a questa funzione + * con la tastiera. Il terzo è la funzione di ritorno che viene chiamata + * quando si seleziona con la tastiera o il mouse questo elemento di menù. + * L'ultimo membro costituisce il dato che viene passato alla funzione di + * ritorno. */ + +static GtkMenuEntry menu_items[] = +{ + {"<Main>/File/New", "<control>N", NULL, NULL}, + {"<Main>/File/Open", "<control>O", NULL, NULL}, + {"<Main>/File/Save", "<control>S", NULL, NULL}, + {"<Main>/File/Save as", NULL, NULL, NULL}, + {"<Main>/File/<separator>", NULL, NULL, NULL}, + {"<Main>/File/Quit", "<control>Q", file_quit_cmd_callback, "OK, I'll quit"}, + {"<Main>/Options/Test", NULL, NULL, NULL} +}; + +/* calculail numero di menu_item */ +static int nmenu_items = sizeof(menu_items) / sizeof(menu_items[0]); + +static int initialize = TRUE; +static GtkMenuFactory *factory = NULL; +static GtkMenuFactory *subfactory[1]; +static GHashTable *entry_ht = NULL; + +void get_main_menu(GtkWidget ** menubar, GtkAcceleratorTable ** table) +{ + if (initialize) + menus_init(); + + if (menubar) + *menubar = subfactory[0]->widget; + if (table) + *table = subfactory[0]->table; +} + +void menus_init(void) +{ + if (initialize) { + initialize = FALSE; + + factory = gtk_menu_factory_new(GTK_MENU_FACTORY_MENU_BAR); + subfactory[0] = gtk_menu_factory_new(GTK_MENU_FACTORY_MENU_BAR); + + gtk_menu_factory_add_subfactory(factory, subfactory[0], "<Main>"); + menus_create(menu_items, nmenu_items); + } +} + +void menus_create(GtkMenuEntry * entries, int nmenu_entries) +{ + char *accelerator; + int i; + + if (initialize) + menus_init(); + + if (entry_ht) + for (i = 0; i < nmenu_entries; i++) { + accelerator = g_hash_table_lookup(entry_ht, entries[i].path); + if (accelerator) { + if (accelerator[0] == '\0') + entries[i].accelerator = NULL; + else + entries[i].accelerator = accelerator; + } + } + gtk_menu_factory_add_entries(factory, entries, nmenu_entries); + + for (i = 0; i < nmenu_entries; i++) + if (entries[i].widget) { + gtk_signal_connect(GTK_OBJECT(entries[i].widget), "install_accelerator", + (GtkSignalFunc) menus_install_accel, + entries[i].path); + gtk_signal_connect(GTK_OBJECT(entries[i].widget), "remove_accelerator", + (GtkSignalFunc) menus_remove_accel, + entries[i].path); + } +} + +static gint menus_install_accel(GtkWidget * widget, gchar * signal_name, gchar key, gchar modifiers, gchar * path) +{ + char accel[64]; + char *t1, t2[2]; + + accel[0] = '\0'; + if (modifiers & GDK_CONTROL_MASK) + strcat(accel, "<control>"); + if (modifiers & GDK_SHIFT_MASK) + strcat(accel, "<shift>"); + if (modifiers & GDK_MOD1_MASK) + strcat(accel, "<alt>"); + + t2[0] = key; + t2[1] = '\0'; + strcat(accel, t2); + + if (entry_ht) { + t1 = g_hash_table_lookup(entry_ht, path); + g_free(t1); + } else + entry_ht = g_hash_table_new(g_string_hash, g_string_equal); + + g_hash_table_insert(entry_ht, path, g_strdup(accel)); + + return TRUE; +} + +static void menus_remove_accel(GtkWidget * widget, gchar * signal_name, gchar * path) +{ + char *t; + + if (entry_ht) { + t = g_hash_table_lookup(entry_ht, path); + g_free(t); + + g_hash_table_insert(entry_ht, path, g_strdup("")); + } +} + +void menus_set_sensitive(char *path, int sensitive) +{ + GtkMenuPath *menu_path; + + if (initialize) + menus_init(); + + menu_path = gtk_menu_factory_find(factory, path); + if (menu_path) + gtk_widget_set_sensitive(menu_path->widget, sensitive); + else + g_warning("Impossibile assegnare sensibilità a menù inesistente: %s", path); +} + +</verb></tscreen> +<p> +Ed ecco main.h + +<tscreen><verb> +#ifndef __MAIN_H__ +#define __MAIN_H__ + + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +void file_quit_cmd_callback(GtkWidget *widget, gpointer data); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __MAIN_H__ */ + +</verb></tscreen> +<p> +E main.c + +<tscreen><verb> +#include <gtk/gtk.h> + +#include "main.h" +#include "menus.h" + + +int main(int argc, char *argv[]) +{ + GtkWidget *window; + GtkWidget *main_vbox; + GtkWidget *menubar; + + GtkAcceleratorTable *accel; + + gtk_init(&argc, &argv); + + window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_signal_connect(GTK_OBJECT(window), "destroy", + GTK_SIGNAL_FUNC(file_quit_cmd_callback), + "WM destroy"); + gtk_window_set_title(GTK_WINDOW(window), "Menu Factory"); + gtk_widget_set_usize(GTK_WIDGET(window), 300, 200); + + main_vbox = gtk_vbox_new(FALSE, 1); + gtk_container_border_width(GTK_CONTAINER(main_vbox), 1); + gtk_container_add(GTK_CONTAINER(window), main_vbox); + gtk_widget_show(main_vbox); + + get_main_menu(&menubar, &accel); + gtk_window_add_accelerator_table(GTK_WINDOW(window), accel); + gtk_box_pack_start(GTK_BOX(main_vbox), menubar, FALSE, TRUE, 0); + gtk_widget_show(menubar); + + gtk_widget_show(window); + gtk_main(); + + return(0); +} + +/* Questo è per mostrare come si usano le funzioni di ritorno quando + * si utilizza la MenuFactory. Spesso, si mettono tutte le funzioni di + * callback in un file separato, e le si fanno chiamare le funzioni + * appropriate da lì. Così le cose sono più organizzate. */ +void file_quit_cmd_callback (GtkWidget *widget, gpointer data) +{ + g_print ("%s\n", (char *) data); + gtk_exit(0); +} +</verb></tscreen> +<p> +Ed infine un bel makefile per semplificare la compilazione. + +<tscreen><verb> +CC = gcc +PROF = -g +C_FLAGS = -Wall $(PROF) -L/usr/local/include -DDEBUG +L_FLAGS = $(PROF) -L/usr/X11R6/lib -L/usr/local/lib +L_POSTFLAGS = -lgtk -lgdk -lglib -lXext -lX11 -lm +PROGNAME = at + +O_FILES = menus.o main.o + +$(PROGNAME): $(O_FILES) + rm -f $(PROGNAME) + $(CC) $(L_FLAGS) -o $(PROGNAME) $(O_FILES) $(L_POSTFLAGS) + +.c.o: + $(CC) -c $(C_FLAGS) $< + +clean: + rm -f core *.o $(PROGNAME) nohup.out +distclean: clean + rm -f *~ +</verb></tscreen> +<p> +Per il momento, accontentatevi di questo esempio. Più avanti aggiungeremo +una spiegazione ed un bel po' di commenti. + + +<sect> Widget non documentati +<p> +Per questi sarebbe utile il contributo degli autori! :) Prendete in +considerazione la possibilità di contribuire al nostro tutorial. + +Se dovete usare uno di questi widget non documentati, vi suggeriamo +caldamente di dare un'occhiata ai loro rispettivi file header nella +distribuzione di GTK. I nomi delle funzioni di GTK sono molto descrittivi. +Non appena si capisce come funzionano le cose, non è +difficile dedurre il modo d'uso di un widget semplicemente guardando la +dichiarazione di funzione ad esso associata. Aggiungendo a questo qualche +spunto tratto dal codice di altri non dovrebbero esserci problemi. + +Quando avrete raggiunto una comprensione globale di tutte le funzioni +di un widget non documentato, considerate la possibilità di scrivere +un tutorial su di esso, in modo che altri possano beneficiare del +vostro lavoro. + +<sect1> Ingressi di testo (Text Entries) +<p> + +<sect1> Selezioni di colore (Color Selections) +<p> + +<sect1> Controlli di intervallo (Range Controls) +<p> + +<sect1> Righelli (Rulers) +<p> + +<sect1> Caselle di testo (Text Boxes) +<p> + +<sect1> Anteprime +<p> + +(Potrebbe essere necessario riscrivere questa parte per conformarsi allo stile +del resto del tutorial) + +<p> +Le anteprime servono a un certo numero di cose in GIMP/GTK. La più +importante è questa: a risoluzioni molto alte le immagini possono +facilmente occupare diverse decine di megabyte di memoria; ogni operazione +su immagini così grosse può richiedere molto tempo. Se per la +scelta di una data modifica vi occorrono 5-10 tentativi (cioè 10-20 +passi, poiché è necessario ripristinare l'originale se si +è commesso un errore), possono volerci letteralmente delle ore per +fare quella giusta - se non si rimane a corto di memoria prima! Coloro che +hanno passato ore in camera oscura conoscono la sensazione. In questi casi +le anteprime sono utilissime! + +Ma la seccatura dell'attesa non è l'unico caso. Spesso è utile +confrontare la versione precedente con la successiva affiancandole, o almeno +alternandole. Se si sta lavorando con grandi immagini e ritardi di una decina +di secondi un confronto efficace è quantomeno difficile da fare. +Per immagini di 30 mega (4 pollici per 6 pollici, 600 punti per pollice, 24 bit) +tale confronto risulta impraticabile per la maggior parte degli utenti. In +questo caso le anteprime sono di grande aiuto! + +Ma c'è di più. Con le anteprime è possibile scrivere +plug-in per ottenere addirittura anteprime di anteprime (per esempio, la +simulazione del pacchetto di filtri). Questi plug-in possono così +fornire un certo numero di anticipazioni di quel che si otterrebbe applicando +certe opzioni. Un simile approccio funziona come una tavolozza di anteprime, +ed è molto efficace per piccoli cambiamenti! + +Non è finita. Per alcuni plug-in può essere necessario un +intervento umano in tempo reale specifico per ogni immagine. Nel plug-in +SuperNova, ad esempio, vengono chieste le coordinate del centro della +futura supernova. Il modo più semplice per fare questo è +senza dubbio quello di mostrare un'anteprima all'utente chiedendogli di +selezionare interattivamente il centro. + +Infine, un paio di applicazioni tipiche. Le anteprime possono essere usate +anche quando non si sta lavorando con grandi immagini. Per esempio, sono +utili quando si stanno calcolando dei pattern complicati (date un'occhiata +al venerabile plug in ``Diffraction'' e a molti altri!). Altro esempio: +date un'occhiata al plug-in di rotazione della mappa dei colori (in allestimento). +Le anteprime possono anche essere usate per visualizzare in un plug-in +piccoli logo o, addirittura, l'immagine dell'Autore! + +Quando non usare le anteprime + +Le anteprime non vanno usate per grafici, disegni ecc., poiché per +queste cose GDK è molto più veloce. Le anteprime vanno usate +solo per immagini derivate da un'elaborazione! + +Le anteprime possono essere inserite dappertutto. In un vbox, in un hbox, +in una tabella, in un bottone, ecc. Sicuramente però hanno il loro +look migliore se bordate con delle cornici (frame). Le anteprime non hanno +bordi propri e appaiono piatte senza (naturalmente, se quel che si vuole +è proprio un aspetto piatto...). I bordi possono essere creati con +delle cornici. + + [Image][Image] + +Le anteprime sono per molti aspetti simili agli altri widget in GTK (con +tutto ciò che questo implica), con l'eccezione di avere una +caratteristica in più: è necessario che siano riempite con +qualche tipo di immagine! Inizialmente parleremo solo dell'aspetto GTK +delle anteprime e successivamente discuteremo di come riempirle. + +Semplicemente: + +<tscreen><verb> + /* Crea un widget di anteprima, + inizializzane le dimensioni + e visualizzalo */ +GtkWidget *preview; +preview=gtk_preview_new(GTK_PREVIEW_COLOR) + /* Alternativamente: + GTK_PREVIEW_GRAYSCALE);*/ +gtk_preview_size (GTK_PREVIEW (preview), WIDTH, HEIGHT); +gtk_widget_show(preview); +my_preview_rendering_function(preview); +</verb></tscreen> + +Come già detto, le anteprime hanno un buon aspetto dentro le cornici, +quindi: + +<tscreen><verb> +GtkWidget *create_a_preview(int Width, + int Height, + int Colorfulness) +{ + GtkWidget *preview; + GtkWidget *frame; + + frame = gtk_frame_new(NULL); + gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN); + gtk_container_border_width (GTK_CONTAINER(frame),0); + gtk_widget_show(frame); + + preview=gtk_preview_new (Colorfulness?GTK_PREVIEW_COLOR + :GTK_PREVIEW_GRAYSCALE); + gtk_preview_size (GTK_PREVIEW (preview), Width, Height); + gtk_container_add(GTK_CONTAINER(frame),preview); + gtk_widget_show(preview); + + my_preview_rendering_function(preview); + return frame; +} + +</verb></tscreen> + +Questa è una semplice anteprima. Questa funzione restituisce la cornice +``madre'', in modo che sia possibile metterla in qualche altro posto nella vostra +interfaccia. Naturalmente è possibile passare alla routine la cornice +madre come parametro. In molte situazioni, comunque, il contenuto di un'anteprima +viene aggiornato continuamente dall'applicazione; in questi casi potreste +preferire passare alla funzione ``create_a_preview()'' un puntatore +all'anteprima, ottenendone così il controllo dopo. + +Un'avvertimento più importante che potrebbe un giorno risparmiarvi +tanto tempo perso: a volte è preferibile etichettare le anteprime; +ad esempio, è possibile etichettare l'anteprima contenente l'immagine +originale come ``Originale'' e quella contenente l'immagine modificata come +``Modificata''. Potrebbe capitarvi di impacchettare in un vbox l'anteprima +insieme con l'etichetta associata. L'insidia inattesa sta nel fatto che se +l'etichetta è più ampia dell'anteprima (cosa che può +accadere per una varietà di motivi da voi non prevedibili, come il +fatto che la dimensione dell'anteprima viene decisa dinamicamente, o la +dimensione del font), la cornice si espande e non risulta più +perfettamente aderente all'anteprima. Questo stesso problema probabilmente +può verificarsi anche in altre situazioni. + + [Image] + +La soluzione è quella di mettere l'anteprima e l'etichetta in una +tabella 2x1 e di legarle insieme chiamando la funzione gtk_table_attach con +i seguenti parametri (questa è una delle varianti possibili, +naturalmente; l'importante è che non ci sia GTK_FILL nella seconda +gtk_table_attach): + +<tscreen><verb> +gtk_table_attach(GTK_TABLE(table),label,0,1,0,1, + 0, + GTK_EXPAND|GTK_FILL, + 0,0); +gtk_table_attach(GTK_TABLE(table),frame,0,1,1,2, + GTK_EXPAND, + GTK_EXPAND, + 0,0); +</verb></tscreen> + +Ed ecco il risultato: + + [Image] + +Altri suggerimenti + +La maniera più semplice per rendere cliccabile un'anteprima è +quella di metterla dentro un bottone. Questo ha anche l'effetto di aggiungere +un bel bordo attorno all'anteprima, il che rende superfluo metterla in una +cornice. + +Questo è tutto per quel che riguarda GTK. + + +Completare un'anteprima + +Per impratichirci con le basi del completamento delle anteprime, creiamo +il seguente disegno (trovato per tentativi): + + [Image] + +<tscreen><verb> +void +my_preview_rendering_function(GtkWidget *preview) +{ +#define SIZE 100 +#define HALF (SIZE/2) + + guchar *row=(guchar *) malloc(3*SIZE); /* 3 bits per dot */ + gint i, j; /* Coordinates */ + double r, alpha, x, y; + + if (preview==NULL) return; /* Di solito aggiungo questo per */ + /* evitare piantamenti stupidi. */ + /* Probabilmente bisognerebbe */ + /* assicurarsi che tutto sia stato*/ + /* inizializzato con successo */ + for (j=0; j < ABS(cos(2*alpha)) ) { /* Siamo dentro la sagoma? */ + /* glib.h contiene ABS(x). */ + row[i*3+0] = sqrt(1-r)*255; /* Definisce il Rosso */ + row[i*3+1] = 128; /* Definisce il Verde */ + row[i*3+2] = 224; /* Definisce il Blu */ + } /* "+0" è per allineamento */ + else { + row[i*3+0] = r*255; + row[i*3+1] = ABS(sin((float)i/SIZE*2*PI))*255; + row[i*3+2] = ABS(sin((float)j/SIZE*2*PI))*255; + } + } + gtk_preview_draw_row( GTK_PREVIEW(preview),row,0,j,SIZE); + /* Inserisce "row" in "preview" a partire del punto avente */ + /* coordinate (0,j) prima colonna, j-esima riga, per SIZE */ + /* pixel verso destra */ + } + + free(row); /* libera un po' di memoria */ + gtk_widget_draw(preview,NULL); /* indovina cosa fa questo? */ + gdk_flush(); /* e questo? */ +} +</verb></tscreen> +Coloro che non usano GIMP probabilmente hanno già visto abbastanza +per fare molte cose. Per gli utenti GIMP c'è ancora qualcosa da +aggiungere. + +Anteprima dell'immagine + +Probabilmente è opportuno tenere pronta una versione ridotta dell'immagine, +grande quanto basta per riempire l'anteprima. Questo può essere fatto +selezionando un pixel ogni n, dove n è il rapporto tra la dimensione +dell'immagine e la dimensione dell'anteprima. Tutte le operazioni successive +(compreso il riempimento dell'anteprima) sono fatte solo sul ridotto numero +di pixel selezionati. Di seguito è riportata un'implementazione della +riduzione dell'immagine (si tenga presente che ho preso solo lezioni basilari +di C!). + + +(ATTENZIONE: CODICE NON VERIFICATO!!!) + +<tscreen><verb> + +typedef struct { + gint width; + gint height; + gint bbp; + guchar *rgb; + guchar *mask; +} ReducedImage; + +enum { + SELECTION_ONLY, + SELCTION_IN_CONTEXT, + ENTIRE_IMAGE +}; + +ReducedImage *Reduce_The_Image(GDrawable *drawable, + GDrawable *mask, + gint LongerSize, + gint Selection) +{ + /* Questa funzione riduce l'immagine alla dimens. scelta per l'anteprima */ + /* La dimensione dell'anteprima è determinata da LongerSize, cioè la più */ + /* grande delle dimensioni. Funziona solo per immagini RGB! */ + gint RH, RW; /* Altezza ridotta e larghezza ridotta */ + gint width, height; /* Larghezza e altezza dell'area da ridurre */ + gint bytes=drawable->bpp; + ReducedImage *temp=(ReducedImage *)malloc(sizeof(ReducedImage)); + + guchar *tempRGB, *src_row, *tempmask, *src_mask_row,R,G,B; + gint i, j, whichcol, whichrow, x1, x2, y1, y2; + GPixelRgn srcPR, srcMask; + gint NoSelectionMade=TRUE; /* Assumiamo di trattare l'intera immagine */ + + gimp_drawable_mask_bounds (drawable->id, &x1, &y1, &x2, &y2); + width = x2-x1; + height = y2-y1; + /* Se c'è una SELEZIONE, ne abbiamo avuto gli estremi! */ + + if (width != drawable->width && height != drawable->height) + NoSelectionMade=FALSE; + /* Controlliamo se l'utente ha una selezione attiva. Questo */ + /* diventerà importante dopo, alla creazione di una maschera ridotta */ + + /* Se si vuole l'anteprima dell'immagine intera, annulla quanto sopra */ + /* Naturalmente, in assenza di una selezione, questo non cambia nulla */ + if (Selection==ENTIRE_IMAGE) { + x1=0; + x2=drawable->width; + y1=0; + y2=drawable->height; + } + + /* Se si vuole l'anteprima di una selezione con parte dell'area */ + /* circostante bisogna espanderla un po'. */ + if (Selection==SELECTION_IN_CONTEXT) { + x1=MAX(0, x1-width/2.0); + x2=MIN(drawable->width, x2+width/2.0); + y1=MAX(0, y1-height/2.0); + y2=MIN(drawable->height, y2+height/2.0); + } + + /* Così si determinano larghezza e altezza dell'area da ridurre. */ + width = x2-x1; + height = y2-y1; + + /* Le linee seguenti determinano quale dimensione deve essere il */ + /* lato più lungo. L'idea è presa dal plug-in supernova. Ritengo */ + /* che avrei potuto pensarci da solo, ma la verità va detta. */ + /* Brutta cosa il plagio! */ + if (width>height) { + RW=LongerSize; + RH=(float) height * (float) LongerSize/ (float) width; + } + else { + RH=LongerSize; + RW=(float)width * (float) LongerSize/ (float) height; + } + + /* L'intera immagine viene "stirata" in una stringa! */ + tempRGB = (guchar *) malloc(RW*RH*bytes); + tempmask = (guchar *) malloc(RW*RH); + + gimp_pixel_rgn_init (&srcPR, drawable, x1, y1, width, height, FALSE, FALSE); + gimp_pixel_rgn_init (&srcMask, mask, x1, y1, width, height, FALSE, FALSE); + + /* Prendine abbastanza da contenere una riga di immagine e una di maschera */ + src_row = (guchar *) malloc (width*bytes); + src_mask_row = (guchar *) malloc (width); + + for (i=0; i < RH; i++) { + whichrow=(float)i*(float)height/(float)RH; + gimp_pixel_rgn_get_row (&srcPR, src_row, x1, y1+whichrow, width); + gimp_pixel_rgn_get_row (&srcMask, src_mask_row, x1, y1+whichrow, width); + + for (j=0; j < RW; j++) { + whichcol=(float)j*(float)width/(float)RW; + + /* Nessuna selezione = tutti i punti sono completamente selezionati */ + if (NoSelectionMade) + tempmask[i*RW+j]=255; + else + tempmask[i*RW+j]=src_mask_row[whichcol]; + + /* Aggiungi la riga alla lunga stringa che ora contiene l'immagine */ + tempRGB[i*RW*bytes+j*bytes+0]=src_row[whichcol*bytes+0]; + tempRGB[i*RW*bytes+j*bytes+1]=src_row[whichcol*bytes+1]; + tempRGB[i*RW*bytes+j*bytes+2]=src_row[whichcol*bytes+2]; + + /* Mantieni anche la trasparenza (alpha) */ + if (bytes==4) + tempRGB[i*RW*bytes+j*bytes+3]=src_row[whichcol*bytes+3]; + } + } + temp->bpp=bytes; + temp->width=RW; + temp->height=RH; + temp->rgb=tempRGB; + temp->mask=tempmask; + return temp; +} +</verb></tscreen> + + +La seguente è una funzione di anteprima che usa lo stesso tipo +ReducedImage! Si noti che usa una finta trasparenza - se ne è presente +una, tramite fake_transparency che è definita come segue: + +<tscreen><verb> +gint fake_transparency(gint i, gint j) +{ + if ( ((i%20)- 10) * ((j%20)- 10)>0 ) + return 64; + else + return 196; +} + +</verb></tscreen> +E adesso la funzione per l'anteprima: +<tscreen><verb> +void +my_preview_render_function(GtkWidget *preview, + gint changewhat, + gint changewhich) +{ + gint Inten, bytes=drawable->bpp; + gint i, j, k; + float partial; + gint RW=reduced->width; + gint RH=reduced->height; + guchar *row=malloc(bytes*RW);; + + + for (i=0; i < RH; i++) { + for (j=0; j < RW; j++) { + + row[j*3+0] = reduced->rgb[i*RW*bytes + j*bytes + 0]; + row[j*3+1] = reduced->rgb[i*RW*bytes + j*bytes + 1]; + row[j*3+2] = reduced->rgb[i*RW*bytes + j*bytes + 2]; + + if (bytes==4) + for (k=0; k<3; k++) { + float transp=reduced->rgb[i*RW*bytes+j*bytes+3]/255.0; + row[3*j+k]=transp*a[3*j+k]+(1-transp)*fake_transparency(i,j); + } + } + gtk_preview_draw_row( GTK_PREVIEW(preview),row,0,i,RW); + } + + free(a); + gtk_widget_draw(preview,NULL); + gdk_flush(); +} + +Funzioni Applicabili + +guint gtk_preview_get_type (void); +/* No idea */ +void gtk_preview_uninit (void); +/* No idea */ +GtkWidget* gtk_preview_new (GtkPreviewType type); +/* Descritta precedentemente */ +void gtk_preview_size (GtkPreview *preview, + gint width, + gint height); +/* Permette di ridimensionare un'anteprima esistente */ +/* Pare che un bug in GTK renda disordinato questo */ +/* processo. Un modo di rimettere le cose a posto */ +/* è quello di ridimensionare manualmente */ +/* la finestra contenente l'anteprima dopo aver */ +/* ridimensionato l'anteprima. */ + +void gtk_preview_put (GtkPreview *preview, + GdkWindow *window, + GdkGC *gc, + gint srcx, + gint srcy, + gint destx, + gint desty, + gint width, + gint height); +/* No idea */ + +void gtk_preview_put_row (GtkPreview *preview, + guchar *src, + guchar *dest, + gint x, + gint y, + gint w); +/* No idea */ + +void gtk_preview_draw_row (GtkPreview *preview, + guchar *data, + gint x, + gint y, + gint w); +/* Descritta nel testo */ + +void gtk_preview_set_expand (GtkPreview *preview, + gint expand); +/* No idea */ + +/* Nessun indizio per le seguenti, ma dovrebbero */ +/* essere standard per la maggior parte dei widget */ +void gtk_preview_set_gamma (double gamma); +void gtk_preview_set_color_cube (guint nred_shades, + guint ngreen_shades, + guint nblue_shades, + guint ngray_shades); +void gtk_preview_set_install_cmap (gint install_cmap); +void gtk_preview_set_reserved (gint nreserved); +GdkVisual* gtk_preview_get_visual (void); +GdkColormap* gtk_preview_get_cmap (void); +GtkPreviewInfo* gtk_preview_get_info (void); + +E' tutto! + +</verb></tscreen> + + +<sect1> Curve +<p> + + +<sect>Il Widget EventBox<label id="sec_The_EventBox_Widget"> +<p> +E' disponibile solo a partire dalla distribuzione gtk+970916.tar.gz. +<p> +Alcuni widget gtk non sono associati a finestre X, sicché +semplicemente disegnano sui loro genitori. Per questo motivo essi non possono +ricevere eventi e se sono sovradimensionati non vengono troncati, ma rischiano +di sovrapporsi, generando confusione. Se si vuole di più da questi +widget si può ricorrere agli EventBox. + +A prima vista il widget EventBox potrebbe sembrare completamente inutile. Non +disegna nulla sullo schermo e non risponde a nessun evento. Tuttavia ha +una funzione: fornire una finestra X al suo widget figlio. Ciò +è importante in quanto molti widget GTK non hanno una finestra X +associata. Se questo da una parte risparmia memoria e migliora le prestazioni, +dall'altra introduce degli svantaggi: un widget senza una finestra X non +può ricevere eventi, e non taglia in alcun modo il suo contenuto. +Sebbene il nome ``EventBox'' (casella di eventi) enfasizzi la funzione di +gestione degli eventi, il widget può essere usato anche per +limitare la dimensione dei widget figli (ma anche per altro: si veda +l'esempio seguente). + +<p> +Per creare un widget di tipo EventBox: + +<tscreen><verb> +GtkWidget* gtk_event_box_new (void); +</verb></tscreen> + +<p> +All'EventBox si può aggiungere un widget figlio: + +<tscreen><verb> +gtk_container_add (GTK_CONTAINER(event_box), widget); +</verb></tscreen> + +<p> +The following example demonstrates both uses of an EventBox - a label +is created that clipped to a small box, and set up so that a +mouse-click on the label causes the program to exit. +Il seguente esempio mostra entrambi gli usi di un EventBox - si crea +un'etichetta limitata da un rettangolo piccolo, fatta in modo che +cliccando con il mouse su di essa il programma termina. + +<tscreen><verb> +#include <gtk/gtk.h> + +int +main (int argc, char *argv[]) +{ + GtkWidget *window; + GtkWidget *event_box; + GtkWidget *label; + + gtk_init (&argc, &argv); + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + + gtk_window_set_title (GTK_WINDOW (window), "Event Box"); + + gtk_signal_connect (GTK_OBJECT (window), "destroy", + GTK_SIGNAL_FUNC (gtk_exit), NULL); + + gtk_container_border_width (GTK_CONTAINER (window), 10); + + /* Crea un EventBox e lo aggiunge alla finestra principale */ + + event_box = gtk_event_box_new (); + gtk_container_add (GTK_CONTAINER(window), event_box); + gtk_widget_show (event_box); + + /* Crea una etichetta lunga */ + + label = gtk_label_new ("Click here to quit, quit, quit, quit, quit"); + gtk_container_add (GTK_CONTAINER (event_box), label); + gtk_widget_show (label); + + /* Limitane le dimensioni */ + gtk_widget_set_usize (label, 110, 20); + + /* E collega ad essa una azione */ + gtk_widget_set_events (event_box, GDK_BUTTON_PRESS_MASK); + gtk_signal_connect (GTK_OBJECT(event_box), "button_press_event", + GTK_SIGNAL_FUNC (gtk_exit), NULL); + + /* Un'altra cosa per cui si ha bisogno di una finestra X ... */ + + gtk_widget_realize (event_box); + gdk_window_set_cursor (event_box->window, gdk_cursor_new (GDK_HAND1)); + + gtk_widget_show (window); + + gtk_main (); + + return 0; +} +</verb></tscreen> + +<sect>Selezionare gli Attributi dei Widget<label id="sec_setting_widget_attributes"> +<p> +Qui si descrivono le funzioni per la gestione dei widget. Esse possono essere +usate per impostarne lo stile, il padding, le dimensioni, ... + +(Forse andrebbe fatta un'intera sezione sugli acceleratori). + +<tscreen><verb> +void gtk_widget_install_accelerator (GtkWidget *widget, + GtkAcceleratorTable *table, + gchar *signal_name, + gchar key, + guint8 modifiers); + +void gtk_widget_remove_accelerator (GtkWidget *widget, + GtkAcceleratorTable *table, + gchar *signal_name); + +void gtk_widget_activate (GtkWidget *widget); + +void gtk_widget_set_name (GtkWidget *widget, + gchar *name); +gchar* gtk_widget_get_name (GtkWidget *widget); + +void gtk_widget_set_sensitive (GtkWidget *widget, + gint sensitive); + +void gtk_widget_set_style (GtkWidget *widget, + GtkStyle *style); + +GtkStyle* gtk_widget_get_style (GtkWidget *widget); + +GtkStyle* gtk_widget_get_default_style (void); + +void gtk_widget_set_uposition (GtkWidget *widget, + gint x, + gint y); +void gtk_widget_set_usize (GtkWidget *widget, + gint width, + gint height); + +void gtk_widget_grab_focus (GtkWidget *widget); + +void gtk_widget_show (GtkWidget *widget); + +void gtk_widget_hide (GtkWidget *widget); +</verb></tscreen> + + + +<sect>Funzioni periodiche, di I/O e di attesa<label id="sec_timeouts"> +<p> +<sect1>Funzioni periodiche +<p> +Probabilmente vi sarete chiesti come far fare qualcosa di utile a GTK +durante la chiamata alla gtk_main(). Ci sono diverse possibilità. +Usando le seguenti funzioni si possono creare funzioni che vengono chiamate +periodicamente. + +<tscreen><verb> +gint gtk_timeout_add (guint32 interval, + GtkFunction function, + gpointer data); +</verb></tscreen> + +Il primo argomento è il numero di millisecondi tra le chiamate alla +funzione. Il secondo è la funzione periodica, mentre il terzo +rappresenta i dati che vengono passati alla funzione. Il valore restituito +è un'etichetta che può essere utilizzata per fermare la chiamata +periodica, passandolo alla funzione: + +<tscreen><verb> +void gtk_timeout_remove (gint tag); +</verb></tscreen> + +La chiamata periodica si ferma anche se la funzione periodica ritorna zero +o FALSE. Naturalmente questo vuol dire che se si vuole che la funzione periodica +continui ad essere richiamata, essa deve restituire un valore non nullo, +cioè TRUE. + +La dichiarazione della funzione periodica dovrebbe essere come questa: + +<tscreen><verb> +gint timeout_callback (gpointer data); +</verb></tscreen> + +<sect1>Controllo dell'I/O +<p> +Un'altra utile caratteristica di GTK è la possibilità di fargli +controllare che siano verificate certe condizioni su un descrittore di file +(come quelli restituiti da open(2) o socket(2)). Questo è utile in +particolar modo per le applicazioni di rete. La funzione è la seguente: + +<tscreen><verb> +gint gdk_input_add (gint source, + GdkInputCondition condition, + GdkInputFunction function, + gpointer data); +</verb></tscreen> + +Il primo argomento è il descrittore che si desidera venga controllato, +mentre il secondo specifica quale condizione si vuole che GDK controlli. +Questa può essere una tra: +<p> +GDK_INPUT_READ - Chiama la funzione quando ci sono dati pronti per la lettura +nel descrittore di file. +<p> +GDK_INPUT_WRITE - Chiama la funzione quando il descrittore di file è +pronto per la scrittura. +<p> +Come sicuramente avrete già intuito, il terzo parametro è la +funzione da chiamare quando la condizione specificata è soddisfatta, +mentre il quarto rappresenta i dati da passare a questa funzione. +<p> +Il valore di ritorno è un etichetta che può essere usata per +fermare il controllo di GDK sul descrittore di file, usando la seguente +funzione: +<p> +<tscreen><verb> +void gdk_input_remove (gint tag); +</verb></tscreen> +<p> +La funzione da richiamare va dichiarata così: +<p> +<tscreen><verb> +void input_callback (gpointer data, gint source, + GdkInputCondition condition); +</verb></tscreen> +<p> + +<sect1>Funzioni di attesa (``Idle'') +<p> +Cosa fare se si ha una funzione che si vuole venga chiamata quando non +sta accadendo nient'altro? + +<tscreen><verb> +gint gtk_idle_add (GtkFunction function, + gpointer data); +</verb></tscreen> + +Questa fa si che GDK chiami la funzione specificata quando non c'è +nessuna altra operazione in corso. + +<tscreen><verb> +void gtk_idle_remove (gint tag); +</verb></tscreen> +<p> +Non ci soffermeremo sul significato dei parametri in quanto del tutto analoghi +ai precedenti. La funzione puntata dal primo argomento della gtk_idle_add +viene chiamata non appena se ne presenta l'opportunità; come +negli altri casi, se essa restituisce FALSE non viene più chiamata. + + +<sect>La gestione delle selezioni + +<sect1> Overview + +<p> + +Le <em>selezioni</em> sono un tipo di comunicazione tra processi +supportato da GTK. Una selezione identifica un frammento di dati; per +esempio, una porzione di testo selezionata dall'utente in qualche modo, +magari con il mouse. Su un display solo un'applicazione alla volta +(il <em>proprietario</em>) puó essere proprietaria di una +particolare selezione, sicché quando un'applicazione richiede +una selezione il precedente proprietario deve comunicare all'utente che +la selezione è stata ceduta. Altre applicazioni possono richiedere +il contenuto di una selezione in diverse forme, chiamate <em>obiettivi</em>. +Ci può essere un numero qualsiasi di selezioni, ma la maggior parte +delle applicazioni X può gestirne solo una, la <em>selezione +primaria</em>. + +<p> +Nella maggior parte dei casi per una applicazione GTK non è +necessario gestire esplicitamente le selezioni. I widget standard, +come quello di Ingresso, hanno già la capacità di +chiedere la selezione se necessario (p. e., quando l'utente +seleziona sul testo), e di recuperare il contenuto di una selezione +di un altro widget o di un'altra applicazione (p. e., quando l'utente +clicca il tasto centrale del mouse). Ci possono comunque essere dei +casi nei quali si vuole dare ad altri widget la capacità di +fornire la selezione, o si vogliono recuperare degli obiettivi non +supportati direttamente. + +<p> +Un concetto fondamentale necessario per comprendere la gestione delle +selezioni è quello di <em>atomo</em>. Un atomo è un intero +che identifica univocamente una stringa (su un certo display). +Certi atomi sono predefiniti dal server X, e in alcuni casi in <tt>gtk.h</tt> +ci sono costanti corrispondenti a questi atomi. Per esempio, la costante +<tt>GDK_PRIMARY_SELECTION</tt> corrisponde alla stringa ``PRIMARY''. +Negli altri casi bisogna usare le funzioni <tt>gdk_atom_intern()</tt> +per ottenere l'atomo corrispondente ad una stringa, e <tt>gdk_atom_name()</tt> +per ottenere il nome di un atomo. Sia le selezioni sia gli obiettivi sono +identificati da atomi. + +<sect1> Recuperare le selezioni + +<p> + +Il recupero di una selezione è un processo asincrono. Per iniziare +il processo, si chiama: +<tscreen><verb> +gint gtk_selection_convert (GtkWidget *widget, + GdkAtom selection, + GdkAtom target, + guint32 time) +</verb</tscreen> + +Questo <em>converte</em> la selezione nella forma specificata +dall'obiettivo <tt/target/. Se possibile, il campo <tt/time/ +dovrebbe essere il tempo dell'evento che ha attivato la selezione. +Questo aiuta a far si che gli eventi avvengano nell'ordine in cui +l'utente li ha richiesti. Se comunque non fosse disponibile (per +esempio, se la conversione è stata attivata da un segnale di +``cliccato''), allora si può usare la costante +<tt>GDK_CURRENT_TIME</tt>. + +<p> +Quando il proprietario di una selezione risponde ad una richiesta, +un segnale ``selection_received'' (selezione ricevuta) viene inviato +alla vostra applicazione. Il gestore di questo segnale riceve un +puntatore ad una struttura <tt>GtkSelectionData</tt>, che è +definita nel modo seguente: +<tscreen><verb> +struct _GtkSelectionData +{ + GdkAtom selection; + GdkAtom target; + GdkAtom type; + gint format; + guchar *data; + gint length; +}; +</verb></tscreen> +<tt>selection</tt> e <tt>target</tt> sono i valori da voi specificati +nella chiamata <tt>gtk_selection_convert()</tt>. <tt>type</tt> è +un atomo che identifica il tipo di dati restituiti dal proprietario della +selezione. Alcuni valori possibili sono ``STRING'', una stringa di +caratteri latin-1, ``ATOM'', una serie di atomi, ``INTEGER'', un intero, ecc. +La maggior parte degli obiettivi può restituire solo un tipo. +<tt/format/ ci dà la lunghezza delle unità (per esempio caratteri) +in bit. Di solito, quando si ricevono i dati non ci si cura di questo. +<tt>data</tt> è un puntatore ai dati restituiti, e <tt>length</tt> +è la lunghezza dei dati restituiti, in byte. Se <tt>length</tt> +è negativo allora si è verificato un errore e non è +stato possibile recuperare la selezione. Questo può avvenire se +nessuna applicazione era proprietaria della selezione, o se si è +richiesto un obiettivo non supportato dall'applicazione. Viene garantito +che il buffer sia un byte più lungo di <tt>length</tt>; il byte +in più sarà sempre zero, in modo che non sia necessario +ricopiare le stringhe solo per farle terminare con zero. + +<p> +Nell'esempio che segue viene recuperato l'obiettivo speciale ``TARGETS'', +che è una lista di tutti gli obiettivi in cui può essere +convertita la selezione. +<tscreen><verb> +#include <gtk/gtk.h> + +void selection_received (GtkWidget *widget, + GtkSelectionData *selection_data, + gpointer data); + +/* Gestore di segnale chiamato quando l'utente clicca nel bottone */ +/* "Get Targets" */ +void +get_targets (GtkWidget *widget, gpointer data) +{ + static GdkAtom targets_atom = GDK_NONE; + + /* Prende l'atomo corrispondente alla stringa "TARGETS" */ + if (targets_atom == GDK_NONE) + targets_atom = gdk_atom_intern ("TARGETS", FALSE); + + /* E richiede l'obiettivo "TARGETS" per la selezione primaria */ + gtk_selection_convert (widget, GDK_SELECTION_PRIMARY, targets_atom, + GDK_CURRENT_TIME); +} + +/* Gestore di segnale chiamato quando il proprietario della selezione */ +/* restituisce i dati */ +void +selection_received (GtkWidget *widget, GtkSelectionData *selection_data, + gpointer data) +{ + GdkAtom *atoms; + GList *item_list; + int i; + + /* **** IMPORTANTE **** Controlla che il recupero sia riuscito */ + if (selection_data->length < 0) + { + g_print ("Selection retrieval failed\n"); + return; + } + /* Make sure we got the data in the expected form */ + if (selection_data->type != GDK_SELECTION_TYPE_ATOM) + { + g_print ("Selection \"TARGETS\" was not returned as atoms!\n"); + return; + } + + /* Stampa gli atomi ricevuti */ + atoms = (GdkAtom *)selection_data->data; + + item_list = NULL; + for (i=0; i<selection_data->length/sizeof(GdkAtom); i++) + { + char *name; + name = gdk_atom_name (atoms[i]); + if (name != NULL) + g_print ("%s\n",name); + else + g_print ("(bad atom)\n"); + } + + return; +} + +int +main (int argc, char *argv[]) +{ + GtkWidget *window; + GtkWidget *button; + + gtk_init (&argc, &argv); + + /* Create the toplevel window */ + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_window_set_title (GTK_WINDOW (window), "Event Box"); + gtk_container_border_width (GTK_CONTAINER (window), 10); + + gtk_signal_connect (GTK_OBJECT (window), "destroy", + GTK_SIGNAL_FUNC (gtk_exit), NULL); + + /* Crea un bottone che l'utente può cliccare per ottenere gli obiettivi */ + + button = gtk_button_new_with_label ("Get Targets"); + gtk_container_add (GTK_CONTAINER (window), button); + + gtk_signal_connect (GTK_OBJECT(button), "clicked", + GTK_SIGNAL_FUNC (get_targets), NULL); + gtk_signal_connect (GTK_OBJECT(button), "selection_received", + GTK_SIGNAL_FUNC (selection_received), NULL); + + gtk_widget_show (button); + gtk_widget_show (window); + + gtk_main (); + + return 0; +} +</verb></tscreen> + +<sect1> Fornire una selezione + +<p> +Fornire la selezione è un po' più complicato. Bisogna +registrare i gestori che verranno chiamati quando viene richiesta la +propria selezione. Per ogni coppia selezione/obiettivo che si gestirà +occorre una chiamata a: + +<tscreen><verb> +void gtk_selection_add_handler (GtkWidget *widget, + GdkAtom selection, + GdkAtom target, + GtkSelectionFunction function, + GtkRemoveFunction remove_func, + gpointer data); +</verb></tscreen> + +<tt/widget/, <tt/selection/, e <tt/target/ identificano le richieste +che questo gestore soddisferà. <tt/remove_func/, se non è +NULL, verrà chiamato quando il gestore di segnale viene rimosso. +Questo è utile, per esempio, per linguaggi interpretati ai quali +serve di tener traccia di un conteggio di riferimento per <tt/data/. + +<p> +La funzione di richiamo ha la forma: + +<tscreen><verb> +typedef void (*GtkSelectionFunction) (GtkWidget *widget, + GtkSelectionData *selection_data, + gpointer data); + +</verb></tscreen> + +La GtkSelectionData è la stessa di prima, ma stavolta siamo +responsabili di riempire i campi <tt/type/, <tt/format/, <tt/data/, +e <tt/length/. (Il campo <tt/format/ qui è effettivamente +importante - il server X lo usa per capire se occorre che i byte +dei dati vengano scambiati o no. Di solito sarà 8 - cioè +un carattere - o 32 - cioè un intero.) Questo viene fatto +chiamando la funzione: + +<tscreen><verb> +void gtk_selection_data_set (GtkSelectionData *selection_data, + GdkAtom type, + gint format, + guchar *data, + gint length); +</verb></tscreen> +Questa funzione si prende cura di fare propriamente una copia dei dati +in modo che non ci si debba preoccupare di conservarli (è opportuno +evitare di riempire a mano i campi della struttura GtkSelectionData). + +<p> +Quando richiesto dall'utente, richiederete la proprietà della selezione +chiamando: + +<tscreen><verb> +gint gtk_selection_owner_set (GtkWidget *widget, + GdkAtom selection, + guint32 time); +</verb></tscreen> + +Se un'altra applicazione richiede la proprietà della selezione, +riceverete un evento di azzeramento della selezione (``selection_clear_event''). + +Come esempio di fornitura della selezione, il programma seguente aggiunge +la funzionalità di selezione a un bottone di attivazione. Quando il +bottone viene premuto, il programma richiede la selezione primaria. +L'unico obiettivo supportato (oltre a certi obiettivi come ``TARGETS'' +fornito dalla stessa GTK) è l'obiettivo ``STRING''. Quando viene +richiesto questo obiettivo, viene restituita una rappresentazione stringa +del tempo. + +<tscreen><verb> +#include <gtk/gtk.h> +#include <time.h> + +/* Richiamata quando l'utente attiva la selezione */ +void +selection_toggled (GtkWidget *widget, gint *have_selection) +{ + if (GTK_TOGGLE_BUTTON(widget)->active) + { + *have_selection = gtk_selection_owner_set (widget, + GDK_SELECTION_PRIMARY, + GDK_CURRENT_TIME); + /* se il richiamo della selezione è fallito, si riporta il + bottone nello stato non premuto */ + if (!*have_selection) + gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON(widget), FALSE); + } + else + { + if (*have_selection) + { + /* Prima di annullare la selezione mettendone a NULL il proprietario, + controlliamo se siamo i veri proprietari */ + if (gdk_selection_owner_get (GDK_SELECTION_PRIMARY) == widget->window) + gtk_selection_owner_set (NULL, GDK_SELECTION_PRIMARY, + GDK_CURRENT_TIME); + *have_selection = FALSE; + } + } +} + +/* Chiamata quando un'altra applicazione richiede la selezione */ +gint +selection_clear (GtkWidget *widget, GdkEventSelection *event, + gint *have_selection) +{ + *have_selection = FALSE; + gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON(widget), FALSE); + + return TRUE; +} + +/* Fornisce come selezione il tempo attuale */ +void +selection_handle (GtkWidget *widget, + GtkSelectionData *selection_data, + gpointer data) +{ + gchar *timestr; + time_t current_time; + + current_time = time (NULL); + timestr = asctime (localtime(&current_time)); + /* Quando si restituisce una singola stringa, non occorre che finisca + con NULL. Questo verrà fatto automaticamente */ + + gtk_selection_data_set (selection_data, GDK_SELECTION_TYPE_STRING, + 8, timestr, strlen(timestr)); +} + +int +main (int argc, char *argv[]) +{ + GtkWidget *window; + + GtkWidget *selection_button; + + static int have_selection = FALSE; + + gtk_init (&argc, &argv); + + /* Crea la finestra di livello superiore */ + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_window_set_title (GTK_WINDOW (window), "Event Box"); + gtk_container_border_width (GTK_CONTAINER (window), 10); + + gtk_signal_connect (GTK_OBJECT (window), "destroy", + GTK_SIGNAL_FUNC (gtk_exit), NULL); + + /* Crea un bottone a commutazione che agisce come la selezione */ + + selection_button = gtk_toggle_button_new_with_label ("Claim Selection"); + gtk_container_add (GTK_CONTAINER (window), selection_button); + gtk_widget_show (selection_button); + + gtk_signal_connect (GTK_OBJECT(selection_button), "toggled", + GTK_SIGNAL_FUNC (selection_toggled), &have_selection); + gtk_signal_connect (GTK_OBJECT(selection_button), "selection_clear_event", + GTK_SIGNAL_FUNC (selection_clear), &have_selection); + + gtk_selection_add_handler (selection_button, GDK_SELECTION_PRIMARY, + GDK_SELECTION_TYPE_STRING, + selection_handle, NULL, NULL); + + gtk_widget_show (selection_button); + gtk_widget_show (window); + + gtk_main (); + + return 0; +} +</verb></tscreen> + + + +<sect>La glib<label id="sec_glib"> +<p> +La glib fornisce molte funzioni e definizioni utili pronte all'uso quando si +creano applicazioni GDK e GTK. Qui verranno elencate tutte, con una +breve spiegazione. Molte sono duplicati delle funzioni standard della libc, +e quindi per queste non si scenderà nei dettagli. Questa vuole essere una +lista di riferimento, in modo che si sappia cosa è possibile usare. + +<sect1>Definizioni +<p> +Le definizioni per gli estremi di molti dei tipi standard sono: + +<tscreen><verb> +G_MINFLOAT +G_MAXFLOAT +G_MINDOUBLE +G_MAXDOUBLE +G_MINSHORT +G_MAXSHORT +G_MININT +G_MAXINT +G_MINLONG +G_MAXLONG +</verb></tscreen> + +Ci sono anche le seguenti definizioni di tipo. Quelle rimaste non specificate +sono dipendenti dall'architettura. Si ricordi di evitare di fare affidamento +sulla dimensione di un puntatore se si vuole la portabilità! P.e., un puntatore +su un Alpha è lungo 8 byte, ma 4 su un Intel. + +<tscreen><verb> +char gchar; +short gshort; +long glong; +int gint; +char gboolean; + +unsigned char guchar; +unsigned short gushort; +unsigned long gulong; +unsigned int guint; + +float gfloat; +double gdouble; +long double gldouble; + +void* gpointer; + +gint8 +guint8 +gint16 +guint16 +gint32 +guint32 +</verb></tscreen> + +<sect1>Liste a doppio collegamento +<p> +le seguenti funzioni sono usate per creare, gestire e distruggere liste a +doppio collegamento. Si assume che il lettore sappia già cosa sono le liste +collegate, poiché descriverle è fuori dagli scopi di questo documento. +Naturalmente non è necessario conoscerle per l'uso generale di GTK, per +quanto conoscerle sia comunque interessante. + +<tscreen><verb> +GList* g_list_alloc (void); + +void g_list_free (GList *list); + +void g_list_free_1 (GList *list); + +GList* g_list_append (GList *list, + gpointer data); + +GList* g_list_prepend (GList *list, + gpointer data); + +GList* g_list_insert (GList *list, + gpointer data, + gint position); + +GList* g_list_remove (GList *list, + gpointer data); + +GList* g_list_remove_link (GList *list, + GList *link); + +GList* g_list_reverse (GList *list); + +GList* g_list_nth (GList *list, + gint n); + +GList* g_list_find (GList *list, + gpointer data); + +GList* g_list_last (GList *list); + +GList* g_list_first (GList *list); + +gint g_list_length (GList *list); + +void g_list_foreach (GList *list, + GFunc func, + gpointer user_data); +</verb></tscreen> + + +<sect1>Liste a collegamento singolo +<p> +Molte delle funzioni per le liste a collegamento singolo sono identiche alle +precedenti. Eccone una lista completa: +<tscreen><verb> +GSList* g_slist_alloc (void); + +void g_slist_free (GSList *list); + +void g_slist_free_1 (GSList *list); + +GSList* g_slist_append (GSList *list, + gpointer data); + +GSList* g_slist_prepend (GSList *list, + gpointer data); + +GSList* g_slist_insert (GSList *list, + gpointer data, + gint position); + +GSList* g_slist_remove (GSList *list, + gpointer data); + +GSList* g_slist_remove_link (GSList *list, + GSList *link); + +GSList* g_slist_reverse (GSList *list); + +GSList* g_slist_nth (GSList *list, + gint n); + +GSList* g_slist_find (GSList *list, + gpointer data); + +GSList* g_slist_last (GSList *list); + +gint g_slist_length (GSList *list); + +void g_slist_foreach (GSList *list, + GFunc func, + gpointer user_data); + +</verb></tscreen> + +<sect1>Gestione della memoria +<p> +<tscreen><verb> +gpointer g_malloc (gulong size); +</verb></tscreen> + +Questa è una sostituta di malloc(). Non occorre controllare il valore +restituito, in quanto lo fa già questa funzione. + +<tscreen><verb> +gpointer g_malloc0 (gulong size); +</verb></tscreen> + +Come la precedente, ma la memoria viene azzerata prima di restituire un +puntatore ad essa. + +<tscreen><verb> +gpointer g_realloc (gpointer mem, + gulong size); +</verb></tscreen> + +Riloca ``size'' byte di memoria che inizia a ``mem''. Ovviamente, la memoria +dovrebbe essere stata allocata precedentemente. + +<tscreen><verb> +void g_free (gpointer mem); +</verb></tscreen> + +Libera la memoria. Facile! + +<tscreen><verb> +void g_mem_profile (void); +</verb></tscreen> + +Emette un profilo della memoria usata, ma occorre ricompilare e reinstallare +la libreria aggiungendo #define MEM_PROFILE all'inizio del file glib/gmem.c. + +<tscreen><verb> +void g_mem_check (gpointer mem); +</verb></tscreen> + +Controlla che una locazione di memoria sia valida. Occorre ricompilare e +reinstallare la libreria aggiungendo #define MEM_CHECK all'inizio del file +gmem.c. + +<sect1>Timer +<p> +Funzioni legate ai timer... + +<tscreen><verb> +GTimer* g_timer_new (void); + +void g_timer_destroy (GTimer *timer); + +void g_timer_start (GTimer *timer); + +void g_timer_stop (GTimer *timer); + +void g_timer_reset (GTimer *timer); + +gdouble g_timer_elapsed (GTimer *timer, + gulong *microseconds); +</verb></tscreen> + +<sect1>Gestione delle stringhe +<p> +Un'accozzaglia di funzioni per la gestione delle stringhe. Sembrano tutte molto +interessanti, e probabilmente migliori per molte caratteristiche delle funzioni +standard del C per le stringhe, ma necessitano di documentazione. + +<tscreen><verb> +GString* g_string_new (gchar *init); +void g_string_free (GString *string, + gint free_segment); + +GString* g_string_assign (GString *lval, + gchar *rval); + +GString* g_string_truncate (GString *string, + gint len); + +GString* g_string_append (GString *string, + gchar *val); + +GString* g_string_append_c (GString *string, + gchar c); + +GString* g_string_prepend (GString *string, + gchar *val); + +GString* g_string_prepend_c (GString *string, + gchar c); + +void g_string_sprintf (GString *string, + gchar *fmt, + ...); + +void g_string_sprintfa (GString *string, + gchar *fmt, + ...); +</verb></tscreen> + +<sect1>Funzioni d'utilità e di errore +<p> +<tscreen><verb> +gchar* g_strdup (const gchar *str); +</verb></tscreen> + +Funzione sostitutiva della strdup. Copia i contenuti originari delle stringhe +in memoria appena allocata, restituendo un puntatore ad essa. + +<tscreen><verb> +gchar* g_strerror (gint errnum); +</verb></tscreen> +Si raccomanda di usare questa gunzione per tutti i messaggi di errore. E' molto +più graziosa, e più portabile di perror() o di altre. L'output di solito ha la +forma: + +<tscreen><verb> +nome programma:funzione fallita:file o altre descrizioni:strerror +</verb></tscreen> + +Di seguito un esempio di una chiamata di questo tipo usata nel nostro +programma Hello World: + +<tscreen><verb> +g_print("hello_world:open:%s:%s\n", filename, g_strerror(errno)); +</verb></tscreen> + +<tscreen><verb> +void g_error (gchar *format, ...); +</verb></tscreen> + +Visualizza un messaggio di errore. Il formato è come quello di printf, +ma prepone ``** ERROR **: '' al messaggio e termina il programma. Da usare solo +per errori gravi. + +<tscreen><verb> +void g_warning (gchar *format, ...); +</verb></tscreen> + +Come la precedente, ma prepone ``** WARNING **: '' e non termina il programma. + +<tscreen><verb> +void g_message (gchar *format, ...); +</verb></tscreen> + +Visualizza ``message: '' e poi il messaggio. + +<tscreen><verb> +void g_print (gchar *format, ...); +</verb></tscreen> + +Sostituta di printf(). + +L'ultima funzione: + +<tscreen><verb> +gchar* g_strsignal (gint signum); +</verb></tscreen> + +Visualizza il nome del messaggio del sistema Unix associato al numero di +segnale. Utile nelle funzioni generiche di gestione dei segnali. + +Tutte le funzioni elencate sono più o meno prese da glib.h. Se qualcuno volesse +documentare qualche funzione, mandi una email all'autore! + +<sect>I file rc di GTK +<p> +GTK ha un suo modo di trattare le preferenze delle applicazioni, usando +i file rc. Questi possono essere usati per scegliere i colori di quasi tutti +i widget, e possono anche essere usati per inserire delle pixmap nello sfondo +di alcuni widget. + +<sect1>Funzioni per i file rc +<p> +All'inizio della vostra applicazione dovrebbe esserci una chiamata a +<tscreen><verb> +void gtk_rc_parse (char *filename); +</verb></tscreen> +<p> +passando come parametro il nome del vostro file rc. Questo farà si che GTK +analizzi tale file e usi le impostazioni di stile per i tipi di widget ivi +definite. +<p> +Se si desidera avere un insieme speciale di widget che abbia uno stile diverso +dagli altri, o qualsiasi altra divisione logica dei widget, si chiami +<tscreen><verb> +void gtk_widget_set_name (GtkWidget *widget, + gchar *name); +</verb></tscreen> +<p> +passando un widget appena creato come primo argomento, e il nome che gli si +vuole dare come secondo. Questo consentirà di cambiare gli attributi di +questo widget per nome tramite il file rc. +<p> +Effettuando una chiamata come questa: + +<tscreen><verb> +button = gtk_button_new_with_label ("Special Button"); +gtk_widget_set_name (button, "special button"); +</verb></tscreen> +<p> +allora a questo bottone viene dato il nome ``special button'' ed esso può essere +riferito per nome nel file rc come ``special button.GtkButton''. [<--- Verificatemi!] +<p> +Il seguente esempio di file rc imposta le proprietà della finestra principale, +e fa si che tutti i figli di questa finestra ereditino lo stile descritto +dallo stile ``main button''. Il codice usato nell'applicazione è: + +<tscreen><verb> +window = gtk_window_new (GTK_WINDOW_TOPLEVEL); +gtk_widget_set_name (window, "main window"); +</verb></tscreen> +<p> +Lo stile viene definito nel file rc usando: + +<tscreen><verb> +widget "main window.*GtkButton*" style "main_button" +</verb></tscreen> +<p> +che assegna a tutti i widget GtkButton nella finestra principale lo stile +``main_buttons'' secondo la definizione data nel file rc. +<p> +Come si può vedere, questo sistema è molto potente e flessibile. Usate la +vostra immaginazione per trarre il massimo vantaggio da esso. + +<sect1>Il formato dei file rc di GTK +<p> +Nell'esempio che segue viene illustrato il formato del file GTK. Si tratta +del file testgkrc dalla distribuzione del GTK, a cui sono stati aggiunti +vari commenti e varie cose. Potete includere questa spiegazione nella +vostra applicazione per consentire all'utente di personalizzarla finemente. +<p> +There are several directives to change the attributes of a widget. +Ci sono diverse direttive per cambiare gli attributi di un widget. +<itemize> +<item>fg - Assegna il colore di primo piano di un widget. +<item>bg - Assegna il colore di sfondo di un widget. +<item>bg_pixmap - Inserisce nello sfondo di un widget una pixmap. +<item>font - Sceglie il font da usarsi con il dato widget. +</itemize> +<p> +Inoltre ci sono diversi stati in cui può trovarsi un widget, e si possono +assegnare diversi colori, pixmap e font per ogni stato. Essi sono: +<itemize> +<item>NORMAL - Lo stato normale di un widget, quando il mouse non si trova su +di esso, quando non è premuto, ecc. +<item>PRELIGHT (evidenziato)- Quando il mouse si trova sopra al widget +verranno usati i colori assegnati per questo stato. +<item>ACTIVE (attivo) - Quando il widget è premuto o cliccato esso sarà attivo, +e verranno usati gli attributi assegnati da questa etichetta. +<item>INSENSITIVE (insensibile)- Quando un widget viene reso insensibile, +e non può essere attivato, prenderà questi attributi. +<item>SELECTED (selezionato) - Quando un oggetto viene selezionato, prende +questi attributi. +</itemize> +<p> +Quando si usano le parole chiave ``fg'' e ``bg'' per assegnare i colori dei +widget il formato è: +<tscreen><verb> +fg[<STATE>] = { Rosso, Verde, Blu } +</verb></tscreen> +<p> +Dove STATE è uno degli stati visti prima (PRELIGHT, ACTIVE ecc.), e Rosso, +Verde e Blu sono valori nell'intervallo 0 - 1.0; { 1.0, 1.0, 1.0 } rappresenta +il bianco. +Devono essere in formato float, o verranno visti come 0, sicché un ``1'' diretto +non funziona, deve essere ``1.0''. Uno ``0'' diretto va invece bene, poiché poco +importa se non viene riconosciuto: valori non riconosciuti vengono considerati +0. +<p> +bg_pixmap è molto simile al precedente, tranne per i colori che vengono +sostituiti dal nome di un file. + +pixmap_path è una lista di percorsi separati da ``:''. In questi percorsi vengono +cercate le pixmap specificate. +<p> +La direttiva font è semplicemente: +<tscreen><verb> +font = "<font name>" +</verb></tscreen> +<p> +dove l'unica parte complicata è immaginare la stringa del font. Allo scopo +può servire usare xfontsel o una utilità analoga. +<p> +``widget_class'' assegna lo stile di una classe di widget. Queste classi sono +elencate nell'introduzione ai widget sulla gerarchia delle classi. +<p> +La direttiva ``widget'' assegna un insieme di widget dal nome specificato ad +un dato stile, annullando qualsiasi stile assegnato per la data classe di widget. +Questi widget vengono registrati nell'applicazione usando la chiamata +gtk_widget_set_name(). Questo consente di specificare gli attributi di un +widget singlarmente, piuttosto che assegnando gli attributi di un'intera classe +di widget. E' opportuno documentare tutti questi widget speciali in modo che +gli utenti possano personalizzarli. +<p> +Quando la parola chiave ``<tt>parent</>'' viene usata come un attributo, il +widget erediterà gli attributi del suo genitore nell'applicazione. +<p> +Quando si definisce uno stile si possono assegnare gli attributi di uno +stile definito precedentemente a quello nuovo. +<tscreen><verb> +style "main_button" = "button" +{ + font = "-adobe-helvetica-medium-r-normal--*-100-*-*-*-*-*-*" + bg[PRELIGHT] = { 0.75, 0, 0 } +} +</verb></tscreen> +<p> +Questo esempio prende lo stile ``button'' e crea un nuovo stile +semplicemente cambiando il font e il colore di sfondo dello stato ``prelight'' +nello stile ``button''. +<p> +Naturalmente, molti di questi attributi non sono applicabili a tutti i widget. +E' veramente un semplice problema di buon senso. Tutto quello che potrebbe +applicarsi, dovrebbe. + +<sect1>Esempio di file rc +<p> + +<tscreen><verb> +# pixmap_path "<dir 1>:<dir 2>:<dir 3>:..." +# +pixmap_path "/usr/include/X11R6/pixmaps:/home/imain/pixmaps" +# +# style <name> [= <name>] +# { +# <option> +# } +# +# widget <widget_set> style <style_name> +# widget_class <widget_class_set> style <style_name> + + +# Ecco una lista di tutti gli stati possibili. Si noti che alcuni non sono +# applicabili a certi widget. +# +# NORMAL - Lo stato normale di un widget, quando il mouse non si trova su +# di esso, quando non è premuto, ecc. +# +# PRELIGHT (evidenziato)- Quando il mouse si trova sopra al widget +# verranno usati i colori assegnati per questo stato. +# +# ACTIVE (attivo) - Quando il widget è premuto o cliccato esso sarà attivo, +# e verranno usati gli attributi assegnati da questa etichetta. +# +# INSENSITIVE (insensibile)- Quando un widget viene reso insensibile, +# e non può essere attivato, prenderà questi attributi. +# +# SELECTED (selezionato) - Quando un oggetto viene selezionato, prende +# questi attributi. +# +# Dati questi stati, è possibile assegnare gli attributi dei widget in +# ognuno di questi stati usando le seguenti direttive. +# +# fg - Assegna il colore di primo piano di un widget. +# bg - Assegna il colore di sfondo di un widget. +# bg_pixmap - Inserisce nello sfondo di un widget una pixmap. +# font - Sceglie il font da usarsi con il dato widget. +# + +# Questo è uno stile chiamato "button". Il nome non è veramente importante, +# in quanto viene assegnato ai veri widget alla fine del file. + +style "window" +{ + # Questo inserisce nella spaziatura attorno alla finestra la pixmap + # specificata. + #bg_pixmap[<STATE>] = "<pixmap filename>" + bg_pixmap[NORMAL] = "warning.xpm" +} + +style "scale" +{ + # Mette il colore di primo piano (il colore del font) a rosso nello + # stato "NORMAL". + + fg[NORMAL] = { 1.0, 0, 0 } + + # Inserisce nello sfondo del gadget la stessa pixmap usata dal suo genitore. + bg_pixmap[NORMAL] = "<parent>" +} + +style "button" +{ + # Questo mostra tutti i possibili stati per un bottone. L'unico che + # non è applicabile è lo stato "SELECTED". + + fg[PRELIGHT] = { 0, 1.0, 1.0 } + bg[PRELIGHT] = { 0, 0, 1.0 } + bg[ACTIVE] = { 1.0, 0, 0 } + fg[ACTIVE] = { 0, 1.0, 0 } + bg[NORMAL] = { 1.0, 1.0, 0 } + fg[NORMAL] = { .99, 0, .99 } + bg[INSENSITIVE] = { 1.0, 1.0, 1.0 } + fg[INSENSITIVE] = { 1.0, 0, 1.0 } +} + +# In questi esempio ereditiamo gli attributi dello stile "button" e poi +# alteriamo il font e il colore di sfondo quando evidenziato per creare +# un nuovo stile "main_button". + +style "main_button" = "button" +{ + font = "-adobe-helvetica-medium-r-normal--*-100-*-*-*-*-*-*" + bg[PRELIGHT] = { 0.75, 0, 0 } +} + +style "toggle_button" = "button" +{ + fg[NORMAL] = { 1.0, 0, 0 } + fg[ACTIVE] = { 1.0, 0, 0 } + + # Questo seleziona come pixmap di sfondo per il toggle_button quella del + # suo widget genitore (definita nell'applicazione). + bg_pixmap[NORMAL] = "<parent>" +} + +style "text" +{ + bg_pixmap[NORMAL] = "marble.xpm" + fg[NORMAL] = { 1.0, 1.0, 1.0 } +} + +style "ruler" +{ + font = "-adobe-helvetica-medium-r-normal--*-80-*-*-*-*-*-*" +} + +# pixmap_path "~/.pixmaps" + +# Queste assegnano ai tipi di widget gli stili definiti prima. +# I tipi di widget sono elencati nella gerarchia delle classi, ma probabilmente +# dovrebbero essere elencati in questo documento come riferimento per l'utente. + +widget_class "GtkWindow" style "window" +widget_class "GtkDialog" style "window" +widget_class "GtkFileSelection" style "window" +widget_class "*Gtk*Scale" style "scale" +widget_class "*GtkCheckButton*" style "toggle_button" +widget_class "*GtkRadioButton*" style "toggle_button" +widget_class "*GtkButton*" style "button" +widget_class "*Ruler" style "ruler" +widget_class "*GtkText" style "text" + +# Questo assegna lo stile main_button a tutti i bottoni che sono figli della +# "main window" (finestra principale). Questi devono essere documenati per +# potersene avvantaggiare. +widget "main window.*GtkButton*" style "main_button" +</verb></tscreen> + + + +<sect>Scrivere un proprio Widget + +<p> +<sect1> Panoramica +<p> +Anche se la distribuzione GTK contiene molto tipi di widget che possono +coprire molte necessità basilari, può essere necessario costruirsi +un proprio widget. GTK usa molto l'ereditarietà tra i vari +widget e, di solito, vi è un widget che si avvicina a quello che ti +servirebbe, ed è spesso possibile creare un nuovo widget con poche linee +di codice. Ma prima di iniziare il lavoro su un nuovo widget, vediamo +se qualcuno non lo ha già creato. Questo eviterà un duplicazione +di lavoro e farà sì che i widget non-GTK puri siano minimi, così da +aiutare sia chi crea il codice che chi l'interfaccia per applicazioni GTK +molto grosse. D'altra parte, quando hai finito di scrivere un widget, +annuncialo a tutto il mondo così che le altre persone ne possano +beneficiare. Il miglioro modo dove farlo è la <tt>gtk-list</tt>. + +<sect1> L'anatomia di un widget + +<p> +Per creare un nuovo widget è importante aver capito come gli ogetti +di GTK lavorano. Questa sezione è solo una breve spiegazione. Guarda la +documentazione di riferimento per maggiori dettagli. + +<p> +I widget GTK sono implementati in un modo orientato agli oggetti, +anche se usando il C standard. Questo aumenta notevolmente la portabilità +e la stabilità, specialmente per le correnti generazioni di compilatori C++; +comunque questo significa che chi scrive un widget deve fare attenzione +ad alcuni dettagli di implementazione. L'informazione comune a tutte le +istanze di una classe di widget (ad esempio: a tutti i bottoni) è memorizzata +<em>class structure</em>. C'e' solamente una copia di questo in cui +sono memorizzate le informazioni riguardanti i segnali della classe +(assomiglia ad una funzione virtuale in C). Per supportare l'ereditarietà +il primo campo della struttura di una classe deve essere una copia della +struttura della classe genitore. La dichiarazione della struttura della +classe GtkButton è: + +<tscreen><verb> +struct _GtkButtonClass +{ + GtkContainerClass parent_class; + + void (* pressed) (GtkButton *button); + void (* released) (GtkButton *button); + void (* clicked) (GtkButton *button); + void (* enter) (GtkButton *button); + void (* leave) (GtkButton *button); +}; +</verb></tscreen> + +<p> +Quando un bottone viene trattato come un contenitore (ad esempio quando viene +ridimensionato) si può fare il cast della struttura della sua classe con la +GtkContainerClass, e usare i campi rilevanti per gestire i segnali. + +<p> +C'è anche una struttura per ogni widget che viene creata +ad ogni istanza. Questa struttura ha campi per +memorizzare le informazioni che sono differenti per ogni volta che il widget +viene istanziato. Chiameremo questa struttura la <em> struttura +oggetto</em>. Per la classe Bottone, questa ha l'aspetto: + +<tscreen><verb> +struct _GtkButton +{ + GtkContainer container; + + GtkWidget *child; + + guint in_button : 1; + guint button_down : 1; +}; +</verb></tscreen> + +<p> +Si noti che, similmente alla struttura della classe, il primo campo +è la struttura dell'oggetto della classe madre, così che, se necessario, si può fare il +cast di questa struttura con quella dell'oggetto della classe madre. + +<sect1> Creare un Widget composto + +<sect2> Introduzione + +<p> +Un tipo di widget a cui potreste essere interessati è un widget che +è semplicemnte un aggregato di altri widget GTK. Questo tipo di +widget non fa nulla che non possa essere fatto creando un nuovo +widget, ma fornisce un modo conveniente per inscatolare elementi +dell'interfaccia utente per poi riutilizzarli. +I widget FileSelection e ColorSelection della ditribuzione standard +sono esempi di questo tipo di widget. + +<p> +Il widget di esempio che creeremo in questo capitolo è il +Tictactoe, un vettore 3x3 di bottoni a commutazione il quale emette +un segnale quando tutti e 3 i bottoni di una riga, colonna o di una +diagonale sono premuti. + +<sect2> Scegliere la classe madre + +<p> +La classe madre per un widget composto e' tipicamente la classe +contenitrice che racchiude tutti gli elementi del widget composto. +Per esempio, la classe madre del widget FileSelection è la classe +Dialog. Visto che i nostri bottoni sono inseriti in una tabella, è +naturale pensare che la nostra classe madre possa essere la GtkTable. +Sfortunatamente, così non è. La creazione di un widget è diviso +tra 2 funzioni : la funzione <tt/WIDGETNAME_new()/ che viene invocata +dall'utente, e la funzione <tt/WIDGETNAME_init()/ che ha il compito +principale di inizializzare il widget che è indipendente dai valori +passati alla funzione <tt/_new()/. Widget figli o discendenti possono +chiamare, solamente, la funzione del loro widget genitore. +Ma questa divisione del lavoro non funziona bene per la tabella, la +quale, quando creata, necessita di conoscere il numero di righe e +colonne che la comporrà. A meno che non vogliamo duplicare molte delle +fuinzionalità della <tt/gtk_table_new()/ nel nostro widget +Tictactoe, faremmo meglio a evitare di derivarlo dalla GtkTable. Per questa +ragione lo deriviamo invece da GtkVBox, e uniamo la nostra tabella +dentro il VBox. + +<sect2> Il File Header + +<p> +Ogni classe di widget ha un file header il quale dichiara l'oggetto e la +struttura della classe del widget, comprese le funzioni pubbliche. +Per prevenire duplicati di definizioni, noi includiamo l'intero file header fra: + +<tscreen><verb> +#ifndef __TICTACTOE_H__ +#define __TICTACTOE_H__ +. +. +. +#endif /* __TICTACTOE_H__ */ +</verb></tscreen> + +E per far felici i programmi in C++ che includono il nostro file header, in: + +<tscreen><verb> +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ +. +. +. +#ifdef __cplusplus +} +#endif /* __cplusplus */ +</verb></tscreen> + +Insieme alle funzioni e alle strutture, dichiariamo tre macro +standard nel nostro file header, <tt/TICTACTOE(obj)/, +<tt/TICTACTOE_CLASS(klass)/, e <tt/IS_TICTACTOE(obj)/, i quali rispettivamente +fanno il cast di un puntatore ad un puntatore ad un ogetto od ad una struttura +di classe, e guarda se un oggetto è un widget Tictactoe. + + +Qui vi è il file header completo: + +<tscreen><verb> + +#ifndef __TICTACTOE_H__ +#define __TICTACTOE_H__ + +#include <gdk/gdk.h> +#include <gtk/gtkvbox.h> + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#define TICTACTOE(obj) GTK_CHECK_CAST (obj, tictactoe_get_type (), Tictactoe) +#define TICTACTOE_CLASS(klass) GTK_CHECK_CLASS_CAST (klass, tictactoe_get_type (), TictactoeClass) +#define IS_TICTACTOE(obj) GTK_CHECK_TYPE (obj, tictactoe_get_type ()) + + +typedef struct _Tictactoe Tictactoe; +typedef struct _TictactoeClass TictactoeClass; + +struct _Tictactoe +{ + GtkVBox vbox; + + GtkWidget *buttons[3][3]; +}; + +struct _TictactoeClass +{ + GtkVBoxClass parent_class; + + void (* tictactoe) (Tictactoe *ttt); +}; + +guint tictactoe_get_type (void); +GtkWidget* tictactoe_new (void); +void tictactoe_clear (Tictactoe *ttt); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __TICTACTOE_H__ */ + +</verb></tscreen> + +<sect2> La funzione <tt/_get_type()/ + +<p> +Continuiamo ora con l'implementazione del nostro widget. Una funzione +basilare di ogni widget è la funzione <tt/WIDGETNAME_get_type()/. +Questa funzione, quando chiamata la prima volta, comunica a GTK la classe +del widget, e ottiene un identificativo univoco per la classe del +widget. Chiamate successive restituiscono semplicemente l'identificativo. + +<tscreen><verb> +guint +tictactoe_get_type () +{ + static guint ttt_type = 0; + + if (!ttt_type) + { + GtkTypeInfo ttt_info = + { + "Tictactoe", + sizeof (Tictactoe), + sizeof (TictactoeClass), + (GtkClassInitFunc) tictactoe_class_init, + (GtkObjectInitFunc) tictactoe_init, + (GtkArgFunc) NULL, + }; + + ttt_type = gtk_type_unique (gtk_vbox_get_type (), &ttt_info); + } + + return ttt_type; +} +</verb></tscreen> + +<p> +La struttura GtkTypeInfo ha la seguente definizione: + +<tscreen><verb> +struct _GtkTypeInfo +{ + gchar *type_name; + guint object_size; + guint class_size; + GtkClassInitFunc class_init_func; + GtkObjectInitFunc object_init_func; + GtkArgFunc arg_func; +}; +</verb></tscreen> + +<p> +I campi di questa struttura sono abbastanza auto-esplicativi. +Ignoreremo, per ora, il campo <tt/arg_func/: ha un ruolo importante, ma +non ancora largamente implementato, nel permettere ai linguaggi interpretati +di settare convenientemente le opzioni del widget. +Una volta che il GTK ha completato correttamente una copia di questa +struttura, sa come creare un oggetto di un particolare widget. + +<sect2> La funzione <tt/_class_init()/ +<p> +La funzione <tt/WIDGETNAME_class_init()/ inizialiazza i campi della +struttura della classe del widget, e setta ogni segnale della classe. +Per il nostro widget Tictactoe ha il seguente aspetto: + +<tscreen><verb> + +enum { + TICTACTOE_SIGNAL, + LAST_SIGNAL +}; + +static gint tictactoe_signals[LAST_SIGNAL] = { 0 }; + +static void +tictactoe_class_init (TictactoeClass *class) +{ + GtkObjectClass *object_class; + + object_class = (GtkObjectClass*) class; + + tictactoe_signals[TICTACTOE_SIGNAL] = gtk_signal_new ("tictactoe", + GTK_RUN_FIRST, + object_class->type, + GTK_SIGNAL_OFFSET (TictactoeClass, tictactoe), + gtk_signal_default_marshaller, GTK_ARG_NONE, 0); + + + gtk_object_class_add_signals (object_class, tictactoe_signals, LAST_SIGNAL); + + class->tictactoe = NULL; +} +</verb></tscreen> + +<p> +Il nostro widget ha semplicemente il segnale ``tictactoe'' che è +invocato quando una riga, colonna o diagonale è completamente premuta. +Non tutti i widget composti necessitano di segnali, quindi se stai +leggendo questo per la prima volta, puoi anche saltare alla prossima sezione, +dal momento che a questo punto le cose diventano un po' complicate. + +La funzione: +<tscreen><verb> +gint gtk_signal_new (gchar *name, + GtkSignalRunType run_type, + gint object_type, + gint function_offset, + GtkSignalMarshaller marshaller, + GtkArgType return_val, + gint nparams, + ...); +</verb></tscreen> + +crea un nuovo segnale. I parametri sono: + +<itemize> +<item> <tt/name/: Il nome del segnale. +<item> <tt/run_type/: Se il segstore predefinito viene eseguito prima o dopo +di quello dell'utente. Di norma questo sarà <tt/GTK_RUN_FIRST/, o <tt/GTK_RUN_LAST/, +anche se ci sono altre possibilità. +<item> <tt/object_type/: l'identificativo dell'oggetto a cui questo segnale si +riferisce. Esso sarà anche applicato agli oggetti discendenti. +<item> <tt/function_offset/: L'offset nella struttura della classe di un +puntatore al gestore predefinito. +<item> <tt/marshaller/: una funzione che è usata per invocare il gestore +del segnale. Per gestori di segnali che non hanno argomenti oltre +all'oggetto che emette il segnale e i dati dell'utente, possiamo usare +la funzione predefinita <tt/gtk_signal_default_marshaller/ +<item> <tt/return_val/: Il tipo del valore di ritorno. +<item> <tt/nparams/: Il numero di parametri del gestore di segnali (oltre +ai due predefiniti menzionati sopra) +<item> <tt/.../: i tipi dei parametri +</itemize> + +Quando si specificano i tipi, si usa l'enumerazione <tt/GtkArgType/: + +<tscreen><verb> +typedef enum +{ + GTK_ARG_INVALID, + GTK_ARG_NONE, + GTK_ARG_CHAR, + GTK_ARG_SHORT, + GTK_ARG_INT, + GTK_ARG_LONG, + GTK_ARG_POINTER, + GTK_ARG_OBJECT, + GTK_ARG_FUNCTION, + GTK_ARG_SIGNAL +} GtkArgType; +</verb></tscreen> + +<p> +<tt/gtk_signal_new()/ restituisce un identificatore unico intero per il segnale, +che memorizziamo nel vettore <tt/tictactoe_signals/, che +indicizzeremo usando una enumerazione. (Convenzionalmente, gli elementi dell'enumerazione +sono i nomi dei segnali, in maiuscolo, +ma qui ci potrebbe essere un conflitto con la macro <tt/TICTACTOE()/, +quindi l'abbiamo chiamato <tt/TICTACTOE_SIGNAL/ + +Dopo aver creato un nostro segnale, abbiamo bisogno di dire a GTK +di associare il nostro segnale alla classe Tictactoe. Lo facciamo +invocando <tt/gtk_object_class_add_signals()/. Settiamo quindi a NULL +il puntatore che punta al gestore predefinito per il segnale +``tictactoe'' a NULL, indicando che non ci sono azioni predefinite. + +<sect2> La funzione <tt/_init()/ + +<p> + +Ogni classe di Widget necessita anche di una funzione per inizializzare +la struttura dell'oggetto. Usualmente questa funzione ha il ruolo abbastanza +limitato di assegnare ai campi della struttura i valori predefiniti. +Per widget composti, comunque, questa funzione crea, anche, +i widget componenti del widget composto. + +<tscreen><verb> + +static void +tictactoe_init (Tictactoe *ttt) +{ + GtkWidget *table; + gint i,j; + + table = gtk_table_new (3, 3, TRUE); + gtk_container_add (GTK_CONTAINER(ttt), table); + gtk_widget_show (table); + + for (i=0;i<3; i++) + for (j=0;j<3; j++) + { + ttt->buttons[i][j] = gtk_toggle_button_new (); + gtk_table_attach_defaults (GTK_TABLE(table), ttt->buttons[i][j], + i, i+1, j, j+1); + gtk_signal_connect (GTK_OBJECT (ttt->buttons[i][j]), "toggled", + GTK_SIGNAL_FUNC (tictactoe_toggle), ttt); + gtk_widget_set_usize (ttt->buttons[i][j], 20, 20); + gtk_widget_show (ttt->buttons[i][j]); + } +} +</verb></tscreen> + +<sect2> E il resto... + +<p> + +C'è un'altra funzione che ogni widget (eccetto i Widget di base come +GtkBin che non possono essere instanziati) deve avere : la funzione +che l'utente invoca per creare un oggetto di quel tipo. Questa è +convenzionalmente chiamata <tt/WIDGETNAME_new()/. In alcuni widget, +non nel caso del nostro Tictactoe, questa funzione richiede degli +argomenti, e fa alcune operazioni basandosi su di essi. Le altre +due funzioni sono specifiche del widget Tictactoe. + +<p> +<tt/tictactoe_clear()/ è una funzione pubblica che resetta tutti i +bottoni, nel widget, allo stato iniziale (non premuto). Notate l'uso +di <tt/gtk_signal_handler_block_by_data()/ per impedire che il nostro +gestore dei segnali venga attivato quando non ce n'è bisogno. + +<p> +<tt/tictactoe_toggle()/ è il gestore del segnale che viene invocato +quando l'utente preme il bottone. Esso guarda se vi è +qualche combinazione vincente che coinvolge i bottoni premuti, e nel +caso ci fosse, emette il segnale ``tictactoe''. + +<tscreen><verb> +GtkWidget* +tictactoe_new () +{ + return GTK_WIDGET ( gtk_type_new (tictactoe_get_type ())); +} + +void +tictactoe_clear (Tictactoe *ttt) +{ + int i,j; + + for (i=0;i<3;i++) + for (j=0;j<3;j++) + { + gtk_signal_handler_block_by_data (GTK_OBJECT(ttt->buttons[i][j]), ttt); + gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON (ttt->buttons[i][j]), + FALSE); + gtk_signal_handler_unblock_by_data (GTK_OBJECT(ttt->buttons[i][j]), ttt); + } +} + +static void +tictactoe_toggle (GtkWidget *widget, Tictactoe *ttt) +{ + int i,k; + + static int rwins[8][3] = { { 0, 0, 0 }, { 1, 1, 1 }, { 2, 2, 2 }, + { 0, 1, 2 }, { 0, 1, 2 }, { 0, 1, 2 }, + { 0, 1, 2 }, { 0, 1, 2 } }; + static int cwins[8][3] = { { 0, 1, 2 }, { 0, 1, 2 }, { 0, 1, 2 }, + { 0, 0, 0 }, { 1, 1, 1 }, { 2, 2, 2 }, + { 0, 1, 2 }, { 2, 1, 0 } }; + + int success, found; + + for (k=0; k<8; k++) + { + success = TRUE; + found = FALSE; + + for (i=0;i<3;i++) + { + success = success && + GTK_TOGGLE_BUTTON(ttt->buttons[rwins[k][i]][cwins[k][i]])->active; + found = found || + ttt->buttons[rwins[k][i]][cwins[k][i]] == widget; + } + + if (success && found) + { + gtk_signal_emit (GTK_OBJECT (ttt), + tictactoe_signals[TICTACTOE_SIGNAL]); + break; + } + } +} +</verb></tscreen> + +<p> + +E finalmente un programma di esempio che usa il nostro widget +Tictactoe: + +<tscreen><verb> +#include <gtk/gtk.h> +#include "tictactoe.h" + +/* Invocato quando una riga, colonna o diagonale e' completata. */ +void +win (GtkWidget *widget, gpointer data) +{ + g_print ("Yay!\n"); + tictactoe_clear (TICTACTOE (widget)); +} + +int +main (int argc, char *argv[]) +{ + GtkWidget *window; + GtkWidget *ttt; + + gtk_init (&argc, &argv); + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + + gtk_window_set_title (GTK_WINDOW (window), "Aspect Frame"); + + gtk_signal_connect (GTK_OBJECT (window), "destroy", + GTK_SIGNAL_FUNC (gtk_exit), NULL); + + gtk_container_border_width (GTK_CONTAINER (window), 10); + + /* Crea un nuovo widget Tictactoe. */ + ttt = tictactoe_new (); + gtk_container_add (GTK_CONTAINER (window), ttt); + gtk_widget_show (ttt); + + /* E gli aggancia il segnale "tictactoe" */ + gtk_signal_connect (GTK_OBJECT (ttt), "tictactoe", + GTK_SIGNAL_FUNC (win), NULL); + + gtk_widget_show (window); + + gtk_main (); + + return 0; +} + +</verb></tscreen> + + +<sect1> Creare un widget a partire da zero + +<sect2> Introduzione + +<p> + +In questa sezione impareremo meglio come i widget si mostrano sullo schermo +e interagiscono con gli eventi. Come esempio, creeremo +un widget di quadrante analogico con un puntatore che l'utente +può trascinare per assegnare il valore. + +<sect2> Mostrare un widget sullo schermo + +<p> +Ci sono alcuni passi che sono necessari nella visualizzazione sullo +schermo. Dopo che il widget è stato creato con una chiamata a +<tt/WIDGETNAME_new()/, sono necessarie alcune altre funzioni: + +<itemize> +<item> <tt/WIDGETNAME_realize()/ è responsabile della creazione di +una finestra X per il widget se ne ha una. +<item> <tt/WIDGETNAME_map()/ è invocata dopo che l'utente ha +chiamato <tt/gtk_widget_show()/. E' responsabile di vedere se il +widget è attualmente disegnato sullo schermo (<em/mappato/). Per +una classe contenitore, essa deve anche creare chiamate alle +funzioni <tt/map()/> per ogni widget figlio. +<item> <tt/WIDGETNAME_draw()/ è invocata quando +<tt/gtk_widget_draw()/ viene chiamata per il widget o per uno dei suoi +predecessori. Esso fa sì che l'attuale chiamata alla +funzione di disegno del widget disegni il widget sullo schermo. +Per la classe contenitore, questa funzione deve eseguire le +chiamate alla funzioni <tt/gtk_widget_draw()/ di ogni suo widget +figlio. +<item> <tt/WIDGETNAME_expose()/ è un gestore per l'evento di esposizione +per il widget. Esso crea le chiamate necessarie alle funzioni di disegno +per disegnare la porzione che si è resa visibile. Per le classi +contenitore, questa funzione deve generare gli eventi di ``expose'' per +tutti i widget figli che non hanno una propria finestra (se essi hanno +una loro finestra, sarà X che genererà i necessari eventi di expose). +</itemize> + +<p> +Potete notare che le ultime due funzioni sono molto simili, ognuna è +responsabile per il disegno del widget sullo schermo. Infatti molti +tipi di widget non sanno relamente la differenza tra le due. +La funzione di predefinita <tt/draw()/ nella classe widget, semplicemente +genera un sintetico evento di ``expose'' per l'area da ridisegnare. +Comunque, alcuni tipi di widget possono risparmiare tempo distinguendo +le due funzioni. Per esempio, se un widget ha piu' finestre X, allora +visto che l'evento ``expose'' identifica solo la finestra esposta, +esso può ridisegnare solo la finestra interessata, cosa che non è +possibile per chiamate a <tt/draw()/. + +<p> +I widget contenitori, anche se essi non farebbero differenze, +non possono semplicemente usare la funzione <tt/draw()/ perchè per i +loro widget figli la differenza potrebbere essere importante. Comunque, +sarebbe uno spreco duplicare il codice di disegno nelle due +funzioni. La convenzione è che questi widget abbiano una funzione +chiamata <tt/WIDGETNAME_paint()/ che disegna il widget, che è poi +chiamata dalle funzioni <tt/draw()/ e <tt/expose()/ + +<p> +Nell'approccio del nostro esempio, visto che il widget, ha +una sola finestra, possiamo utilizzare il modo piu' semplice +ed usare la funzione predefinita <tt/draw()/ e implementare +solamente la funzione <tt/expose()/. + +<sect2> Le origini del widget Dial + +<p> +Come tutti gli animali terresti sono semplicemente varianti del primo +amfibio, i widget Gtk tendono ad essere varianti di altri widget, precedentemente +scritti. Così, anche se questa sezione è intitolata ``Creare +un widget a partire da zero", il nostro widget inizia in realtà con il codice +sorgente del widget Range. Questo è stato preso come punto d'inizio +perche' sarebbe carino se il nostro widget avesse la +stessa interfaccia del widget Scale il quale è semplicemente una +specializzazione del widget Range. Così, sebbene il codice sorgente e' +presentato sotto in forma definitiva, non si deve pensare che sia stato +scritto <em>deus ex machina</em> in questo modo. Se poi non avete familiarità +con il funzionamento del widget Scale dal punto di vista di chi scrive +un'applicazione, potrebbe essere una buona idea guardare indietro prima +di continuare. + +<sect2> Le basi + +<p> +Una parte del nostro widget potrebbe essere simile +al widget Tictactoe. In primo luogo, abbiamo il file header: + +<tscreen><verb> +/* GTK - The GIMP Toolkit + * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free + * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef __GTK_DIAL_H__ +#define __GTK_DIAL_H__ + +#include <gdk/gdk.h> +#include <gtk/gtkadjustment.h> +#include <gtk/gtkwidget.h> + + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +#define GTK_DIAL(obj) GTK_CHECK_CAST (obj, gtk_dial_get_type (), GtkDial) +#define GTK_DIAL_CLASS(klass) GTK_CHECK_CLASS_CAST (klass, gtk_dial_get_type (), GtkDialClass) +#define GTK_IS_DIAL(obj) GTK_CHECK_TYPE (obj, gtk_dial_get_type ()) + + +typedef struct _GtkDial GtkDial; +typedef struct _GtkDialClass GtkDialClass; + +struct _GtkDial +{ + GtkWidget widget; + + /* Politica di update (GTK_UPDATE_[CONTINUOUS/DELAYED/DISCONTINUOUS]) */ + guint policy : 2; + + /* Bottone correntemente premuto o 0 altrimenti */ + guint8 button; + + /* Dimensione della componente Dial. */ + gint radius; + gint pointer_width; + + /* ID del timer di update, o 0 altrimenti */ + guint32 timer; + + /* Angolo corrente. */ + gfloat angle; + + /* Vecchi valori dell'aggiustamento così sappiamo quando + * qualcosa cambia */ + gfloat old_value; + gfloat old_lower; + gfloat old_upper; + + /* L'oggetto adjustament che memorizza i dati per questo dial */ + GtkAdjustment *adjustment; +}; + +struct _GtkDialClass +{ + GtkWidgetClass parent_class; +}; + + +GtkWidget* gtk_dial_new (GtkAdjustment *adjustment); +guint gtk_dial_get_type (void); +GtkAdjustment* gtk_dial_get_adjustment (GtkDial *dial); +void gtk_dial_set_update_policy (GtkDial *dial, + GtkUpdateType policy); + +void gtk_dial_set_adjustment (GtkDial *dial, + GtkAdjustment *adjustment); +#ifdef __cplusplus +} +#endif /* __cplusplus */ + + +#endif /* __GTK_DIAL_H__ */ + +</verb></tscreen> + +Essendoci più cose da fare con questo widget, rispetto al precedente, +abbiamo più cambi nella struttura dati, ma le altre cose sono +abbastamza simili. + +<p> + +Dopo aver incluso i file di header e aver dichiarato alcune costanti, +dobbiamo fornire alcune funzioni circa il widget e la sua +inizializzazione. + +<tscreen><verb> +#include <math.h> +#include <stdio.h> +#include <gtk/gtkmain.h> +#include <gtk/gtksignal.h> + +#include "gtkdial.h" + +#define SCROLL_DELAY_LENGTH 300 +#define DIAL_DEFAULT_SIZE 100 + +/* Dichiarazioni di funzioni successive */ + +[ omesse per salvare spazio ] + +/* variabili locali. */ + +static GtkWidgetClass *parent_class = NULL; + +guint +gtk_dial_get_type () +{ + static guint dial_type = 0; + + if (!dial_type) + { + GtkTypeInfo dial_info = + { + "GtkDial", + sizeof (GtkDial), + sizeof (GtkDialClass), + (GtkClassInitFunc) gtk_dial_class_init, + (GtkObjectInitFunc) gtk_dial_init, + (GtkArgFunc) NULL, + }; + + dial_type = gtk_type_unique (gtk_widget_get_type (), &dial_info); + } + + return dial_type; +} + +static void +gtk_dial_class_init (GtkDialClass *class) +{ + GtkObjectClass *object_class; + GtkWidgetClass *widget_class; + + object_class = (GtkObjectClass*) class; + widget_class = (GtkWidgetClass*) class; + + parent_class = gtk_type_class (gtk_widget_get_type ()); + + object_class->destroy = gtk_dial_destroy; + + widget_class->realize = gtk_dial_realize; + widget_class->expose_event = gtk_dial_expose; + widget_class->size_request = gtk_dial_size_request; + widget_class->size_allocate = gtk_dial_size_allocate; + widget_class->button_press_event = gtk_dial_button_press; + widget_class->button_release_event = gtk_dial_button_release; + widget_class->motion_notify_event = gtk_dial_motion_notify; +} + +static void +gtk_dial_init (GtkDial *dial) +{ + dial->button = 0; + dial->policy = GTK_UPDATE_CONTINUOUS; + dial->timer = 0; + dial->radius = 0; + dial->pointer_width = 0; + dial->angle = 0.0; + dial->old_value = 0.0; + dial->old_lower = 0.0; + dial->old_upper = 0.0; + dial->adjustment = NULL; +} + +GtkWidget* +gtk_dial_new (GtkAdjustment *adjustment) +{ + GtkDial *dial; + + dial = gtk_type_new (gtk_dial_get_type ()); + + if (!adjustment) + adjustment = (GtkAdjustment*) gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0); + + gtk_dial_set_adjustment (dial, adjustment); + + return GTK_WIDGET (dial); +} + +static void +gtk_dial_destroy (GtkObject *object) +{ + GtkDial *dial; + + g_return_if_fail (object != NULL); + g_return_if_fail (GTK_IS_DIAL (object)); + + dial = GTK_DIAL (object); + + if (dial->adjustment) + gtk_object_unref (GTK_OBJECT (dial->adjustment)); + + if (GTK_OBJECT_CLASS (parent_class)->destroy) + (* GTK_OBJECT_CLASS (parent_class)->destroy) (object); +} +</verb></tscreen> + +Notate che questa funzione <tt/init()/ fa meno rispetto all'analoga del +widget Tictactoe, essendo questo un widget non composto, e la +funzione <tt/new()/ fa di più, essendoci un argomento. Inoltre, +notate che quando memorizziamo un puntatore all'oggetto Adjustment, +incrementiamo il conteggio dei suoi riferimenti(e corrispondentemente +lo decrementato quando non lo usiamo più) così che GTK può tener traccia di +quando è possibile distruggerlo senza causare guai. + +<p> +Inoltre, ci sono alcune funzioni per manipolare le opzioni del widget: + +<tscreen><verb> +GtkAdjustment* +gtk_dial_get_adjustment (GtkDial *dial) +{ + g_return_val_if_fail (dial != NULL, NULL); + g_return_val_if_fail (GTK_IS_DIAL (dial), NULL); + + return dial->adjustment; +} + +void +gtk_dial_set_update_policy (GtkDial *dial, + GtkUpdateType policy) +{ + g_return_if_fail (dial != NULL); + g_return_if_fail (GTK_IS_DIAL (dial)); + + dial->policy = policy; +} + +void +gtk_dial_set_adjustment (GtkDial *dial, + GtkAdjustment *adjustment) +{ + g_return_if_fail (dial != NULL); + g_return_if_fail (GTK_IS_DIAL (dial)); + + if (dial->adjustment) + { + gtk_signal_disconnect_by_data (GTK_OBJECT (dial->adjustment), (gpointer) dial); + gtk_object_unref (GTK_OBJECT (dial->adjustment)); + } + + dial->adjustment = adjustment; + gtk_object_ref (GTK_OBJECT (dial->adjustment)); + + gtk_signal_connect (GTK_OBJECT (adjustment), "changed", + (GtkSignalFunc) gtk_dial_adjustment_changed, + (gpointer) dial); + gtk_signal_connect (GTK_OBJECT (adjustment), "value_changed", + (GtkSignalFunc) gtk_dial_adjustment_value_changed, + (gpointer) dial); + + dial->old_value = adjustment->value; + dial->old_lower = adjustment->lower; + dial->old_upper = adjustment->upper; + + gtk_dial_update (dial); +} +</verb></tscreen> + +<sect2> <tt/gtk_dial_realize()/ + +<p> +Abbiamo ora raggiunto alcuni nuovi tipi di funzione. In primo luogo, +abbiamo una funzione che crea la finestra di X. Noterete che viene +passata alla funzione <tt/gdk_window_new()/ una maschera che +specifica quali campi della struttura GdkWindowAttr non sono vuoti +(ai rimanenti campi può essere dato il valore predefinito). Anche +il modo con cui la maschera degli eventi del widget creata non è +complicato. Chiameremo <tt/gtk_widget_get_events()/ per sapere la +maschera degli eventi che l'utente ha specificato per questo widget +(con <tt/gtk_widget_set_events()/) e aggiungeremo gli eventi che ci possono +interessare. + +<p> +Dopo aver creato la finestra, settiamo lo stile e lo sfondo, +e creiamo un puntatore al widget nel campo dei dati utente (user data) +del GdkWindow. Quest'ultimo passo permette a GTK di mandare gli +eventi della finestra al widget corretto. + +<tscreen><verb> +static void +gtk_dial_realize (GtkWidget *widget) +{ + GtkDial *dial; + GdkWindowAttr attributes; + gint attributes_mask; + + g_return_if_fail (widget != NULL); + g_return_if_fail (GTK_IS_DIAL (widget)); + + GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED); + dial = GTK_DIAL (widget); + + attributes.x = widget->allocation.x; + attributes.y = widget->allocation.y; + attributes.width = widget->allocation.width; + attributes.height = widget->allocation.height; + attributes.wclass = GDK_INPUT_OUTPUT; + attributes.window_type = GDK_WINDOW_CHILD; + attributes.event_mask = gtk_widget_get_events (widget) | + GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK | + GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK | + GDK_POINTER_MOTION_HINT_MASK; + attributes.visual = gtk_widget_get_visual (widget); + attributes.colormap = gtk_widget_get_colormap (widget); + + attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP; + widget->window = gdk_window_new (widget->parent->window, &attributes, attributes_mask); + + widget->style = gtk_style_attach (widget->style, widget->window); + + gdk_window_set_user_data (widget->window, widget); + + gtk_style_set_background (widget->style, widget->window, GTK_STATE_ACTIVE); +} +</verb></tscreen> + +<sect2> Negoziazione della dimensione + +<p> +Prima di visualizzare per la prima volta la finestra, e se il +layout della finestra cambia, GTK chiede ad ogni widget, incluso nella +finestra, la propria dimensione. Questa richiesta è fatta dalla +funzione <tt/gtk_dial_size_request()/. Non essendo il nostro widget +un contenitore, e non avendo dei veri limiti per la propria +dimensione, restituiamo semplicemnte un valore ragionevole. + +<tscreen><verb> +static void +gtk_dial_size_request (GtkWidget *widget, + GtkRequisition *requisition) +{ + requisition->width = DIAL_DEFAULT_SIZE; + requisition->height = DIAL_DEFAULT_SIZE; +} +</verb></tscreen> + +<p> +Dopo che tutti i widget hanno restituito una dimensione ideale, viene +calcolata la disposizione della finestra e ad ogni widget figlio è +notificata la propria dimensione attuale <!--ndMichel : che può essere diversa +da quella restitutita con la funzione sopra -->. Usualmente, questo sarà +almeno quanto richiesto, ma occasionalmente può essere più piccolo. +La notifica della dimensione viene fatta dalla funzione + <tt/gtk_dial_size_allocate()/. Notate che questa funzione è utilizzata +anche quando la finestra X del widget è spostata o modificata come +dimensione. + +<tscreen><verb> +static void +gtk_dial_size_allocate (GtkWidget *widget, + GtkAllocation *allocation) +{ + GtkDial *dial; + + g_return_if_fail (widget != NULL); + g_return_if_fail (GTK_IS_DIAL (widget)); + g_return_if_fail (allocation != NULL); + + widget->allocation = *allocation; + if (GTK_WIDGET_REALIZED (widget)) + { + dial = GTK_DIAL (widget); + + gdk_window_move_resize (widget->window, + allocation->x, allocation->y, + allocation->width, allocation->height); + + dial->radius = MAX(allocation->width,allocation->height) * 0.45; + dial->pointer_width = dial->radius / 5; + } +} +</verb></tscreen>. + +<sect2> <tt/gtk_dial_expose()/ + +<p> +Come menzionato sopra, tutto il lavoro di questo widget viene fatto nella +gestione dell'evento ``expose''. Non c'è molto da notare su questo eccetto +l'uso della funzione <tt/gtk_draw_polygon/ per disegnare il +puntatore con un'ombreggiatura a tre dimensioni in accordo con il colore +memorizzato nello stile del wiget. + +<tscreen><verb> +static gint +gtk_dial_expose (GtkWidget *widget, + GdkEventExpose *event) +{ + GtkDial *dial; + GdkPoint points[3]; + gdouble s,c; + gdouble theta; + gint xc, yc; + gint tick_length; + gint i; + + g_return_val_if_fail (widget != NULL, FALSE); + g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE); + g_return_val_if_fail (event != NULL, FALSE); + + if (event->count > 0) + return FALSE; + + dial = GTK_DIAL (widget); + + gdk_window_clear_area (widget->window, + 0, 0, + widget->allocation.width, + widget->allocation.height); + + xc = widget->allocation.width/2; + yc = widget->allocation.height/2; + + /* Draw ticks */ + + for (i=0; i<25; i++) + { + theta = (i*M_PI/18. - M_PI/6.); + s = sin(theta); + c = cos(theta); + + tick_length = (i%6 == 0) ? dial->pointer_width : dial->pointer_width/2; + + gdk_draw_line (widget->window, + widget->style->fg_gc[widget->state], + xc + c*(dial->radius - tick_length), + yc - s*(dial->radius - tick_length), + xc + c*dial->radius, + yc - s*dial->radius); + } + + /* Draw pointer */ + + s = sin(dial->angle); + c = cos(dial->angle); + + + points[0].x = xc + s*dial->pointer_width/2; + points[0].y = yc + c*dial->pointer_width/2; + points[1].x = xc + c*dial->radius; + points[1].y = yc - s*dial->radius; + points[2].x = xc - s*dial->pointer_width/2; + points[2].y = yc - c*dial->pointer_width/2; + + gtk_draw_polygon (widget->style, + widget->window, + GTK_STATE_NORMAL, + GTK_SHADOW_OUT, + points, 3, + TRUE); + + return FALSE; +} +</verb></tscreen> + +<sect2> Gestore degli eventi + +<p> + +Il resto del codice del widget manipola vari tipi di eventi, e non +è differente da quello che può essere trovato in molte applicazione +GTK. Due tipi di eventi possono verificarsi: l'utente può +clickare sul widget con il mouse e trascinare per muovere il puntatore, +o il valore dell'oggetto Adjustmente può cambiare a causa di alcune +circostanze esterne. + +<p> +Quando l'utente clicka sul widget, noi vediamo se la pressione +era veramente vicina al puntatore, e se così, memorizziamo il bottone +premuto dall'utente con il campo <tt/button/ della struttura del +widget, e prendiamo tutti gli eventi del mouse con una chiamata alla +funzione <tt/gtk_grab_add()/. Successivi movimenti del mouse causano il +ricalcolo dei valori di controllo (fatto dalla funzione +<tt/gtk_dial_update_mouse/). Dipendentemente dalla politica che abbiamo +stabilito, gli eventi ``value_changed'' possono essere generati +istantaneamente (<tt/GTK_UPDATE_CONTINUOUS/), dopo un certo tempo aggiunto +con la funzione <tt/gtk_timeout_add()/ (<tt/GTK_UPDATE_DELAYED/), o +solamente quando il bottone del mouse e' rilasciato +(<tt/GTK_UPDATE_DISCONTINUOUS/). + +<tscreen><verb> +static gint +gtk_dial_button_press (GtkWidget *widget, + GdkEventButton *event) +{ + GtkDial *dial; + gint dx, dy; + double s, c; + double d_parallel; + double d_perpendicular; + + g_return_val_if_fail (widget != NULL, FALSE); + g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE); + g_return_val_if_fail (event != NULL, FALSE); + + dial = GTK_DIAL (widget); + + /* Determina se il bottone premuto era dentro la regione del puntatore: + lo facciamo calcolando la distanza parallela e + perpendicolare dal punto dove il bottone del mouse e' stato premuto + alla linea passante per il puntatore. */ + + dx = event->x - widget->allocation.width / 2; + dy = widget->allocation.height / 2 - event->y; + + s = sin(dial->angle); + c = cos(dial->angle); + + d_parallel = s*dy + c*dx; + d_perpendicular = fabs(s*dx - c*dy); + + if (!dial->button && + (d_perpendicular < dial->pointer_width/2) && + (d_parallel > - dial->pointer_width)) + { + gtk_grab_add (widget); + + dial->button = event->button; + + gtk_dial_update_mouse (dial, event->x, event->y); + } + + return FALSE; +} + +static gint +gtk_dial_button_release (GtkWidget *widget, + GdkEventButton *event) +{ + GtkDial *dial; + + g_return_val_if_fail (widget != NULL, FALSE); + g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE); + g_return_val_if_fail (event != NULL, FALSE); + + dial = GTK_DIAL (widget); + + if (dial->button == event->button) + { + gtk_grab_remove (widget); + + dial->button = 0; + + if (dial->policy == GTK_UPDATE_DELAYED) + gtk_timeout_remove (dial->timer); + + if ((dial->policy != GTK_UPDATE_CONTINUOUS) && + (dial->old_value != dial->adjustment->value)) + gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), "value_changed"); + } + + return FALSE; +} + +static gint +gtk_dial_motion_notify (GtkWidget *widget, + GdkEventMotion *event) +{ + GtkDial *dial; + GdkModifierType mods; + gint x, y, mask; + + g_return_val_if_fail (widget != NULL, FALSE); + g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE); + g_return_val_if_fail (event != NULL, FALSE); + + dial = GTK_DIAL (widget); + + if (dial->button != 0) + { + x = event->x; + y = event->y; + + if (event->is_hint || (event->window != widget->window)) + gdk_window_get_pointer (widget->window, &x, &y, &mods); + + switch (dial->button) + { + case 1: + mask = GDK_BUTTON1_MASK; + break; + case 2: + mask = GDK_BUTTON2_MASK; + break; + case 3: + mask = GDK_BUTTON3_MASK; + break; + default: + mask = 0; + break; + } + + if (mods & mask) + gtk_dial_update_mouse (dial, x,y); + } + + return FALSE; +} + +static gint +gtk_dial_timer (GtkDial *dial) +{ + g_return_val_if_fail (dial != NULL, FALSE); + g_return_val_if_fail (GTK_IS_DIAL (dial), FALSE); + + if (dial->policy == GTK_UPDATE_DELAYED) + gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), "value_changed"); + + return FALSE; +} + +static void +gtk_dial_update_mouse (GtkDial *dial, gint x, gint y) +{ + gint xc, yc; + gfloat old_value; + + g_return_if_fail (dial != NULL); + g_return_if_fail (GTK_IS_DIAL (dial)); + + xc = GTK_WIDGET(dial)->allocation.width / 2; + yc = GTK_WIDGET(dial)->allocation.height / 2; + + old_value = dial->adjustment->value; + dial->angle = atan2(yc-y, x-xc); + + if (dial->angle < -M_PI/2.) + dial->angle += 2*M_PI; + + if (dial->angle < -M_PI/6) + dial->angle = -M_PI/6; + + if (dial->angle > 7.*M_PI/6.) + dial->angle = 7.*M_PI/6.; + + dial->adjustment->value = dial->adjustment->lower + (7.*M_PI/6 - dial->angle) * + (dial->adjustment->upper - dial->adjustment->lower) / (4.*M_PI/3.); + + if (dial->adjustment->value != old_value) + { + if (dial->policy == GTK_UPDATE_CONTINUOUS) + { + gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), "value_changed"); + } + else + { + gtk_widget_draw (GTK_WIDGET(dial), NULL); + + if (dial->policy == GTK_UPDATE_DELAYED) + { + if (dial->timer) + gtk_timeout_remove (dial->timer); + + dial->timer = gtk_timeout_add (SCROLL_DELAY_LENGTH, + (GtkFunction) gtk_dial_timer, + (gpointer) dial); + } + } + } +} +</verb></tscreen> + +<p> +Cambiamenti esterni all'Adjustment sono comunicati al nostro widget +dai segnali ``changed'' e ``value_changed''. Il gestore per +queste funzioni chiama <tt/gtk_dial_update()/ per validare gli +argomenti, calcolare il nuovo angolo del puntatore e ridisegnare il +widget (chiamando <tt/gtk_widget_draw()/). + +<tscreen><verb> +static void +gtk_dial_update (GtkDial *dial) +{ + gfloat new_value; + + g_return_if_fail (dial != NULL); + g_return_if_fail (GTK_IS_DIAL (dial)); + + new_value = dial->adjustment->value; + + if (new_value < dial->adjustment->lower) + new_value = dial->adjustment->lower; + + if (new_value > dial->adjustment->upper) + new_value = dial->adjustment->upper; + + if (new_value != dial->adjustment->value) + { + dial->adjustment->value = new_value; + gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), "value_changed"); + } + + dial->angle = 7.*M_PI/6. - (new_value - dial->adjustment->lower) * 4.*M_PI/3. / + (dial->adjustment->upper - dial->adjustment->lower); + + gtk_widget_draw (GTK_WIDGET(dial), NULL); +} + +static void +gtk_dial_adjustment_changed (GtkAdjustment *adjustment, + gpointer data) +{ + GtkDial *dial; + + g_return_if_fail (adjustment != NULL); + g_return_if_fail (data != NULL); + + dial = GTK_DIAL (data); + + if ((dial->old_value != adjustment->value) || + (dial->old_lower != adjustment->lower) || + (dial->old_upper != adjustment->upper)) + { + gtk_dial_update (dial); + + dial->old_value = adjustment->value; + dial->old_lower = adjustment->lower; + dial->old_upper = adjustment->upper; + } +} + +static void +gtk_dial_adjustment_value_changed (GtkAdjustment *adjustment, + gpointer data) +{ + GtkDial *dial; + + g_return_if_fail (adjustment != NULL); + g_return_if_fail (data != NULL); + + dial = GTK_DIAL (data); + + if (dial->old_value != adjustment->value) + { + gtk_dial_update (dial); + + dial->old_value = adjustment->value; + } +} +</verb></tscreen> + +<sect2> Possibili Miglioramenti + +<p> + +Il widget Dial, da come l'abbiamo costruito, è lungo circa 670 linee +di codice C. Anche se questo potrebbe sembrare un po' troppo, abbiamo +realmente fatto un bel po' con quel tanto di codice, specialmente +considerando che molta della lunghezza è costituita da file header e +commmenti. Comunque ci sono alcuni miglioramenti che potrebbero essere +fatti a questo widget: + +<itemize> +<item> Se tu provate questo widget, troverete che ci sono alcuni lampeggiamenti +quando il puntatore viene trascinato in giro. Questo +perchè l'intero widget è cancellato ogni volta che il +puntatore viene mosso, prima di essere ridisegnato. Spesso, il modo migliore +per gestire questo tipo di problema è il disegnare il tutto su una +pixmap non visibile, poi copiare il risultato finale sullo schermo +in una passata sola (il widget ProgressBar viene disegnato in questo +modo). + +<item> L'utente potrebbe essere abilitato ad usare le frecce su e giu per +incrementare e diminuire il valore. + +<item> Potrebbe essere carino se il widget avesse i bottoni per +incrementare e decrementare il valore di step. Anche se potrebbe essere +possibile usare dei widget Bottone incorporati per questo, possiamo anche +far sì che il bottone sia auto-ripentente quando premuto, come le frecce +in una barra di scorrimento. Molto del codice per implementare questo tipo di +comportamento può essere trovato nel widget GtkRange. + +<item> il widget Dial potrebbe essere fatto/creato dentro un widget +contenitore con un singolo widget figlio posizionato all'inizio tra i +2 bottoni menzionati prima. L'utente potrebbe poi aggiungere o una etichetta +o un widget ``entry'' per mostrare il valore corrente del dial. + +</itemize> + +<sect1> Impararne di più + +<p> +Fin qui abbiamo esposto solo una piccola parte di tutto quello che serve +per creare un widget. Se volete davvero scrivere un vostro widget, la +miglior risorsa di esempi è lo stesso codice sorgente GTK. Chiedete a voi +stessi alcune cose su come deve essere il widget che volete scrivere: è +un widget contenitore? dovrà avere una propria finestra? è una modifica di +un widget precedente? Trovate poi un widget simile e iniziate a fargli +delle modifiche. +Buone Fortuna. + + +<sect>Scribble, Un semplice esempio di Programma di Disegno + +<sect1> Panoramica + +<p> +In questa sezione, creeremo un semplice programma di disegno. Durante +questo processo, esamineremo come gestire gli eventi generati dal mouse, +come disegnare all'interno di una finestra e come disegnare in modo migliore +usando una pixmap di supporto. Dopo averlo creato, lo amplieremo aggiungendo +il supporto per i dispositivi XInput, per esempio le tavolette grafiche. +Il GTK fornisce delle routine di supporto grazie alle quali risulta piuttosto +semplice ottenere informazioni estese, come la pressione o l'inclinazione. + +<sect1> Gestione degli Eventi + +<p> +I segnali di GTK che abbiamo discusso finora si riferivano ad azioni di +alto livello, ad esempio la selezione di un elemento di un menù. Però, a volte +è utile sapere qualcosa su cose che si svolgono a livello più basso livello, +come possono essere il movimento del mouse o la pressione di un tasto. +Ci sono segnali di GTK anche per questi <em>eventi</em> di basso livello. +I gestori di questo tipo di segnali hanno un parametro caratteristico in più, +che è il puntatore ad una struttura che contiene informazioni riguardo +all'evento. Per esempio, ai gestori di eventi che riguardano dei movimenti, +si passa un puntatore ad una struttura GdkEventMotion, che è fatta (in parte) +così: + +<tscreen><verb> +struct _GdkEventMotion +{ + GdkEventType type; + GdkWindow *window; + guint32 time; + gdouble x; + gdouble y; + ... + guint state; + ... +}; +</verb></tscreen> + +<tt/type/ avrà il valore del tipo di evento, in questo caso +<tt/GDK_MOTION_NOTIFY/, <tt/window/ rappresenta la finestra in cui l'evento +si è verificato. <tt/x/ e <tt/y/ forniscono le coordinate dell'evento e +<tt/state/ specifica lo stato dei modificatori nel momento in cui l'evento +si è verificato (cioè, specifica quali tasti modificatori e tasti del mouse +erano premuti in quel momento). E' un OR bit per bit dei seguenti valori: + +<tscreen><verb> +GDK_SHIFT_MASK +GDK_LOCK_MASK +GDK_CONTROL_MASK +GDK_MOD1_MASK +GDK_MOD2_MASK +GDK_MOD3_MASK +GDK_MOD4_MASK +GDK_MOD5_MASK +GDK_BUTTON1_MASK +GDK_BUTTON2_MASK +GDK_BUTTON3_MASK +GDK_BUTTON4_MASK +GDK_BUTTON5_MASK +</verb></tscreen> + +<p> +Come succede per gli altri segnali, per determinare cosa deve accadere in +corrispondenza di un evento, si chiama <tt>gtk_signal_connect()</tt>. Ma +è anche necessario far sì che GTK sappia di quali eventi vogliamo essere +informati. A questo fine, chiamiamo la funzione: + +<tscreen><verb> +void gtk_widget_set_events (GtkWidget *widget, gint events); +</verb></tscreen> + +Il secondo campo specifica gli eventi che ci interessano. Si tratta dell'OR +bit per bit delle costanti che identificano i diversi tipi di eventi. La lista +dei tipi di eventi è la seguente: + +<tscreen><verb> +GDK_EXPOSURE_MASK +GDK_POINTER_MOTION_MASK +GDK_POINTER_MOTION_HINT_MASK +GDK_BUTTON_MOTION_MASK +GDK_BUTTON1_MOTION_MASK +GDK_BUTTON2_MOTION_MASK +GDK_BUTTON3_MOTION_MASK +GDK_BUTTON_PRESS_MASK +GDK_BUTTON_RELEASE_MASK +GDK_KEY_PRESS_MASK +GDK_KEY_RELEASE_MASK +GDK_ENTER_NOTIFY_MASK +GDK_LEAVE_NOTIFY_MASK +GDK_FOCUS_CHANGE_MASK +GDK_STRUCTURE_MASK +GDK_PROPERTY_CHANGE_MASK +GDK_PROXIMITY_IN_MASK +GDK_PROXIMITY_OUT_MASK +</verb></tscreen> + +Per chiamare <tt/gtk_widget_set_events()/, si devono fare alcune osservazioni +sottili. In primo luogo, la si deve chiamare prima che sia stata creata la +finestra X per il widget GTK. In pratica, ciò significa che la si deve +chiamare subito dopo aver creato il widget. In secondo luogo, il widget +deve avere una finestra X associata. Molti widget, per ragioni di +efficienza, non hanno una propria finetra, e vengono mostrati nella +finestra madre. Questi widget sono: + +<tscreen><verb> +GtkAlignment +GtkArrow +GtkBin +GtkBox +GtkImage +GtkItem +GtkLabel +GtkPaned +GtkPixmap +GtkScrolledWindow +GtkSeparator +GtkTable +GtkViewport +GtkAspectFrame +GtkFrame +GtkVPaned +GtkHPaned +GtkVBox +GtkHBox +GtkVSeparator +GtkHSeparator +</verb></tscreen> + +Per catturare degli eventi per questo tipo di widget, si deve fare uso +del widget EventBox. Si veda a questo proposito la sezione su +<ref id="sec_The_EventBox_Widget" name="The EventBox Widget">. + +<p> +Per il nostro programma di disegno, vogliamo sapere quando il pulsante del +mouse è premuto e quando viene mosso, quindi specificheremo +<tt/GDK_POINTER_MOTION_MASK/ e <tt/GDK_BUTTON_PRESS_MASK/. Vogliamo anche +essere informati su quando è necessario ridisegnare la nostra finestra, +quindi specifichiamo <tt/GDK_EXPOSURE_MASK/. Anche se vogliamo essere +avvertiti con un evento ``Configure'' se la dimensione della nostra finestra +cambia, non è necessario specificare il flag <tt/GDK_STRUCTURE_MASK/, dal +momento che questo viene specificato automaticamente per tutte le finestre. + +<p> +Risulta, conunque, che specificando semplicemente <tt/GDK_POINTER_MOTION_MASK/ +si crea un problema. Ciò infatti fa sì che il server aggiunga nella coda un +un nuovo evento di movimento ogni volta che l'utente muovoe il mouse. Immaginate +che ci vogliano 0.1 secondi per gestire uno di questi eventi, e che il server +X metta in coda un nuovo evento ogni 0.05 secondi. Rimarremo ben presto indietro +rispetto al disegno dell'utente. Se l'utente disegna per 5 secondi, ci metteremmo +altri 5 secondi prima di finire dopo che l'utente ha rilasciato il pulsante del +mouse! Vorremmo quindi che venga notificato un solo evento di movimento per +ogni evento che processiamo. Il modo per farlo è di specificare +<tt/GDK_POINTER_MOTION_HINT_MASK/. + +<p> +Quando specifichiamo <tt/GDK_POINTER_MOTION_HINT_MASK/, il server ci notifica +un evento di movimento la prima volta che il puntatore si muove dopo essere +entrato nella nostra finestra, oppure dopo ogni rilascio di un pulsante del +mouse. Gli altri eventi di movimento verranno soppressi finché non richiediamo +esplicitamente la posizione del puntatore con la funzione: + +<tscreen><verb> +GdkWindow* gdk_window_get_pointer (GdkWindow *window, + gint *x, + gint *y, + GdkModifierType *mask); +</verb></tscreen> + +(c'è anche un'altra funzione, <tt>gtk_widget_get_pointer()</tt>, che ha +un'interfaccia più semplice, ma che non risulta molto utile dal momento +che restituisce solo la posizione del puntatore, senza dettagli sullo +sato dei pulsanti.) + +<p> +Quindi, il codice per assegnare gli eventi per la nostra finestra, avrà l'aspetto: + +<tscreen><verb> + gtk_signal_connect (GTK_OBJECT (drawing_area), "expose_event", + (GtkSignalFunc) expose_event, NULL); + gtk_signal_connect (GTK_OBJECT(drawing_area),"configure_event", + (GtkSignalFunc) configure_event, NULL); + gtk_signal_connect (GTK_OBJECT (drawing_area), "motion_notify_event", + (GtkSignalFunc) motion_notify_event, NULL); + gtk_signal_connect (GTK_OBJECT (drawing_area), "button_press_event", + (GtkSignalFunc) button_press_event, NULL); + + gtk_widget_set_events (drawing_area, GDK_EXPOSURE_MASK + | GDK_LEAVE_NOTIFY_MASK + | GDK_BUTTON_PRESS_MASK + | GDK_POINTER_MOTION_MASK + | GDK_POINTER_MOTION_HINT_MASK); +</verb></tscreen> + +Teniamo per dopo i gestori di ``expose_event'' e ``configure_event''. Quelli di +``motion_notify_event'' e ``button_press_event'' sono piuttosto semplici: + +<tscreen><verb> +static gint +button_press_event (GtkWidget *widget, GdkEventButton *event) +{ + if (event->button == 1 && pixmap != NULL) + draw_brush (widget, event->x, event->y); + + return TRUE; +} + +static gint +motion_notify_event (GtkWidget *widget, GdkEventMotion *event) +{ + int x, y; + GdkModifierType state; + + if (event->is_hint) + gdk_window_get_pointer (event->window, &x, &y, &state); + else + { + x = event->x; + y = event->y; + state = event->state; + } + + if (state & GDK_BUTTON1_MASK && pixmap != NULL) + draw_brush (widget, x, y); + + return TRUE; +} +</verb></tscreen> + + +<sect1> Il widget Area di Disegno (DrawingArea) e il procedimento per Disegnare + +<p> +Vediamo ora il procedimento per disegnare sullo schermo. Il +widget da usare è l'Area di Disegno (DrawingArea). Essenzialmente si +tratta di una finestra X e nient'altro. E' una tela bianca su cui possimo +disegnare tutto quello che vogliamo. Per crearne una usiamo la chiamata: + +<tscreen><verb> +GtkWidget* gtk_drawing_area_new (void); +</verb></tscreen> + +Per specificare una dimensione predefinita, si puo fare: + +<tscreen><verb> +void gtk_drawing_area_size (GtkDrawingArea *darea, + gint width, + gint height); +</verb></tscreen> + +Come è vero per tutti i widget, si può modificare questa dimensione +predefinita, tramite la chamata a <tt>gtk_widget_set_usize()</tt>, e +questa a sua volta può essere modificata dall'utente ridimensionando +manualmente la finestra che contiene l'area di disegno. + +<p> +Si deve notare che nel momento in cui creiamo un widget DrawingArea, siamo +<em>completamente</em> responsabili di disegnarne il contenuto. Se ad +esempio la nostra finestra viene prima nascosta e poi dinuovo portata in +primo piano, otteniamo un evento di ``esposizione'' e doppiamo ridisegnare +ciò che era stato precedente nascosto. + +<p> +Dover ricordare tutto quello che era disegnato sulla finestra in modo da +poterlo ridisegnare successivamente, può essere, come minimo, noioso. +In più, può essere spiacevole dal punto di vista visivo, se delle porzioni +dello schermo vengono prima cancellate e poi ridisegnate passo per passo. +La soluzione per questo problema è di usare una <em>pixmap di supporto</em>. +Invece di disegnare direttamente sullo schermo, disegnamo su un'iimagine +conservata nella memoria del server ma che non viene mostrata; quindi, quando +l'immagine cambia o ne vengono mostrate nuove porzioni, copiamo sullo schermo +le parti corrispondenti. + +<p> +Per creare una ppixmap fuori dallo schermo, usiamo la funzione: + +<tscreen><verb> +GdkPixmap* gdk_pixmap_new (GdkWindow *window, + gint width, + gint height, + gint depth); +</verb></tscreen> + +Il parametro <tt>window</tt>specifica una finestra GDK dalla quale questa +pixmap prende alcune delle sue proprietà. <tt>width</tt> e <tt>height</tt> +specificano le dimensioni della pixmap. <tt>depth</tt> specifica la +<em>profondità di colore</em>, cioè il numero di bit per ogni pixel, per +la nuova pixmap. Se alla profondità è assegnato il valore <tt>-1</tt>, questa +verrà posta identica a quella di <tt>window</tt>. + +<p> +Creiamo la pixmap all'interno del gestore di ``configure_event''. Questo evento +è generato ogni volta che la finestra cambia di dimensione, compreso il +momento in cui viene creata per la prima volta. + +<tscreen><verb> +/* Pixmap di supporto per l'area di disegno */ +static GdkPixmap *pixmap = NULL; + +/* Creare una pixmap della dimensione appropriata */ +static gint +configure_event (GtkWidget *widget, GdkEventConfigure *event) +{ + if (pixmap) + { + gdk_pixmap_destroy(pixmap); + } + pixmap = gdk_pixmap_new(widget->window, + widget->allocation.width, + widget->allocation.height, + -1); + gdk_draw_rectangle (pixmap, + widget->style->white_gc, + TRUE, + 0, 0, + widget->allocation.width, + widget->allocation.height); + + return TRUE; +} +</verb></tscreen> + +La chiamata a <tt>gdk_draw_rectangle()</tt> inizialmente rende bianca l'intera +pixmap. Fra un momento ne riparleremo. + +<p> +Il gestore dell'evento ``esposizione'', copia quindi la porzione appropriata +della pixmap sullo schermo (determiniamo qual è l'area da ridisegnare usando +il campo event->area dell'evento di esposizione): + +<tscreen><verb> +/* Ridisegna sullo schermo a partire dalla pixmap di supporto */ +static gint +expose_event (GtkWidget *widget, GdkEventExpose *event) +{ + gdk_draw_pixmap(widget->window, + widget->style->fg_gc[GTK_WIDGET_STATE (widget)], + pixmap, + event->area.x, event->area.y, + event->area.x, event->area.y, + event->area.width, event->area.height); + + return FALSE; +} +</verb></tscreen> + +Abbiamo quindi visto come tenete aggiornato lo schermo con la nostra +pixmap, ma come facciamo per disegnare delle cose interessanti sulla +pixmap? Ci sono un bel po' di funzioni nella libreria GDK di GTK che +servono per disegnare su superfici <em>disegnabili</em>. Una superficie +disegnabile è semplicemente qualcosa su cui si può disegnare un'immagine. +Può essere una finestra, una pixmap o una bitmap (un'immagine in bianco e +nero). Abbiamo già visto sopra due di chiamate, +<tt>gdk_draw_rectangle()</tt> and <tt>gdk_draw_pixmap()</tt>. La lista +completa è la seguente: + +<tscreen><verb> +gdk_draw_line () +gdk_draw_rectangle () +gdk_draw_arc () +gdk_draw_polygon () +gdk_draw_string () +gdk_draw_text () +gdk_draw_pixmap () +gdk_draw_bitmap () +gdk_draw_image () +gdk_draw_points () +gdk_draw_segments () +</verb></tscreen> + +Per ulteriori dettagli su queste funzioni, vedete la documentazione di +riferimento nei file header <tt><gdk/gdk.h></tt>. +Tutte queste funzioni hanno i medesimi primi due argomenti. Il primo +è la superficie disegnabili su cui disegnare, il secondo è un +<em>contesto grafico</em> (GC). + +<p> +Un contesto grafico incapsula delle informazioni riguardo a cose come +il colore di sfondo e di primo piano e lo spessore della linea. +GDK ha un ampio insieme di funzioni per crare e modificare contesti grafici, +ma per tenere le cose semplici useremo solo dei contesti grafici predefiniti. +Ogni widget ha uno stile associato (che può essere modificato agendo su un +file gtkrc). Questo, fra le altre cose, contiene un certo numero di contesti +grafici. Alcuni esempi di come accedere a questi contesti grafici sono +i seguenti: + +<tscreen><verb> +widget->style->white_gc +widget->style->black_gc +widget->style->fg_gc[GTK_STATE_NORMAL] +widget->style->bg_gc[GTK_WIDGET_STATE(widget)] +</verb></tscreen> + +I campi <tt>fg_gc</tt>, <tt>bg_gc</tt>, <tt>dark_gc</tt>, e +<tt>light_gc</tt> sono indicizzati tramite un parametri di tipo +<tt>GtkStateType</tt>, che può assumere i valori: + +<tscreen><verb> +GTK_STATE_NORMAL, +GTK_STATE_ACTIVE, +GTK_STATE_PRELIGHT, +GTK_STATE_SELECTED, +GTK_STATE_INSENSITIVE +</verb></tscreen> + +Per esempio, per <tt/GTK_STATE_SELECTED/ il colore di sfondo predefinito +è blu scuro e quello di primo piano bianco. + +<p> +La nostra funzione <tt>draw_brush()</tt>, che efettivamente disegna sullo +schermo, diventa quindi: + +<tscreen><verb> +/* Disegna un rettangolo sullo schermo */ +static void +draw_brush (GtkWidget *widget, gdouble x, gdouble y) +{ + GdkRectangle update_rect; + + update_rect.x = x - 5; + update_rect.y = y - 5; + update_rect.width = 10; + update_rect.height = 10; + gdk_draw_rectangle (pixmap, + widget->style->black_gc, + TRUE, + update_rect.x, update_rect.y, + update_rect.width, update_rect.height); + gtk_widget_draw (widget, &update_rect); +} +</verb></tscreen> + +Dopo aver disegnato il rettangolo sulla pixmap, chiamiamo la funzione: + +<tscreen><verb> +void gtk_widget_draw (GtkWidget *widget, + GdkRectangle *area); +</verb></tscreen> + +che notifica a X che l'area data dal parametro <tt>area</tt> deve essere +aggiornata. X poi genererà un evento di esposizione (che può essere combinato +con le aree passate da diverse chiamate a <tt>gtk_widget_draw()</tt>) che +farà sì che il nostro gestore dell'evento di esposizione, copi le porzioni +rilevanti sullo schermo. + +<p> +Abbiamo a questo punto creato tutto il programma di disegno, tranne che +per qualche dettaglio irrilevante come la creazione della finestra principale. +Il codice sorgente completo è reperibile dove avete ottenuto questo tutorial. + +<sect1> Aggiungere il supporto per XInput + +<p> +Al giorno d'oggi è possibile acquistare dei dispositivi abbastanza a buon +mercato, come tavolette grafice, che permettono di disegnare con una +espressività artistica molto semplificata rispetto ad un mouse. +Il modo più semplice per usare questi dispositivi è di sostituirli +semplicemente al mouse, ma in questo modo si perdono molti dei loro +vantaggi, come: + +<itemize> +<item> Sensibilità alla pressione +<item> Sensibilità all'inclinazione +<item> Posizionamento infra-pixel +<item> Ingressi multipli (per esempio, uno stilo che contiene sia una ``matita'' +sia una ``gomma'') +</itemize> + +Per ulteriori informazioni sulle estensioni XInput, vedere l'<url +url="http://www.msc.cornell.edu/~otaylor/xinput/XInput-HOWTO.html" +name="XInput-HOWTO">. + +<p> +Se esaminiamo, per esempio, la definizione completa della struttura +GdkEventMotion, possiamo vedere che contiene dei campi per il supporto +delle informazioni estese dai dispositivi. + +<tscreen><verb> +struct _GdkEventMotion +{ + GdkEventType type; + GdkWindow *window; + guint32 time; + gdouble x; + gdouble y; + gdouble pressure; + gdouble xtilt; + gdouble ytilt; + guint state; + gint16 is_hint; + GdkInputSource source; + guint32 deviceid; +}; +</verb></tscreen> + +<tt/pressure/ fornisce la pressione sotto forma di un numero decimale +compreso fra 0 e 1. <tt/xtilt/ e <tt/ytilt/ possono assumere valori +compresi fra -1 e 1, corrispondenti al grado di inclinazione in ciascuna +direzione. <tt/source/ e <tt/deviceid/ specificano il dispositivo per il +quale si è verificato l'evento in due modi distinti. <tt/source/ da alcune +semplici informazioni sul tipo di dispositivo, e può assumere i valori: + +<tscreen><verb> +GDK_SOURCE_MOUSE +GDK_SOURCE_PEN +GDK_SOURCE_ERASER +GDK_SOURCE_CURSOR +</verb></tscreen> + +<tt/deviceid/ specifica invece un identificativo numerico univoco per il +dispositivo. Questo può essere a sua volta utilizzato per avere ulteriori +informazioni sul dispositivo tramite la chiamata a <tt/gdk_input_list_devices()/ +(vedi sotto). Il valore speciale <tt/GDK_CORE_POINTER/ viene usato per identificare +il dispositivo di puntamento principale (di solito il mouse). + +<sect2> Abilitare le informazioni estese + +<p> +Per far sì che GTK sappia che ci interessano le informazioni estese dai +dispositivi, basta aggiungere un'unica linea al nostro programma: + +<tscreen><verb> +gtk_widget_set_extension_events (drawing_area, GDK_EXTENSION_EVENTS_CURSOR); +</verb></tscreen> + +Dando il valore <tt/GDK_EXTENSION_EVENTS_CURSOR/, diciamo che ci interessano +gli eventi relativi alle estensioni, ma solo se non dobbiamo disegnare da noi +il nostro cursore. Si veda più sotto alla sezione <ref +id="sec_Further_Sophistications" name="Ulteriori Sofisticazioni"> per ulteriori +informazioni sul modo si disegnare i cursori. Potremmo anche dare i valori +<tt/GDK_EXTENSION_EVENTS_ALL/ se vogliamo disegnare il nostro cursore o +<tt/GDK_EXTENSION_EVENTS_NONE/ se vogliamo tornare alle condizioni predefinite. + +<p> +Comunque, non finisce tutto qui. Non ci sono estensioni abilitate per difetto. +Abbiamo bisogno di un meccanismo per permettere agli utenti l'abilitazione e +la configurazione delle estensioni dei loro dispositivi, GTK fornisce il +widget InputDialog per automatizzare questo processo. La seguente procedura +mostra come gestire un widget InputDialog. Crea la finestra di dialogo nel +caso non sia presente, mentre la porta in primo piano in caso contrario. + +<tscreen><verb> +void +input_dialog_destroy (GtkWidget *w, gpointer data) +{ + *((GtkWidget **)data) = NULL; +} + +void +create_input_dialog () +{ + static GtkWidget *inputd = NULL; + + if (!inputd) + { + inputd = gtk_input_dialog_new(); + + gtk_signal_connect (GTK_OBJECT(inputd), "destroy", + (GtkSignalFunc)input_dialog_destroy, &inputd); + gtk_signal_connect_object (GTK_OBJECT(GTK_INPUT_DIALOG(inputd)->close_button), + "clicked", + (GtkSignalFunc)gtk_widget_hide, + GTK_OBJECT(inputd)); + gtk_widget_hide ( GTK_INPUT_DIALOG(inputd)->save_button); + + gtk_widget_show (inputd); + } + else + { + if (!GTK_WIDGET_MAPPED(inputd)) + gtk_widget_show(inputd); + else + gdk_window_raise(inputd->window); + } +} +</verb></tscreen> + +(Notate come gestiamo questo dialogo. Con la connessione del segnale +``destroy'' ci assicuriamo di non tenerci in giro il puntatore al dialogo +dopo che lo abbiamo distrutto, cosa che potrebbe portare ad un errore di +segmentazione.) + +<p> +L'InputDialog ha due pulsanti, ``Close'' e ``Save'', i quali non hanno alcuna +azione predefinita assegnata ad essi. Nella funzione precedente, abbiamo +fatto in modo che ``Close'' nasconda la finestra di dialogo, e abbiamo nascosto +il pulsante ``Save'' dal momento che in questo programma non implementiamo il +salvataggio delle opzioni di XInput. + +<sect2> Usare le informazioni estese + +<p> +Una volta abilitato il dipositivo, possiamo usare le informazioni estese +che si trovano nei corrispondenti campi delle strutture che descrivono gli +eventi. A dire il vero, l'utilizzo di questi campi è sempre sicuro, perché +sono tutti posti per difetto a valori ragionevoli ancje quando la gestione +degli eventi estesi non è abilitata. + +<p> +Un cambiamento che dobbiamo fare è di chiamare <tt/gdk_input_window_get_pointer()/ +invece di <tt/gdk_window_get_pointer/. Ciò si rende necessario perché +<tt/gdk_window_get_pointer/ non restituisce le informazioni esetese. + +<tscreen><verb> +void gdk_input_window_get_pointer (GdkWindow *window, + guint32 deviceid, + gdouble *x, + gdouble *y, + gdouble *pressure, + gdouble *xtilt, + gdouble *ytilt, + GdkModifierType *mask); +</verb></tscreen> + +Quando chiamiamo questa funzione, dobbiamo specificare l'identificativo +del dispositivo e la finestra. Normalmente questo identificativo lo si +ottiene dal campo <tt/deviceid/ della struttura dell'evento. +Questa funzione restituirà valori ragionevoli nel caso che la gestione +degli eventi estesi non sia attivata (in questo caso, <tt/event->deviceid/ +avrà il valore <tt/GDK_CORE_POINTER/). + +Quindi, la struttura di base dei gestori degli eventi relativi alla +pressione di bottoni e ai movomenti non cambia molto - abbiamo solo +bisogno di aggiungere il codice necessario per tenere conto delle +informazioni estese. + +<tscreen><verb> +static gint +button_press_event (GtkWidget *widget, GdkEventButton *event) +{ + print_button_press (event->deviceid); + + if (event->button == 1 && pixmap != NULL) + draw_brush (widget, event->source, event->x, event->y, event->pressure); + + return TRUE; +} + +static gint +motion_notify_event (GtkWidget *widget, GdkEventMotion *event) +{ + gdouble x, y; + gdouble pressure; + GdkModifierType state; + + if (event->is_hint) + gdk_input_window_get_pointer (event->window, event->deviceid, + &x, &y, &pressure, NULL, NULL, &state); + else + { + x = event->x; + y = event->y; + pressure = event->pressure; + state = event->state; + } + + if (state & GDK_BUTTON1_MASK && pixmap != NULL) + draw_brush (widget, event->source, x, y, pressure); + + return TRUE; +} +</verb></tscreen> + +Avremo anche bisogno di fare qualcosa con queste nuove informazioni. La +nostra nuova funzione <tt/draw_brush/ disegna con un colore diverso per +ogni <tt/event->source/ e cambia la dimensione della linea in funzione +della pressione. + +<tscreen><verb> +/* Disegna un rettangolo sullo schermo, con la dimensione dipendente + dalla pressione e il colore dipendente dal tipo di dispositivo */ +static void +draw_brush (GtkWidget *widget, GdkInputSource source, + gdouble x, gdouble y, gdouble pressure) +{ + GdkGC *gc; + GdkRectangle update_rect; + + switch (source) + { + case GDK_SOURCE_MOUSE: + gc = widget->style->dark_gc[GTK_WIDGET_STATE (widget)]; + break; + case GDK_SOURCE_PEN: + gc = widget->style->black_gc; + break; + case GDK_SOURCE_ERASER: + gc = widget->style->white_gc; + break; + default: + gc = widget->style->light_gc[GTK_WIDGET_STATE (widget)]; + } + + update_rect.x = x - 10 * pressure; + update_rect.y = y - 10 * pressure; + update_rect.width = 20 * pressure; + update_rect.height = 20 * pressure; + gdk_draw_rectangle (pixmap, gc, TRUE, + update_rect.x, update_rect.y, + update_rect.width, update_rect.height); + gtk_widget_draw (widget, &update_rect); +} +</verb></tscreen> + +<sect2> Trovare ulteriori informazioni su di un dispositivo + +<p> +Come esempio del modo di trovare altre informazioni su di un dispositivo, +il nostro programma stamperà il nome di ogni dispositivo che genera un +evento di pressione di un pulsante. Per avere il nome di un dispositivo, +chiamiamo la funzione + +<tscreen><verb> +GList *gdk_input_list_devices (void); +</verb></tscreen> + +che restituisce una GList (un tipo di lista collegata che si trova nella +libreria glib) di strutture di tipo GdkDeviceInfo. La definizione di +GdkDeviceInfo è la seguente: + +<tscreen><verb> +struct _GdkDeviceInfo +{ + guint32 deviceid; + gchar *name; + GdkInputSource source; + GdkInputMode mode; + gint has_cursor; + gint num_axes; + GdkAxisUse *axes; + gint num_keys; + GdkDeviceKey *keys; +}; +</verb></tscreen> + +La maggior parte di questi campi rappresentano informazioni di configurazione +che potete ignorare a meno che non implementiate il salvataggio della +configurazione di un XInput. Quelle che ci interessano sono <tt/name/, che +è semplicemente il nome che X assegna al dispositivo, e <tt/has_cursor/. Anche +<tt/has_cursor/ non è informazione di configurazione, e indica, nel caso +abbia valore ``falso'', che dobbiamo disegnare da soli il nostro cursore. Ma +dal momento che abbiamo specificato <tt/GDK_EXTENSION_EVENTS_CURSOR/, +possiamo anche non preoccuparcene. + +<p> + +La nostra funzione <tt/print_button_press()/ scorre semplicemente la lista +che è stata restituita finché non trova il valore corretto, e poi stampa +il nome del dispositivo. + +<tscreen><verb> +static void +print_button_press (guint32 deviceid) +{ + GList *tmp_list; + + /* gdk_input_list_devices restituisce una lista interna, così poi + non dobbiamo liberarla */ + tmp_list = gdk_input_list_devices(); + + while (tmp_list) + { + GdkDeviceInfo *info = (GdkDeviceInfo *)tmp_list->data; + + if (info->deviceid == deviceid) + { + printf("Button press on device '%s'\n", info->name); + return; + } + + tmp_list = tmp_list->next; + } +} +</verb></tscreen> +Questo completa i cambiamenti necessari per usare gli XInput nel nostro +programma. Come per la prima versione, i sorgenti completi sono prelevabili +da dove avete prelevato questo tutorial. + +<sect2> Ulteriori sofisticazioni <label id="sec_Further_Sophistications"> + +<p> +Anche se ora il nostro programma supporta XInput pittosto bene, gli mancano +alcune caratteristiche che probabilmente vorremmo mettere in una applicazione +completa. In primo luogo, probabilmente all'utente non farà piacere dover +configurare i propri dispositivi ogni volta che lanciano il programma, per +cui dovremmo dare la possibilità di salvare la configurazione dei dispositivi. +Ciò può essere fatto scorrendo la lista restituita da <tt/gdk_input_list_devices()/ +e scrivendo la configurazione su di un file. + +<p> +Per tornare allo stato salvato la prossima volta che il programma viene +eseguito, GDK mette a disposizione delle funzioni per cambiare la configurazione +dei dispositivi: + +<tscreen><verb> +gdk_input_set_extension_events() +gdk_input_set_source() +gdk_input_set_mode() +gdk_input_set_axes() +gdk_input_set_key() +</verb></tscreen> + +(La lista restituita da <tt/gdk_input_list_devices()/ non dovrebbe +essere modificata direttamente.) Un esempio di come fare può essere +trovato nel programma di disegno gsumi (disponibile da <htmlurl +url="http://www.msc.cornell.edu/~otaylor/gsumi/" +name="http://www.msc.cornell.edu/~otaylor/gsumi/">). Sarebbe bello +avere alla fine un modo standard di recuperare le informazioni per tutte +le applicazioni. Questo probabilmente appartiene ad un livello un po' +più elevato ripetto a GTK, forse alla libreria GNOME. + +<p> +Un'altra notevole omissione a cui abbiamo accennato precedentemente è il +fatto di non disegnare il cursore direttamente. Piattaforme diverse da +XFree86 non permettono in questo momento di usare contemporaneamente un +dispositivo sia come puntatore principale sia direttamente da una +applicazione. Vedere <url url="http://www.msc.cornell.edu/~otaylor/xinput/XInput-HOWTO.html" +name="XInput-HOWTO"> per ulteriori informazioni. Ciò significa che le +applicazioni che vogliono rivolgersi al pubblico più ampio dovranno prevedere +di disegnare esse stesse il proprio cursore. + +<p> +Un'applicazione che voglia disegnare il proprio cursore dovrà fare due cose: +determinare se il dispositivo corrente necessita che venga disegnato un +cursore, e determinare se il dispositivo corrente è in prossimità. (Se il +dispositivo è una tavoletta grafica, un tocco di finezza è fare sparire +il puntatore quando lo stilo viene sollevato dalla tavoletta. Quando c'è +contatto fra lo stilo e la tavoletta, si dice che il dispositivo è ``in +prossimità".) La prima cosa viene fatta scorrendo la lista dei dispositivi, +come abbiamo fatto per trovare il nome del dispositivo. La seconda cosa +viene ottenuta selezionando gli eventi ``proximity_out''. Un esempio di +disegno del proprio cursore si trova nel programma 'testinput' incluso nella +distribuzione di GTK. + +<sect>Consigli per scrivere Applicazioni GTK + +<p> + +Questa sezione è semplicemente una raccolta di saggezza, una +guida di stile e un aiuto per creare buone applicazioni GTK. E' totalmente +inutile per ora perché è solamente un appunto. + +Usa autoconf e automake! Sono tuoi amici :) Ho intenzione di fare una +piccola introduzione su di loro qui. + +<sect>Contributi +<p> + +Questo documento, come molti altri grandi software fuori di qui, è stato +creato da volontari. Se sai tutto quello che c'è da sapere su GTK e non +lo hai trovato qui allora considera la possibilità di contribuire a questo +documento. + +<p> +Se decidi di contribuire, per favore trasmettimi il tuo testo a +<tt><htmlurl url="mailto:slow@intergate.bc.ca" +name="slow@intergate.bc.ca"></tt>. Inoltre, Si consapevole che l'intero +documento è ``free'', e ogni tua aggiunta sarà considerata allo stesso modo. +Per questo motivo le persone possono usare porzioni dei tuoi esempi nei loro +programmi, copie di questo documento possono essere distribuite all'infinito, +ecc... + +<p> + +Grazie. + + +<sect>Credits +<p> +Voglio qui ringraziare le persone che seguono, per il loro contributo +alla stesura di questo testo. + +<itemize> +<item>Bawer Dagdeviren, <tt><htmlurl url="mailto:chamele0n@geocities.com" +name="chamele0n@geocities.com"></tt> per il tutorial sui menù. + +<item>Raph Levien, <tt><htmlurl url="mailto:raph@acm.org" + name="raph@acm.org"></tt> +per il "hello world" alla GTK, l'immpacchettamento del widget, e in generale +per tutta la sua saggezza. +Lui ha anche donato una casa per questo tutorial. + +<item>Peter Mattis, <tt><htmlurl url="mailto:petm@xcf.berkeley.edu" +name="petm@xcf.berkeley.edu"></tt> Per il più semplice programma GTK e l'abilità +di farlo. :) + +<item>Werner Koch <tt><htmlurl url="mailto:werner.koch@guug.de" +name="werner.koch@guug.de"></tt> per la conversione da testo semplice a SGML +e la gerarchia delle classi di widget. + +<item>Mark Crichton <tt><htmlurl url="mailto:crichton@expert.cc.purdue.edu" +name="crichton@expert.cc.purdue.edu"></tt> per il codice della "MenuFactory" +e per la parte sull'impacchettamento nelle tabelle del tutorial. + +<item>Owen Taylor <tt><htmlurl url="mailto:owt1@cornell.edu" +name="mailto:owt1@cornell.edu"></tt> per la sezione del widget EventBox +(e il patch alla distribuzione). Lui è anche responsabile per il codice +e il tutorial delle selezioni, come per la sezione sulla scrittura di un +proprio widget, e l'applicazione d'esempio. Grazie di tutto Owen. + +<item>Mark VanderBoom <tt><htmlurl url="mailto:mvboom42@calvin.edu" +name="mailto:mailto:mvboom42@calvin.edu"></tt> per il suo meraviglioso lavoro +sul Notebook, Progres Bar, Dialogs e File selection. Grazie molto Mark. Sei +stato di grande aiuto. + +<item>Tim Janik <tt><htmlurl url="mailto:timj@psynet.net" +name="mailto:timj@psynet.net"></tt> per il suo grande lavoro sul widget List. +Grazie Tim :) + +<item> Michael K. Johnson <tt><htmlurl url="mailto:johnsonm@redhat.com" +name="johnsonm@redhat.com"> </tt> per le informazioni e il codice dei menu +a comparsa. + +</itemize> +<p> +E a tutti voi che avete fatto commenti e avete aiutato a raffinare questo documento. +<p> + +Thanks. + +<sect> Copying +<p> +This tutorial is Copyright (c) 1997 Ian Main + +La traduzione italiana è sotto Copyright (c) 1997-1998 di Michel Morelli, +Daniele Canazza e Antonio Schifano. + +<p> + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. +<p> +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +<p> +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +</article> + diff --git a/docs/gtkfaq_fix b/docs/gtkdocs_fix index 5038cc9ba..d13daa688 100755 --- a/docs/gtkfaq_fix +++ b/docs/gtkdocs_fix @@ -1,4 +1,4 @@ -#!/usr/bin/perl -w +#!/usr/bin/perl # Stupid script to fix look of html files created with sgml2html... @@ -8,3 +8,4 @@ foreach (@ARGV) { system("sed -e 's/<BODY>/<BODY BGCOLOR=\"#FFFFFF\">/g' -e 's/<HR>/<HR NOSHADE>/g' $_.orig > $_"); unlink("$_.orig"); } + diff --git a/docs/tutorial/gtk_tut.sgml b/docs/tutorial/gtk_tut.sgml new file mode 100644 index 000000000..1406c5e5d --- /dev/null +++ b/docs/tutorial/gtk_tut.sgml @@ -0,0 +1,9092 @@ +<!doctype linuxdoc system> + +<!-- This is the tutorial marked up in SGML + (just to show how to write a comment) +--> + +<article> +<title>GTK Tutorial +<author>Ian Main <tt><htmlurl url="mailto:slow@intergate.bc.ca" + name="<slow@intergate.bc.ca>"></tt>, +Tony Gale <tt><htmlurl url="mailto:gale@gimp.org" + name="<gale@gimp.org>"></tt +<date>March 8th, 1998 + +<!-- ***************************************************************** --> +<sect>Introduction +<!-- ***************************************************************** --> +<p> +GTK (GIMP Toolkit) was originally developed as a toolkit for the GIMP +(General Image Manipulation Program). GTK is built on top of GDK (GIMP +Drawing Kit) which is basically wrapper around the Xlib functions. It's +called the GIMP toolkit because it was original written for developing +the GIMP, but has now been used in several free software projects. The +authors are +<itemize> +<item> Peter Mattis <tt><htmlurl url="mailto:petm@xcf.berkeley.edu" + name="petm@xcf.berkeley.edu"></tt> +<item> Spencer Kimball <tt><htmlurl url="mailto:spencer@xcf.berkeley.edu" + name="spencer@xcf.berkeley.edu"></tt> +<item> Josh MacDonald <tt><htmlurl url="mailto:jmacd@xcf.berkeley.edu" + name="jmacd@xcf.berkeley.edu"></tt> +</itemize> + +<p> +GTK is essentially an object oriented application programmers interface (API). +Although written completely in +C, it is implemented using the idea of classes and callback functions +(pointers to functions). +<p> +There is also a third component called glib which contains a few +replacements for some standard calls, as well as some additional functions +for handling linked lists etc. The replacement functions are used to +increase GTK's portability, as some of the functions implemented +here are not available or are nonstandard on other unicies such as +g_strerror(). Some also contain enhancements to the libc versions such as +g_malloc has enhanced debugging utilities. +<p> +This tutorial is an attempt to document as much as possible of GTK, it is by +no means complete. This +tutorial assumes a good understanding of C, and how to create C programs. +It would be a great benefit for the reader to have previous X programming +experience, but it shouldn't be necessary. If you are learning GTK as your +first widget set, please comment on how you found this tutorial, and what +you had troubles with. +Note that there is also a C++ API for GTK (GTK--) in the works, so if you +prefer to use C++, you should look into this instead. There's also an +Objective C wrapper, and guile bindings available, but I don't follow these. +<p> +I would very much like to hear any problems you have learning GTK from this +document, and would appreciate input as to how it may be improved. + +<!-- ***************************************************************** --> +<sect>Getting Started +<!-- ***************************************************************** --> + +<p> +The first thing to do of course, is download the GTK source and install +it. You can always get the latest version from ftp.gimp.org in /pub/gtk. +You can also view other sources of GTK information on http://www.gimp.org/gtk +GTK uses GNU autoconf for +configuration. Once untar'd, type ./configure --help to see a list of options. +<p> +To begin our introduction to GTK, we'll start with the simplest program +possible. This program will +create a 200x200 pixel window and has no way of exiting except to be +killed using the shell. + +<tscreen><verb> +#include <gtk/gtk.h> + +int main (int argc, char *argv[]) +{ + GtkWidget *window; + + gtk_init (&argc, &argv); + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_widget_show (window); + + gtk_main (); + + return 0; +} +</verb></tscreen> + +All programs will of course include the gtk/gtk.h which declares the +variables, functions, structures etc. that will be used in your GTK +application. +<p> +The next line: + +<tscreen><verb> +gtk_init (&argc, &argv); +</verb></tscreen> + +calls the function gtk_init(gint *argc, gchar ***argv) which will be +called in all GTK applications. This sets up a few things for us such +as the default visual and color map and then proceeds to call +gdk_init(gint *argc, gchar ***argv). This function initializes the +library for use, sets up default signal handlers, and checks the +arguments passed to your application on the command line, looking for one +of the following: + +<itemize> +<item> <tt/--display/ +<item> <tt/--debug-level/ +<item> <tt/--no-xshm/ +<item> <tt/--sync/ +<item> <tt/--show-events/ +<item> <tt/--no-show-events/ +</itemize> +<p> +It removes these from the argument list, leaving anything it does +not recognize for your application to parse or ignore. This creates a set +of standard arguments excepted by all GTK applications. +<p> +The next two lines of code create and display a window. + +<tscreen><verb> + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_widget_show (window); +</verb></tscreen> + +The GTK_WINDOW_TOPLEVEL argument specifies that we want the window to +undergo window manager decoration and placement. Rather than create a +window of 0x0 size, a window without children is set to 200x200 by default +so you can still manipulate it. +<p> +The gtk_widget_show() function, lets GTK know that we are done setting the +attributes of this widget, and it can display it. +<p> +The last line enters the GTK main processing loop. + +<tscreen><verb> +gtk_main (); +</verb></tscreen> + +gtk_main() is another call you will see in every GTK application. When +control reaches this point, GTK will sleep waiting for X events (such as +button or key presses), timeouts, or file IO notifications to occur. +In our simple example however, events are ignored. + +<!-- ----------------------------------------------------------------- --> +<sect1>Hello World in GTK +<p> +OK, now for a program with a widget (a button). It's the classic hello +world ala GTK. + +<tscreen><verb> +/* helloworld.c */ + +#include <gtk/gtk.h> + +/* this is a callback function. the data arguments are ignored in this example.. + * More on callbacks below. */ +void hello (GtkWidget *widget, gpointer data) +{ + g_print ("Hello World\n"); +} + +gint delete_event(GtkWidget *widget, gpointer data) +{ + g_print ("delete event occured\n"); + /* if you return TRUE in the "delete_event" signal handler, + * GTK will emit the "destroy" signal. Returning FALSE means + * you don't want the window to be destroyed. + * This is useful for popping up 'are you sure you want to quit ?' + * type dialogs. */ + + /* Change FALSE to TRUE and the main window will be destroyed with + * a "delete_event". */ + + return (FALSE); +} + +/* another callback */ +void destroy (GtkWidget *widget, gpointer data) +{ + gtk_main_quit (); +} + +int main (int argc, char *argv[]) +{ + /* GtkWidget is the storage type for widgets */ + GtkWidget *window; + GtkWidget *button; + + /* this is called in all GTK applications. arguments are parsed from + * the command line and are returned to the application. */ + gtk_init (&argc, &argv); + + /* create a new window */ + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + + /* when the window is given the "delete_event" signal (this is given + * by the window manager (usually the 'close' option, or on the + * titlebar), we ask it to call the delete_event () function + * as defined above. The data passed to the callback + * function is NULL and is ignored in the callback. */ + gtk_signal_connect (GTK_OBJECT (window), "delete_event", + GTK_SIGNAL_FUNC (delete_event), NULL); + + /* here we connect the "destroy" event to a signal handler. + * This event occurs when we call gtk_widget_destroy() on the window, + * or if we return 'TRUE' in the "delete_event" callback. */ + gtk_signal_connect (GTK_OBJECT (window), "destroy", + GTK_SIGNAL_FUNC (destroy), NULL); + + /* sets the border width of the window. */ + gtk_container_border_width (GTK_CONTAINER (window), 10); + + /* creates a new button with the label "Hello World". */ + button = gtk_button_new_with_label ("Hello World"); + + /* When the button receives the "clicked" signal, it will call the + * function hello() passing it NULL as it's argument. The hello() function is + * defined above. */ + gtk_signal_connect (GTK_OBJECT (button), "clicked", + GTK_SIGNAL_FUNC (hello), NULL); + + /* This will cause the window to be destroyed by calling + * gtk_widget_destroy(window) when "clicked. Again, the destroy + * signal could come from here, or the window manager. */ + gtk_signal_connect_object (GTK_OBJECT (button), "clicked", + GTK_SIGNAL_FUNC (gtk_widget_destroy), + GTK_OBJECT (window)); + + /* this packs the button into the window (a gtk container). */ + gtk_container_add (GTK_CONTAINER (window), button); + + /* the final step is to display this newly created widget... */ + gtk_widget_show (button); + + /* and the window */ + gtk_widget_show (window); + + /* all GTK applications must have a gtk_main(). Control ends here + * and waits for an event to occur (like a key press or mouse event). */ + gtk_main (); + + return 0; +} +</verb></tscreen> + +<!-- ----------------------------------------------------------------- --> +<sect1>Compiling Hello World +<p> +To compile use: + +<tscreen><verb> +gcc -Wall -g helloworld.c -o hello_world -L/usr/X11R6/lib \ + -lgtk -lgdk -lglib -lX11 -lXext -lm +</verb></tscreen> +<p> +The libraries above must all be in your default search paths, if not, add +-L<library directory> and gcc will look in these directories for +the needed +libraries. For instance, on my Debian Linux system, I have to add +<tt>-L/usr/X11R6/lib</> for it to find the X11 libraries. +<p> +The order of the libraries are significant. The linker has to know what +functions it needs from a library before it processes it. +<p> +The libraries we are linking in are: +<itemize> +<item>The GTK library (-lgtk), the widget library, based on top of GDK. +<item>The GDK library (-lgdk), the Xlib wrapper. +<item>The glib library (-lglib), containing miscellaneous functions, only +g_print() is used in this particular example. GTK is built on top +of glib so you will always require this library. See the section on +<ref id="sec_glib" name="glib"> for details. +<item>The Xlib library (-lX11) which is used by GDK. +<item>The Xext library (-lXext). This contains code for shared memory +pixmaps and other X extensions. +<item>The math library (-lm). This is used by GTK for various purposes. +</itemize> + +<!-- ----------------------------------------------------------------- --> +<sect1>Theory of Signals and Callbacks +<p> +Before we look in detail at hello world, we'll discuss events and callbacks. +GTK is an event driven toolkit, which means it will sleep in +gtk_main until an event occurs and control is passed to the appropriate +function. +<p> +This passing of control is done using the idea of "signals". When an +event occurs, such as the press of a mouse button, the +appropriate signal will be "emitted" by the widget that was pressed. +This is how GTK does +most of its useful work. To make a button perform an action, +we set up a signal handler to catch these +signals and call the appropriate function. This is done by using a +function such as: + +<tscreen><verb> +gint gtk_signal_connect (GtkObject *object, + gchar *name, + GtkSignalFunc func, + gpointer func_data); +</verb></tscreen> +<p> +Where the first argument is the widget which will be emitting the signal, and +the second, the name of the signal you wish to catch. The third is the function +you wish to be called when it is caught, and the fourth, the data you wish +to have passed to this function. +<p> +The function specified in the third argument is called a "callback +function", and should be of the form: + +<tscreen><verb> +void callback_func(GtkWidget *widget, gpointer *callback_data); +</verb></tscreen> +<p> +Where the first argument will be a pointer to the widget that emitted the signal, and +the second, a pointer to the data given as the last argument to the +gtk_signal_connect() function as shown above. +<p> +Another call used in the hello world example, is: + +<tscreen><verb> +gint gtk_signal_connect_object (GtkObject *object, + gchar *name, + GtkSignalFunc func, + GtkObject *slot_object); +</verb></tscreen> +<p> +gtk_signal_connect_object() is the same as gtk_signal_connect() except that +the callback function only uses one argument, a +pointer to a GTK +object. So when using this function to connect signals, the callback should be of +the form: + +<tscreen><verb> +void callback_func (GtkObject *object); +</verb></tscreen> +<p> +Where the object is usually a widget. We usually don't setup callbacks for +gtk_signal_connect_object however. They are usually used +to call a GTK function that accept a single widget or object as an +argument, as is the case in our hello world example. + +The purpose of having two functions to connect signals is simply to allow +the callbacks to have a different number of arguments. Many functions in +the GTK library accept only a single GtkWidget pointer as an argument, so you +want to use the gtk_signal_connect_object() for these, whereas for your +functions, you may need to have additional data supplied to the callbacks. + +<!-- ----------------------------------------------------------------- --> +<sect1>Stepping Through Hello World +<p> +Now that we know the theory behind this, lets clarify by walking through +the example hello world program. +<p> +Here is the callback function that will be called when the button is +"clicked". We ignore both the widget and the data in this example, but it +is not hard to do things with them. The next example will use the data +argument to tell us which button was pressed. + +<tscreen><verb> +void hello (GtkWidget *widget, gpointer *data) +{ + g_print ("Hello World\n"); +} +</verb></tscreen> + +<p> +This callback is a bit special. The "delete_event" occurs when the +window manager sends this event to the application. We have a choice here +as to what to do about these events. We can ignore them, make some sort of +response, or simply quit the application. + +The value you return in this callback lets GTK know what action to take. +By returning FALSE, we let it know that we don't want to have the "destroy" +signal emitted, keeping our application running. By returning TRUE, we +ask that "destroy" is emitted, which in turn will call our "destroy" +signal handler. + +<tscreen><verb> +gint delete_event(GtkWidget *widget, gpointer data) +{ + g_print ("delete event occured\n"); + + return (FALSE); +} +</verb></tscreen> + +<p> +Here is another callback function which just quits by calling +gtk_main_quit(). Not really much to say about this, it is pretty self +explanatory. +<tscreen><verb> +void destroy (GtkWidget *widget, gpointer *data) +{ + gtk_main_quit (); +} +</verb></tscreen> +<p> +I assume you know about the main() function... yes, as with other +applications, all GTK applications will also have one of these. +<tscreen><verb> +int main (int argc, char *argv[]) +{ +</verb></tscreen> +<p> +This next part, declares a pointer to a structure of type GtkWidget. These +are used below to create a window and a button. +<tscreen><verb> + GtkWidget *window; + GtkWidget *button; +</verb></tscreen> +<p> +Here is our gtk_init again. As before, this initializes the toolkit, and +parses the arguments found on the command line. Any argument it +recognizes from the command line, it removes from the list, and modifies +argc and argv to make it look like they never existed, allowing your +application to parse the remaining arguments. +<tscreen><verb> + gtk_init (&argc, &argv); +</verb></tscreen> +<p> +Create a new window. This is fairly straight forward. Memory is allocated +for the GtkWidget *window structure so it now points to a valid structure. +It sets up a new window, but it is not displayed until below where we call +gtk_widget_show(window) near the end of our program. +<tscreen><verb> + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); +</verb></tscreen> +<p> +Here is an example of connecting a signal handler to an object, in this case, the +window. Here, the "destroy" signal is caught. This is emitted when we use +the window manager to kill the window (and we return TRUE in the +"delete_event" handler), or when we use the +gtk_widget_destroy() call passing in the window widget as the object to +destroy. By setting this up, we handle both cases with a single call. +Here, it just calls the destroy() function defined above with a NULL +argument, which quits GTK for us. +<p> +The GTK_OBJECT and GTK_SIGNAL_FUNC are macros that perform type casting and +checking for us, as well as aid the readability of the code. +<tscreen><verb> + gtk_signal_connect (GTK_OBJECT (window), "destroy", + GTK_SIGNAL_FUNC (destroy), NULL); +</verb></tscreen> +<p> +This next function is used to set an attribute of a container object. +This just sets the window +so it has a blank area along the inside of it 10 pixels wide where no +widgets will go. There are other similar functions which we will look at +in the section on +<ref id="sec_setting_widget_attributes" name="Setting Widget Attributes"> +<p> +And again, GTK_CONTAINER is a macro to perform type casting. +<tscreen><verb> + gtk_container_border_width (GTK_CONTAINER (window), 10); +</verb></tscreen> +<p> +This call creates a new button. It allocates space for a new GtkWidget +structure in memory, initializes it, and makes the button pointer point to +it. It will have the label "Hello World" on it when displayed. +<tscreen><verb> + button = gtk_button_new_with_label ("Hello World"); +</verb></tscreen> +<p> +Here, we take this button, and make it do something useful. We attach a +signal handler to it so when it emits the "clicked" signal, our hello() +function is called. The data is ignored, so we simply pass in NULL to the +hello() callback function. Obviously, the "clicked" signal is emitted when +we click the button with our mouse pointer. + +<tscreen><verb> + gtk_signal_connect (GTK_OBJECT (button), "clicked", + GTK_SIGNAL_FUNC (hello), NULL); +</verb></tscreen> +<p> +We are also going to use this button to exit our program. This will +illustrate how the "destroy" +signal may come from either the window manager, or our program. When the +button is "clicked", same as above, it calls the first hello() callback function, +and then this one in the order they are set up. You may have as many +callback function as you need, and all will be executed in the order you +connected them. Because the gtk_widget_destroy() function accepts only a +GtkWidget *widget as an argument, we use the gtk_signal_connect_object() +function here instead of straight gtk_signal_connect(). + +<tscreen><verb> + gtk_signal_connect_object (GTK_OBJECT (button), "clicked", + GTK_SIGNAL_FUNC (gtk_widget_destroy), + GTK_OBJECT (window)); +</verb></tscreen> +<p> +This is a packing call, which will be explained in depth later on. But it +is fairly easy to understand. It simply tells GTK that the button is to be +placed in the window where it will be displayed. +<tscreen><verb> + gtk_container_add (GTK_CONTAINER (window), button); +</verb></tscreen> +<p> +Now that we have everything setup the way we want it to be. With all the +signal handlers in place, and the button placed in the window where it +should be, we ask GTK to "show" the widgets on the screen. The window +widget is shown last so the whole window will pop up at once rather than +seeing the window pop up, and then the button form inside of it. Although +with such simple example, you'd never notice. +<tscreen><verb> + gtk_widget_show (button); + + gtk_widget_show (window); +</verb></tscreen> +<p> +And of course, we call gtk_main() which waits for events to come from the X +server and will call on the widgets to emit signals when these events come. +<tscreen><verb> + gtk_main (); +</verb></tscreen> +And the final return. Control returns here after gtk_quit() is called. +<tscreen><verb> + return 0; +</verb></tscreen> +<p> +Now, when we click the mouse button on a GTK button, the +widget emits a "clicked" signal. In order for us to use this information, our +program sets up a signal handler to catch that signal, which dispatches the function +of our choice. In our example, when the button we created is "clicked", the +hello() function is called with a NULL +argument, and then the next handler for this signal is called. This calls +the gtk_widget_destroy() function, passing it the window widget as it's +argument, destroying the window widget. This causes the window to emit the +"destroy" signal, which is +caught, and calls our destroy() callback function, which simply exits GTK. +<p> +Another course of events, is to use the window manager to kill the window. +This will cause the "delete_event" to be emitted. This will call our +"delete_event" handler. If we return FALSE here, the window will be left as +is and nothing will happen. Returning TRUE will cause GTK to emit the +"destroy" signal which of course, calls the "destroy" callback, exiting GTK. +<p> +Note that these signals are not the same as the Unix system +signals, and are not implemented using them, although the terminology is +almost identical. + +<!-- ***************************************************************** --> +<sect>Moving On +<!-- ***************************************************************** --> + +<!-- ----------------------------------------------------------------- --> +<sect1>Data Types +<p> +There are a few things you probably noticed in the previous examples that +need explaining. The +gint, gchar etc. that you see are typedefs to int and char respectively. This is done +to get around that nasty dependency on the size of simple data types when doing calculations. +A good example is "gint32" which will be +typedef'd to a 32 bit integer for any given platform, whether it be the 64 bit +alpha, or the 32 bit i386. The +typedefs are very straight forward and intuitive. They are all defined in +glib/glib.h (which gets included from gtk.h). +<p> +You'll also notice the ability to use GtkWidget when the function calls for a GtkObject. +GTK is an object oriented design, and a widget is an object. + +<!-- ----------------------------------------------------------------- --> +<sect1>More on Signal Handlers +<p> +Lets take another look at the gtk_signal_connect declaration. + +<tscreen><verb> +gint gtk_signal_connect (GtkObject *object, gchar *name, + GtkSignalFunc func, gpointer func_data); +</verb></tscreen> + +Notice the gint return value ? This is a tag that identifies your callback +function. As said above, you may have as many callbacks per signal and per +object as you need, and each will be executed in turn, in the order they were attached. +This tag allows you to remove this callback from the list by using: +<tscreen><verb> +void gtk_signal_disconnect (GtkObject *object, + gint id); +</verb></tscreen> +So, by passing in the widget you wish to remove the handler from, and the +tag or id returned by one of the signal_connect functions, you can +disconnect a signal handler. +<p> +Another function to remove all the signal handers from an object is: +<tscreen><verb> +gtk_signal_handlers_destroy (GtkObject *object); +</verb></tscreen> +<p> +This call is fairly self explanatory. It simply removes all the current +signal handlers from the object passed in as the first argument. + +<!-- ----------------------------------------------------------------- --> +<sect1>An Upgraded Hello World +<p> +Let's take a look at a slightly improved hello world with better examples +of callbacks. This will also introduce us to our next topic, packing +widgets. + +<tscreen><verb> +/* helloworld2.c */ + +#include <gtk/gtk.h> + +/* Our new improved callback. The data passed to this function is printed + * to stdout. */ +void callback (GtkWidget *widget, gpointer *data) +{ + g_print ("Hello again - %s was pressed\n", (char *) data); +} + +/* another callback */ +void delete_event (GtkWidget *widget, gpointer *data) +{ + gtk_main_quit (); +} + +int main (int argc, char *argv[]) +{ + /* GtkWidget is the storage type for widgets */ + GtkWidget *window; + GtkWidget *button; + GtkWidget *box1; + + /* this is called in all GTK applications. arguments are parsed from + * the command line and are returned to the application. */ + gtk_init (&argc, &argv); + + /* create a new window */ + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + + /* this is a new call, this just sets the title of our + * new window to "Hello Buttons!" */ + gtk_window_set_title (GTK_WINDOW (window), "Hello Buttons!"); + + /* Here we just set a handler for delete_event that immediately + * exits GTK. */ + gtk_signal_connect (GTK_OBJECT (window), "delete_event", + GTK_SIGNAL_FUNC (delete_event), NULL); + + + /* sets the border width of the window. */ + gtk_container_border_width (GTK_CONTAINER (window), 10); + + /* we create a box to pack widgets into. this is described in detail + * in the "packing" section below. The box is not really visible, it + * is just used as a tool to arrange widgets. */ + box1 = gtk_hbox_new(FALSE, 0); + + /* put the box into the main window. */ + gtk_container_add (GTK_CONTAINER (window), box1); + + /* creates a new button with the label "Button 1". */ + button = gtk_button_new_with_label ("Button 1"); + + /* Now when the button is clicked, we call the "callback" function + * with a pointer to "button 1" as it's argument */ + gtk_signal_connect (GTK_OBJECT (button), "clicked", + GTK_SIGNAL_FUNC (callback), (gpointer) "button 1"); + + /* instead of gtk_container_add, we pack this button into the invisible + * box, which has been packed into the window. */ + gtk_box_pack_start(GTK_BOX(box1), button, TRUE, TRUE, 0); + + /* always remember this step, this tells GTK that our preparation for + * this button is complete, and it can be displayed now. */ + gtk_widget_show(button); + + /* do these same steps again to create a second button */ + button = gtk_button_new_with_label ("Button 2"); + + /* call the same callback function with a different argument, + * passing a pointer to "button 2" instead. */ + gtk_signal_connect (GTK_OBJECT (button), "clicked", + GTK_SIGNAL_FUNC (callback), (gpointer) "button 2"); + + gtk_box_pack_start(GTK_BOX(box1), button, TRUE, TRUE, 0); + + /* The order in which we show the buttons is not really important, but I + * recommend showing the window last, so it all pops up at once. */ + gtk_widget_show(button); + + gtk_widget_show(box1); + + gtk_widget_show (window); + + /* rest in gtk_main and wait for the fun to begin! */ + gtk_main (); + + return 0; +} +</verb></tscreen> +<p> +Compile this program using the same linking arguments as our first example. +You'll notice this time there is no easy way to exit the program, you have to use +your window manager or command line to kill it. A good exercise for the +reader would be to insert a third "Quit" button that will exit the +program. You may also wish to play with the options to +gtk_box_pack_start() while reading the next section. +Try resizing the window, and observe the behavior. +<p> +Just as a side note, there is another useful define for gtk_window_new() - +GTK_WINDOW_DIALOG. This interacts with the window manager a little +differently and should be used for transient windows. + +<!-- ***************************************************************** --> +<sect>Packing Widgets +<!-- ***************************************************************** --> + +<p> +When creating an application, you'll want to put more than one button +inside a window. Our first hello world example only used one widget so we +could simply use a gtk_container_add call to "pack" the widget into the +window. But when you want to put more than one widget into a window, how +do you control where that widget is positioned ? This is where packing +comes in. + +<!-- ----------------------------------------------------------------- --> +<sect1>Theory of Packing Boxes +<p> +Most packing is done by creating boxes as in the example above. These are +invisible widget containers that we can pack our widgets into and come in +two forms, a horizontal box, and a vertical box. When packing widgets +into a horizontal box, the objects are inserted horizontally from left to +right or right to left depending on the call used. In a vertical box, +widgets are packed from top to bottom or vice versa. You may use any +combination of boxes inside or beside other boxes to create the desired +effect. +<p> +To create a new horizontal box, we use a call to gtk_hbox_new(), and for +vertical boxes, gtk_vbox_new(). The gtk_box_pack_start() and +gtk_box_pack_end() functions are used to place objects inside of these +containers. The gtk_box_pack_start() function will start at the top and +work its way down in a vbox, and pack left to right in an hbox. +gtk_box_pack_end() will do the opposite, packing from bottom to top in a +vbox, and right to left in an hbox. Using these functions allow us to +right justify or left justify our widgets and may be mixed in any way to +achieve the desired effect. We will use gtk_box_pack_start() in most of +our examples. An object may be another container or a widget. And in +fact, many widgets are actually containers themselves including the +button, but we usually only use a label inside a button. +<p> +By using these calls, GTK knows where you want to place your widgets so it +can do automatic resizing and other nifty things. there's also a number +of options as to how your widgets should be packed. As you can imagine, +this method gives us a quite a bit of flexibility when placing and +creating widgets. + +<!-- ----------------------------------------------------------------- --> +<sect1>Details of Boxes +<p> +Because of this flexibility, packing boxes in GTK can be confusing at +first. There are a lot of options, and it's not immediately obvious how +they all fit together. In the end however, there are basically five +different styles you can get. + +<p> +<? <CENTER> > +<? +<IMG SRC="packbox1.gif" VSPACE="15" HSPACE="10" WIDTH="528" HEIGHT="235" +ALT="Box Packing Example Image"> +> +<? </CENTER> > + +Each line contains one horizontal box (hbox) with several buttons. The +call to gtk_box_pack is shorthand for the call to pack each of the buttons +into the hbox. Each of the buttons is packed into the hbox the same way +(i.e. same arguments to the gtk_box_pack_start () function). +<p> +This is the declaration of the gtk_box_pack_start function. + +<tscreen><verb> +void gtk_box_pack_start (GtkBox *box, + GtkWidget *child, + gint expand, + gint fill, + gint padding); +</verb></tscreen> + +The first argument is the box you are packing the object into, the second +is this object. The objects will all be buttons for now, so we'll be +packing buttons into boxes. +<p> +The expand argument to gtk_box_pack_start() or gtk_box_pack_end() controls +whether the widgets are laid out in the box to fill in all the extra space +in the box so the box is expanded to fill the area alloted to it (TRUE). +Or the box is shrunk to just fit the widgets (FALSE). Setting expand to +FALSE will allow you to do right and left +justifying of your widgets. Otherwise, they will all expand to fit in the +box, and the same effect could be achieved by using only one of +gtk_box_pack_start or pack_end functions. +<p> +The fill argument to the gtk_box_pack functions control whether the extra +space is allocated to the objects themselves (TRUE), or as extra padding +in the box around these objects (FALSE). It only has an effect if the +expand argument is also TRUE. +<p> +When creating a new box, the function looks like this: + +<tscreen><verb> +GtkWidget * gtk_hbox_new (gint homogeneous, + gint spacing); +</verb></tscreen> + +The homogeneous argument to gtk_hbox_new (and the same for gtk_vbox_new) +controls whether each object in the box has the same size (i.e. the same +width in an hbox, or the same height in a vbox). If it is set, the expand +argument to the gtk_box_pack routines is always turned on. +<p> +What's the difference between spacing (set when the box is created) and +padding (set when elements are packed)? Spacing is added between objects, +and padding is added on either side of an object. The following figure +should make it clearer: + +<? <CENTER> > +<? +<IMG ALIGN="center" SRC="packbox2.gif" WIDTH="509" HEIGHT="213" +VSPACE="15" HSPACE="10" ALT="Box Packing Example Image"> +> +<? </CENTER> > + +Here is the code used to create the above images. I've commented it fairly +heavily so hopefully you won't have any problems following it. Compile it yourself +and play with it. + +<!-- ----------------------------------------------------------------- --> +<sect1>Packing Demonstration Program +<p> +<tscreen><verb> +/* packbox.c */ + +#include "gtk/gtk.h" + +void +delete_event (GtkWidget *widget, gpointer *data) +{ + gtk_main_quit (); +} + +/* Make a new hbox filled with button-labels. Arguments for the + * variables we're interested are passed in to this function. + * We do not show the box, but do show everything inside. */ +GtkWidget *make_box (gint homogeneous, gint spacing, + gint expand, gint fill, gint padding) +{ + GtkWidget *box; + GtkWidget *button; + char padstr[80]; + + /* create a new hbox with the appropriate homogeneous and spacing + * settings */ + box = gtk_hbox_new (homogeneous, spacing); + + /* create a series of buttons with the appropriate settings */ + button = gtk_button_new_with_label ("gtk_box_pack"); + gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding); + gtk_widget_show (button); + + button = gtk_button_new_with_label ("(box,"); + gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding); + gtk_widget_show (button); + + button = gtk_button_new_with_label ("button,"); + gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding); + gtk_widget_show (button); + + /* create a button with the label depending on the value of + * expand. */ + if (expand == TRUE) + button = gtk_button_new_with_label ("TRUE,"); + else + button = gtk_button_new_with_label ("FALSE,"); + + gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding); + gtk_widget_show (button); + + /* This is the same as the button creation for "expand" + * above, but uses the shorthand form. */ + button = gtk_button_new_with_label (fill ? "TRUE," : "FALSE,"); + gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding); + gtk_widget_show (button); + + sprintf (padstr, "%d);", padding); + + button = gtk_button_new_with_label (padstr); + gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding); + gtk_widget_show (button); + + return box; +} + +int +main (int argc, char *argv[]) +{ + GtkWidget *window; + GtkWidget *button; + GtkWidget *box1; + GtkWidget *box2; + GtkWidget *separator; + GtkWidget *label; + GtkWidget *quitbox; + int which; + + /* Our init, don't forget this! :) */ + gtk_init (&argc, &argv); + + if (argc != 2) { + fprintf (stderr, "usage: packbox num, where num is 1, 2, or 3.\n"); + /* this just does cleanup in GTK, and exits with an exit status of 1. */ + gtk_exit (1); + } + + which = atoi (argv[1]); + + /* Create our window */ + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + + /* You should always remember to connect the destroy signal to the + * main window. This is very important for proper intuitive + * behavior */ + gtk_signal_connect (GTK_OBJECT (window), "delete_event", + GTK_SIGNAL_FUNC (delete_event), NULL); + gtk_container_border_width (GTK_CONTAINER (window), 10); + + /* We create a vertical box (vbox) to pack the horizontal boxes into. + * This allows us to stack the horizontal boxes filled with buttons one + * on top of the other in this vbox. */ + box1 = gtk_vbox_new (FALSE, 0); + + /* which example to show. These correspond to the pictures above. */ + switch (which) { + case 1: + /* create a new label. */ + label = gtk_label_new ("gtk_hbox_new (FALSE, 0);"); + + /* Align the label to the left side. We'll discuss this function and + * others in the section on Widget Attributes. */ + gtk_misc_set_alignment (GTK_MISC (label), 0, 0); + + /* Pack the label into the vertical box (vbox box1). Remember that + * widgets added to a vbox will be packed one on top of the other in + * order. */ + gtk_box_pack_start (GTK_BOX (box1), label, FALSE, FALSE, 0); + + /* show the label */ + gtk_widget_show (label); + + /* call our make box function - homogeneous = FALSE, spacing = 0, + * expand = FALSE, fill = FALSE, padding = 0 */ + box2 = make_box (FALSE, 0, FALSE, FALSE, 0); + gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0); + gtk_widget_show (box2); + + /* call our make box function - homogeneous = FALSE, spacing = 0, + * expand = FALSE, fill = FALSE, padding = 0 */ + box2 = make_box (FALSE, 0, TRUE, FALSE, 0); + gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0); + gtk_widget_show (box2); + + /* Args are: homogeneous, spacing, expand, fill, padding */ + box2 = make_box (FALSE, 0, TRUE, TRUE, 0); + gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0); + gtk_widget_show (box2); + + /* creates a separator, we'll learn more about these later, + * but they are quite simple. */ + separator = gtk_hseparator_new (); + + /* pack the separator into the vbox. Remember each of these + * widgets are being packed into a vbox, so they'll be stacked + * vertically. */ + gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 5); + gtk_widget_show (separator); + + /* create another new label, and show it. */ + label = gtk_label_new ("gtk_hbox_new (TRUE, 0);"); + gtk_misc_set_alignment (GTK_MISC (label), 0, 0); + gtk_box_pack_start (GTK_BOX (box1), label, FALSE, FALSE, 0); + gtk_widget_show (label); + + /* Args are: homogeneous, spacing, expand, fill, padding */ + box2 = make_box (TRUE, 0, TRUE, FALSE, 0); + gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0); + gtk_widget_show (box2); + + /* Args are: homogeneous, spacing, expand, fill, padding */ + box2 = make_box (TRUE, 0, TRUE, TRUE, 0); + gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0); + gtk_widget_show (box2); + + /* another new separator. */ + separator = gtk_hseparator_new (); + /* The last 3 arguments to gtk_box_pack_start are: expand, fill, padding. */ + gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 5); + gtk_widget_show (separator); + + break; + + case 2: + + /* create a new label, remember box1 is a vbox as created + * near the beginning of main() */ + label = gtk_label_new ("gtk_hbox_new (FALSE, 10);"); + gtk_misc_set_alignment (GTK_MISC (label), 0, 0); + gtk_box_pack_start (GTK_BOX (box1), label, FALSE, FALSE, 0); + gtk_widget_show (label); + + /* Args are: homogeneous, spacing, expand, fill, padding */ + box2 = make_box (FALSE, 10, TRUE, FALSE, 0); + gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0); + gtk_widget_show (box2); + + /* Args are: homogeneous, spacing, expand, fill, padding */ + box2 = make_box (FALSE, 10, TRUE, TRUE, 0); + gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0); + gtk_widget_show (box2); + + separator = gtk_hseparator_new (); + /* The last 3 arguments to gtk_box_pack_start are: expand, fill, padding. */ + gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 5); + gtk_widget_show (separator); + + label = gtk_label_new ("gtk_hbox_new (FALSE, 0);"); + gtk_misc_set_alignment (GTK_MISC (label), 0, 0); + gtk_box_pack_start (GTK_BOX (box1), label, FALSE, FALSE, 0); + gtk_widget_show (label); + + /* Args are: homogeneous, spacing, expand, fill, padding */ + box2 = make_box (FALSE, 0, TRUE, FALSE, 10); + gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0); + gtk_widget_show (box2); + + /* Args are: homogeneous, spacing, expand, fill, padding */ + box2 = make_box (FALSE, 0, TRUE, TRUE, 10); + gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0); + gtk_widget_show (box2); + + separator = gtk_hseparator_new (); + /* The last 3 arguments to gtk_box_pack_start are: expand, fill, padding. */ + gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 5); + gtk_widget_show (separator); + break; + + case 3: + + /* This demonstrates the ability to use gtk_box_pack_end() to + * right justify widgets. First, we create a new box as before. */ + box2 = make_box (FALSE, 0, FALSE, FALSE, 0); + /* create the label that will be put at the end. */ + label = gtk_label_new ("end"); + /* pack it using gtk_box_pack_end(), so it is put on the right side + * of the hbox created in the make_box() call. */ + gtk_box_pack_end (GTK_BOX (box2), label, FALSE, FALSE, 0); + /* show the label. */ + gtk_widget_show (label); + + /* pack box2 into box1 (the vbox remember ? :) */ + gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0); + gtk_widget_show (box2); + + /* a separator for the bottom. */ + separator = gtk_hseparator_new (); + /* this explicitly sets the separator to 400 pixels wide by 5 pixels + * high. This is so the hbox we created will also be 400 pixels wide, + * and the "end" label will be separated from the other labels in the + * hbox. Otherwise, all the widgets in the hbox would be packed as + * close together as possible. */ + gtk_widget_set_usize (separator, 400, 5); + /* pack the separator into the vbox (box1) created near the start + * of main() */ + gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 5); + gtk_widget_show (separator); + } + + /* Create another new hbox.. remember we can use as many as we need! */ + quitbox = gtk_hbox_new (FALSE, 0); + + /* Our quit button. */ + button = gtk_button_new_with_label ("Quit"); + + /* setup the signal to destroy the window. Remember that this will send + * the "destroy" signal to the window which will be caught by our signal + * handler as defined above. */ + gtk_signal_connect_object (GTK_OBJECT (button), "clicked", + GTK_SIGNAL_FUNC (gtk_main_quit), + GTK_OBJECT (window)); + /* pack the button into the quitbox. + * The last 3 arguments to gtk_box_pack_start are: expand, fill, padding. */ + gtk_box_pack_start (GTK_BOX (quitbox), button, TRUE, FALSE, 0); + /* pack the quitbox into the vbox (box1) */ + gtk_box_pack_start (GTK_BOX (box1), quitbox, FALSE, FALSE, 0); + + /* pack the vbox (box1) which now contains all our widgets, into the + * main window. */ + gtk_container_add (GTK_CONTAINER (window), box1); + + /* and show everything left */ + gtk_widget_show (button); + gtk_widget_show (quitbox); + + gtk_widget_show (box1); + /* Showing the window last so everything pops up at once. */ + gtk_widget_show (window); + + /* And of course, our main function. */ + gtk_main (); + + /* control returns here when gtk_main_quit() is called, but not when + * gtk_exit is used. */ + + return 0; +} +</verb></tscreen> + +<!-- ----------------------------------------------------------------- --> +<sect1>Packing Using Tables +<p> +Let's take a look at another way of packing - Tables. These can be +extremely useful in certain situations. + +Using tables, we create a grid that we can place widgets in. The widgets +may take up as many spaces as we specify. + +The first thing to look at of course, is the gtk_table_new function: + +<tscreen><verb> +GtkWidget* gtk_table_new (gint rows, + gint columns, + gint homogeneous); +</verb></tscreen> +<p> +The first argument is the number of rows to make in the table, while the +second, obviously, the number of columns. + +The homogeneous argument has to do with how the table's boxes are sized. If homogeneous +is TRUE, the table boxes are resized to the size of the largest widget in the table. +If homogeneous is FALSE, the size of a table boxes is dictated by the tallest widget +in its same row, and the widest widget in its column. + +The rows and columnts are laid out starting with 0 to n, where n was the +number specified in the call to gtk_table_new. So, if you specify rows = 2 and +columns = 2, the layout would look something like this: + +<tscreen><verb> + 0 1 2 +0+----------+----------+ + | | | +1+----------+----------+ + | | | +2+----------+----------+ +</verb></tscreen> +<p> +Note that the coordinate system starts in the upper left hand corner. To place a +widget into a box, use the following function: + +<tscreen><verb> +void gtk_table_attach (GtkTable *table, + GtkWidget *child, + gint left_attach, + gint right_attach, + gint top_attach, + gint bottom_attach, + gint xoptions, + gint yoptions, + gint xpadding, + gint ypadding); +</verb></tscreen> +<p> +Where the first argument ("table") is the table you've created and the second +("child") the widget you wish to place in the table. + +The left and right attach +arguments specify where to place the widget, and how many boxes to use. If you want +a button in the lower right table entry +of our 2x2 table, and want it to fill that entry ONLY. left_attach would be = 1, +right_attach = 2, top_attach = 1, bottom_attach = 2. + +Now, if you wanted a widget to take up the whole +top row of our 2x2 table, you'd use left_attach = 0, right_attach =2, top_attach = 0, +bottom_attach = 1. + +The xoptions and yoptions are used to specify packing options and may be OR'ed +together to allow multiple options. + +These options are: +<itemize> +<item>GTK_FILL - If the table box is larger than the widget, and GTK_FILL is +specified, the widget will expand to use all the room available. + +<item>GTK_SHRINK - If the table widget was allocated less space then was +requested (usually by the user resizing the window), then the widgets would +normally just be pushed off the bottom of +the window and disappear. If GTK_SHRINK is specified, the widgets will +shrink with the table. + +<item>GTK_EXPAND - This will cause the table to expand to use up any remaining +space in the window. +</itemize> + +Padding is just like in boxes, creating a clear area around the widget +specified in pixels. + +gtk_table_attach() has a LOT of options. So, there's a shortcut: + +<tscreen><verb> +void gtk_table_attach_defaults (GtkTable *table, + GtkWidget *widget, + gint left_attach, + gint right_attach, + gint top_attach, + gint bottom_attach); +</verb></tscreen> + +The X and Y options default to GTK_FILL | GTK_EXPAND, and X and Y padding +are set to 0. The rest of the arguments are identical to the previous +function. + +We also have gtk_table_set_row_spacing() and gtk_table_set_col_spacing(). This places +spacing between the rows at the specified row or column. + +<tscreen><verb> +void gtk_table_set_row_spacing (GtkTable *table, + gint row, + gint spacing); +</verb></tscreen> +and +<tscreen><verb> +void gtk_table_set_col_spacing (GtkTable *table, + gint column, + gint spacing); +</verb></tscreen> + +Note that for columns, the space goes to the right of the column, and for rows, +the space goes below the row. + +You can also set a consistent spacing of all rows and/or columns with: + +<tscreen><verb> +void gtk_table_set_row_spacings (GtkTable *table, + gint spacing); +</verb></tscreen> +<p> +And, +<tscreen><verb> +void gtk_table_set_col_spacings (GtkTable *table, + gint spacing); +</verb></tscreen> +<p> +Note that with these calls, the last row and last column do not get any spacing + +<!-- ----------------------------------------------------------------- --> +<sect1>Table Packing Example +<p> +Here we make a window with three buttons in a 2x2 table. +The first two buttons will be placed in the upper row. +A third, quit button, is placed in the lower row, spanning both columns. +Which means it should look something like this: +<p> +<? <CENTER> > +<? +<IMG SRC="table.gif" VSPACE="15" HSPACE="10" +ALT="Table Packing Example Image" WIDTH="180" HEIGHT="120"> +> +<? </CENTER> > + +Here's the source code: + +<tscreen><verb> +/* table.c */ +#include <gtk/gtk.h> + +/* our callback. + * the data passed to this function is printed to stdout */ +void callback (GtkWidget *widget, gpointer *data) +{ + g_print ("Hello again - %s was pressed\n", (char *) data); +} + +/* this callback quits the program */ +void delete_event (GtkWidget *widget, gpointer *data) +{ + gtk_main_quit (); +} + +int main (int argc, char *argv[]) +{ + GtkWidget *window; + GtkWidget *button; + GtkWidget *table; + + gtk_init (&argc, &argv); + + /* create a new window */ + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + + /* set the window title */ + gtk_window_set_title (GTK_WINDOW (window), "Table"); + + /* set a handler for delete_event that immediately + * exits GTK. */ + gtk_signal_connect (GTK_OBJECT (window), "delete_event", + GTK_SIGNAL_FUNC (delete_event), NULL); + + /* sets the border width of the window. */ + gtk_container_border_width (GTK_CONTAINER (window), 20); + + /* create a 2x2 table */ + table = gtk_table_new (2, 2, TRUE); + + /* put the table in the main window */ + gtk_container_add (GTK_CONTAINER (window), table); + + /* create first button */ + button = gtk_button_new_with_label ("button 1"); + + /* when the button is clicked, we call the "callback" function + * with a pointer to "button 1" as it's argument */ + gtk_signal_connect (GTK_OBJECT (button), "clicked", + GTK_SIGNAL_FUNC (callback), (gpointer) "button 1"); + + + /* insert button 1 into the upper left quadrant of the table */ + gtk_table_attach_defaults (GTK_TABLE(table), button, 0, 1, 0, 1); + + gtk_widget_show (button); + + /* create second button */ + + button = gtk_button_new_with_label ("button 2"); + + /* when the button is clicked, we call the "callback" function + * with a pointer to "button 2" as it's argument */ + gtk_signal_connect (GTK_OBJECT (button), "clicked", + GTK_SIGNAL_FUNC (callback), (gpointer) "button 2"); + /* insert button 2 into the upper right quadrant of the table */ + gtk_table_attach_defaults (GTK_TABLE(table), button, 1, 2, 0, 1); + + gtk_widget_show (button); + + /* create "Quit" button */ + button = gtk_button_new_with_label ("Quit"); + + /* when the button is clicked, we call the "delete_event" function + * and the program exits */ + gtk_signal_connect (GTK_OBJECT (button), "clicked", + GTK_SIGNAL_FUNC (delete_event), NULL); + + /* insert the quit button into the both + * lower quadrants of the table */ + gtk_table_attach_defaults (GTK_TABLE(table), button, 0, 2, 1, 2); + + gtk_widget_show (button); + + gtk_widget_show (table); + gtk_widget_show (window); + + gtk_main (); + + return 0; +} +</verb></tscreen> + +You can compile this program with something like: + +<tscreen><verb> +gcc -g -Wall -ansi -o table table.c -L/usr/X11R6/lib \ + -lgdk -lgtk -lglib -lX11 -lXext -lm +</verb></tscreen> + +<!-- ***************************************************************** --> +<sect>Widget Overview +<!-- ***************************************************************** --> + +<p> +The general steps to creating a widget in GTK are: +<enum> +<item> gtk_*_new - one of various functions to create a new widget. These +are all detailed in this section. + +<item> Connect all signals we wish to use to the appropriate handlers. + +<item> Set the attributes of the widget. + +<item> Pack the widget into a container using the appropriate call such as +gtk_container_add() or gtk_box_pack_start(). + +<item> gtk_widget_show() the widget. +</enum> +<p> +gtk_widget_show() lets GTK know that we are done setting the attributes +of the widget, and it is ready to be displayed. You may also use +gtk_widget_hide to make it disappear again. The order in which you +show the widgets is not important, but I suggest showing the window +last so the whole window pops up at once rather than seeing the individual +widgets come up on the screen as they're formed. The children of a widget +(a window is a widget too) +will not be displayed until the window itself is shown using the +gtk_widget_show() function. + +<!-- ----------------------------------------------------------------- --> +<sect1> Casting +<p> +You'll notice as you go on, that GTK uses a type casting system. This is +always done using macros that both test the ability to cast the given item, +and perform the cast. Some common ones you will see are: + +<itemize> +<item> GTK_WIDGET(widget) +<item> GTK_OBJECT(object) +<item> GTK_SIGNAL_FUNC(function) +<item> GTK_CONTAINER(container) +<item> GTK_WINDOW(window) +<item> GTK_BOX(box) +</itemize> + +These are all used to cast arguments in functions. You'll see them in the +examples, and can usually tell when to use them simply by looking at the +function's declaration. + +As you can see below in the class hierarchy, all GtkWidgets are derived from +the GtkObject base class. This means you can use an widget in any place the +function asks for an object - simply use the GTK_OBJECT() macro. + +For example: + +<tscreen><verb> +gtk_signal_connect(GTK_OBJECT(button), "clicked", + GTK_SIGNAL_FUNC(callback_function), callback_data); +</verb></tscreen> + +This casts the button into an object, and provides a cast for the function +pointer to the callback. + +Many widgets are also containers. If you look in the class hierarchy below, +you'll notice that many widgets drive from the GtkContainer class. Any one +of those widgets may use with the GTK_CONTAINER macro to +pass them to functions that ask for containers. + +Unfortunately, these macros are not extensively covered in the tutorial, but I +recomend taking a look through the GTK header files. It can be very +educational. In fact, it's not difficult to learn how a widget works just +by looking at the function declarations. + +<!-- ----------------------------------------------------------------- --> +<sect1>Widget Hierarchy +<p> +For your reference, here is the class hierarchy tree used to implement widgets. + +<tscreen><verb> + GtkObject + +GtkData + | +GtkAdjustment + | `GtkTooltips + `GtkWidget + +GtkContainer + | +GtkBin + | | +GtkAlignment + | | +GtkEventBox + | | +GtkFrame + | | | `GtkAspectFrame + | | +GtkHandleBox + | | +GtkItem + | | | +GtkListItem + | | | +GtkMenuItem + | | | | `GtkCheckMenuItem + | | | | `GtkRadioMenuItem + | | | `GtkTreeItem + | | +GtkViewport + | | `GtkWindow + | | +GtkColorSelectionDialog + | | +GtkDialog + | | | `GtkInputDialog + | | `GtkFileSelection + | +GtkBox + | | +GtkButtonBox + | | | +GtkHButtonBox + | | | `GtkVButtonBox + | | +GtkHBox + | | | +GtkCombo + | | | `GtkStatusbar + | | `GtkVBox + | | +GtkColorSelection + | | `GtkGammaCurve + | +GtkButton + | | +GtkOptionMenu + | | `GtkToggleButton + | | `GtkCheckButton + | | `GtkRadioButton + | +GtkCList + | +GtkFixed + | +GtkList + | +GtkMenuShell + | | +GtkMenuBar + | | `GtkMenu + | +GtkNotebook + | +GtkPaned + | | +GtkHPaned + | | `GtkVPaned + | +GtkScrolledWindow + | +GtkTable + | +GtkToolbar + | `GtkTree + +GtkDrawingArea + | `GtkCurve + +GtkEditable + | +GtkEntry + | | `GtkSpinButton + | `GtkText + +GtkMisc + | +GtkArrow + | +GtkImage + | +GtkLabel + | | `GtkTipsQuery + | `GtkPixmap + +GtkPreview + +GtkProgressBar + +GtkRange + | +GtkScale + | | +GtkHScale + | | `GtkVScale + | `GtkScrollbar + | +GtkHScrollbar + | `GtkVScrollbar + +GtkRuler + | +GtkHRuler + | `GtkVRuler + `GtkSeparator + +GtkHSeparator + `GtkVSeparator +</verb></tscreen> + +<!-- ----------------------------------------------------------------- --> +<sect1>Widgets Without Windows +<p> +The following widgets do not have an associated window. If you want to +capture events, you'll have to use the GtkEventBox. See the section on +<ref id="sec_The_EventBox_Widget" name="The EventBox Widget"> + +<tscreen><verb> +GtkAlignment +GtkArrow +GtkBin +GtkBox +GtkImage +GtkItem +GtkLabel +GtkPaned +GtkPixmap +GtkScrolledWindow +GtkSeparator +GtkTable +GtkViewport +GtkAspectFrame +GtkFrame +GtkVPaned +GtkHPaned +GtkVBox +GtkHBox +GtkVSeparator +GtkHSeparator +</verb></tscreen> +<p> +We'll further our exploration of GTK by examining each widget in turn, +creating a few simple functions to display them. Another good source is +the testgtk.c program that comes with GTK. It can be found in +gtk/testgtk.c. + +<!-- ***************************************************************** --> +<sect>The Button Widget +<!-- ***************************************************************** --> + +<!-- ----------------------------------------------------------------- --> +<sect1>Normal Buttons +<p> +We've almost seen all there is to see of the button widget. It's pretty +simple. There is however two ways to create a button. You can use the +gtk_button_new_with_label() to create a button with a label, or use +gtk_button_new() to create a blank button. It's then up to you to pack a +label or pixmap into this new button. To do this, create a new box, and +then pack your objects into this box using the usual gtk_box_pack_start, +and then use gtk_container_add to pack the box into the button. +<p> +Here's an example of using gtk_button_new to create a button with a +picture and a label in it. I've broken the code to create a box up from +the rest so you can use it in your programs. + +<tscreen><verb> +/* buttons.c */ + +#include <gtk/gtk.h> + +/* create a new hbox with an image and a label packed into it + * and return the box.. */ + +GtkWidget *xpm_label_box (GtkWidget *parent, gchar *xpm_filename, gchar *label_text) +{ + GtkWidget *box1; + GtkWidget *label; + GtkWidget *pixmapwid; + GdkPixmap *pixmap; + GdkBitmap *mask; + GtkStyle *style; + + /* create box for xpm and label */ + box1 = gtk_hbox_new (FALSE, 0); + gtk_container_border_width (GTK_CONTAINER (box1), 2); + + /* get style of button.. I assume it's to get the background color. + * if someone knows the real reason, please enlighten me. */ + style = gtk_widget_get_style(parent); + + /* now on to the xpm stuff.. load xpm */ + pixmap = gdk_pixmap_create_from_xpm (parent->window, &mask, + &style->bg[GTK_STATE_NORMAL], + xpm_filename); + pixmapwid = gtk_pixmap_new (pixmap, mask); + + /* create label for button */ + label = gtk_label_new (label_text); + + /* pack the pixmap and label into the box */ + gtk_box_pack_start (GTK_BOX (box1), + pixmapwid, FALSE, FALSE, 3); + + gtk_box_pack_start (GTK_BOX (box1), label, FALSE, FALSE, 3); + + gtk_widget_show(pixmapwid); + gtk_widget_show(label); + + return (box1); +} + +/* our usual callback function */ +void callback (GtkWidget *widget, gpointer *data) +{ + g_print ("Hello again - %s was pressed\n", (char *) data); +} + + +int main (int argc, char *argv[]) +{ + /* GtkWidget is the storage type for widgets */ + GtkWidget *window; + GtkWidget *button; + GtkWidget *box1; + + gtk_init (&argc, &argv); + + /* create a new window */ + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + + gtk_window_set_title (GTK_WINDOW (window), "Pixmap'd Buttons!"); + + /* It's a good idea to do this for all windows. */ + gtk_signal_connect (GTK_OBJECT (window), "destroy", + GTK_SIGNAL_FUNC (gtk_exit), NULL); + + gtk_signal_connect (GTK_OBJECT (window), "delete_event", + GTK_SIGNAL_FUNC (gtk_exit), NULL); + + + /* sets the border width of the window. */ + gtk_container_border_width (GTK_CONTAINER (window), 10); + gtk_widget_realize(window); + + /* create a new button */ + button = gtk_button_new (); + + /* You should be getting used to seeing most of these functions by now */ + gtk_signal_connect (GTK_OBJECT (button), "clicked", + GTK_SIGNAL_FUNC (callback), (gpointer) "cool button"); + + /* this calls our box creating function */ + box1 = xpm_label_box(window, "info.xpm", "cool button"); + + /* pack and show all our widgets */ + gtk_widget_show(box1); + + gtk_container_add (GTK_CONTAINER (button), box1); + + gtk_widget_show(button); + + gtk_container_add (GTK_CONTAINER (window), button); + + gtk_widget_show (window); + + /* rest in gtk_main and wait for the fun to begin! */ + gtk_main (); + + return 0; +} +</verb></tscreen> + +The xpm_label_box function could be used to pack xpm's and labels into any +widget that can be a container. + +<!-- ----------------------------------------------------------------- --> +<sect1> Toggle Buttons +<p> +Toggle buttons are very similar to normal buttons, except they will always +be in one of two states, alternated by a click. They may be depressed, and +when you click again, they will pop back up. Click again, and they will pop +back down. + +Toggle buttons are the basis for check buttons and radio buttons, as such, +many of the calls used for toggle buttons are inherited by radio and check +buttons. I will point these out when we come to them. + +Creating a new toggle button: + +<tscreen><verb> +GtkWidget* gtk_toggle_button_new (void); + +GtkWidget* gtk_toggle_button_new_with_label (gchar *label); +</verb></tscreen> +<p> +As you can imagine, these work identically to the normal button widget +calls. The first creates a blank toggle button, and the second, a button +with a label widget already packed into it. +<p> +To retrieve the state of the toggle widget, including radio and check +buttons, we use a macro as shown in our example below. This tests the state +of the toggle in a callback. The signal of interest emitted to us by toggle +buttons (the toggle button, check button, and radio button widgets), is the +"toggled" signal. To check the state of these buttons, set up a signal +handler to catch the toggled signal, and use the macro to determine it's +state. The callback will look something like: + +<tscreen><verb> +void toggle_button_callback (GtkWidget *widget, gpointer data) +{ + if (GTK_TOGGLE_BUTTON (widget)->active) + { + /* If control reaches here, the toggle button is down */ + + } else { + + /* If control reaches here, the toggle button is up */ + } +} +</verb></tscreen> + +<!-- + +COMMENTED! + +<tscreen><verb> +guint gtk_toggle_button_get_type (void); +</verb></tscreen> +<p> +No idea... they all have this, but I dunno what it is :) + + +<tscreen><verb> +void gtk_toggle_button_set_mode (GtkToggleButton *toggle_button, + gint draw_indicator); +</verb></tscreen> +<p> +No idea. +--> + +<tscreen><verb> +void gtk_toggle_button_set_state (GtkToggleButton *toggle_button, + gint state); +</verb></tscreen> +<p> +The above call can be used to set the state of the toggle button, and it's +children the radio and check buttons. Passing +in your created button as the first argument, and a TRUE or FALSE +for the second state argument to specify whether it should be up (released) or +down (depressed). Default is up, or FALSE. + +Note that when you use the gtk_toggle_button_set_state() function, and the +state is actually changed, it causes +the "clicked" signal to be emitted from the button. + +<tscreen><verb> +void gtk_toggle_button_toggled (GtkToggleButton *toggle_button); +</verb></tscreen> +<p> +This simply toggles the button, and emits the "toggled" signal. + +<!-- ----------------------------------------------------------------- --> +<sect1> Check Buttons +<p> +Check buttons inherent many properties and functions from the the toggle buttons above, +but look a little +different. Rather than being buttons with text inside them, they are small +squares with the text to the right of them. These are often seen for +toggling options on and off in applications. + +The two creation functions are the same as for the normal button. + +<tscreen><verb> +GtkWidget* gtk_check_button_new (void); + +GtkWidget* gtk_check_button_new_with_label (gchar *label); +</verb></tscreen> + +The new_with_label function creates a check button with a label beside it. + +Checking the state of the check button is identical to that of the toggle +button. + +<!-- ----------------------------------------------------------------- --> +<sect1> Radio Buttons +<p> +Radio buttons are similar to check buttons except they are grouped so that +only one may be selected/depressed at a time. This is good for places in +your application where you need to select from a short list of options. + +Creating a new radio button is done with one of these calls: + +<tscreen><verb> +GtkWidget* gtk_radio_button_new (GSList *group); + +GtkWidget* gtk_radio_button_new_with_label (GSList *group, + gchar *label); +</verb></tscreen> +<p> +You'll notice the extra argument to these calls. They require a group to +perform they're duty properly. The first call should pass NULL as the first +argument. Then create a group using: + +<tscreen><verb> +GSList* gtk_radio_button_group (GtkRadioButton *radio_button); +</verb></tscreen> + +<p> +The important thing to remember is that gtk_radio_button_group must be +called for each new button added to the group, with the previous button +passed in as an argument. The result is then passed into the call to +gtk_radio_button_new or gtk_radio_button_new_with_label. This allows a +chain of buttons to be established. The example below should make this +clear. + +It is also a good idea to explicitly set which button should be the +default depressed button with: + +<tscreen><verb> +void gtk_toggle_button_set_state (GtkToggleButton *toggle_button, + gint state); +</verb></tscreen> +<p> +This is described in the section on toggle buttons, and works in exactly the +same way. +<p> +The following example creates a radio button group with three buttons. + +<tscreen><verb> +/* radiobuttons.c */ + +#include <gtk/gtk.h> +#include <glib.h> + +void close_application( GtkWidget *widget, gpointer *data ) { + gtk_main_quit(); +} + +main(int argc,char *argv[]) +{ + static GtkWidget *window = NULL; + GtkWidget *box1; + GtkWidget *box2; + GtkWidget *button; + GtkWidget *separator; + GSList *group; + + gtk_init(&argc,&argv); + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + + gtk_signal_connect (GTK_OBJECT (window), "delete_event", + GTK_SIGNAL_FUNC(close_application), + NULL); + + gtk_window_set_title (GTK_WINDOW (window), "radio buttons"); + gtk_container_border_width (GTK_CONTAINER (window), 0); + + box1 = gtk_vbox_new (FALSE, 0); + gtk_container_add (GTK_CONTAINER (window), box1); + gtk_widget_show (box1); + + box2 = gtk_vbox_new (FALSE, 10); + gtk_container_border_width (GTK_CONTAINER (box2), 10); + gtk_box_pack_start (GTK_BOX (box1), box2, TRUE, TRUE, 0); + gtk_widget_show (box2); + + button = gtk_radio_button_new_with_label (NULL, "button1"); + gtk_box_pack_start (GTK_BOX (box2), button, TRUE, TRUE, 0); + gtk_widget_show (button); + + group = gtk_radio_button_group (GTK_RADIO_BUTTON (button)); + button = gtk_radio_button_new_with_label(group, "button2"); + gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON (button), TRUE); + gtk_box_pack_start (GTK_BOX (box2), button, TRUE, TRUE, 0); + gtk_widget_show (button); + + group = gtk_radio_button_group (GTK_RADIO_BUTTON (button)); + button = gtk_radio_button_new_with_label(group, "button3"); + gtk_box_pack_start (GTK_BOX (box2), button, TRUE, TRUE, 0); + gtk_widget_show (button); + + separator = gtk_hseparator_new (); + gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 0); + gtk_widget_show (separator); + + box2 = gtk_vbox_new (FALSE, 10); + gtk_container_border_width (GTK_CONTAINER (box2), 10); + gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, TRUE, 0); + gtk_widget_show (box2); + + button = gtk_button_new_with_label ("close"); + gtk_signal_connect_object (GTK_OBJECT (button), "clicked", + GTK_SIGNAL_FUNC(close_application), + GTK_OBJECT (window)); + gtk_box_pack_start (GTK_BOX (box2), button, TRUE, TRUE, 0); + GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT); + gtk_widget_grab_default (button); + gtk_widget_show (button); + gtk_widget_show (window); + + gtk_main(); + return(0); +} +</verb></tscreen> + +You can shorten this slightly by using the following syntax, which +removes the need for a variable to hold the list of buttons: + +<tscreen><verb> + button2 = gtk_radio_button_new_with_label( + gtk_radio_button_group (GTK_RADIO_BUTTON (button1)), + "button2"); +</verb></tscreen> + +<!-- ***************************************************************** --> +<sect> Miscallaneous Widgets +<!-- ***************************************************************** --> + +<!-- ----------------------------------------------------------------- --> +<sect1> Labels +<p> +Labels are used a lot in GTK, and are relatively simple. Labels emit no +signals as they do not have an associated X window. If you need to catch +signals, or do clipping, use the EventBox widget. + +To create a new label, use: + +<tscreen><verb> +GtkWidget* gtk_label_new (char *str); +</verb></tscreen> + +Where the sole argument is the string you wish the label to display. + +To change the label's text after creation, use the function: + +<tscreen><verb> +void gtk_label_set (GtkLabel *label, + char *str); +</verb></tscreen> +<p> +Where the first argument is the label you created previously (casted using +the GTK_LABEL() macro), and the second is the new string. + +The space needed for the new string will be automatically adjusted if needed. + +To retrieve the current string, use: + +<tscreen><verb> +void gtk_label_get (GtkLabel *label, + char **str); +</verb></tscreen> + +Where the first arguement is the label you've created, and the second, the +return for the string. + +<!-- ----------------------------------------------------------------- --> +<sect1>The Tooltips Widget +<p> +These are the little text strings that pop up when you leave your pointer +over a button or other widget for a few seconds. They are easy to use, so I +will just explain them without giving an example. If you want to see some +code, take a look at the testgtk.c program distributed with GDK. +<p> +Some widgets (such as the label) will not work with tooltips. +<p> +The first call you will use to create a new tooltip. You only need to do +this once in a given function. The GtkTooltip this function returns can be +used to create multiple tooltips. + +<tscreen><verb> +GtkTooltips *gtk_tooltips_new (void); +</verb></tscreen> + +Once you have created a new tooltip, and the widget you wish to use it on, +simply use this call to set it. + +<tscreen><verb> +void gtk_tooltips_set_tips (GtkTooltips *tooltips, + GtkWidget *widget, + gchar *tips_text); +</verb></tscreen> + +The first argument is the tooltip you've already created, followed by the +widget you wish to have this tooltip pop up for, and the text you wish it to +say. +<p> +Here's a short example: + +<tscreen><verb> +GtkTooltips *tooltips; +GtkWidget *button; +... +tooltips = gtk_tooltips_new (); +button = gtk_button_new_with_label ("button 1"); +... +gtk_tooltips_set_tips (tooltips, button, "This is button 1"); +</verb></tscreen> + + +There are other calls used with tooltips. I will just list them with a +brief description of what they do. + +<tscreen><verb> +void gtk_tooltips_destroy (GtkTooltips *tooltips); +</verb></tscreen> + +Destroy the created tooltips. + +<tscreen><verb> +void gtk_tooltips_enable (GtkTooltips *tooltips); +</verb></tscreen> + +Enable a disabled set of tooltips. + +<tscreen><verb> +void gtk_tooltips_disable (GtkTooltips *tooltips); +</verb></tscreen> + +Disable an enabled set of tooltips. + +<tscreen><verb> +void gtk_tooltips_set_delay (GtkTooltips *tooltips, + gint delay); + +</verb></tscreen> +Sets how many milliseconds you have to hold you pointer over the widget before the +tooltip will pop up. The default is 1000 milliseconds or 1 second. + +<tscreen><verb> +void gtk_tooltips_set_tips (GtkTooltips *tooltips, + GtkWidget *widget, + gchar *tips_text); +</verb></tscreen> + +Change the tooltip text of an already created tooltip. + +<tscreen><verb> +void gtk_tooltips_set_colors (GtkTooltips *tooltips, + GdkColor *background, + GdkColor *foreground); +</verb></tscreen> + +Set the foreground and background color of the tooltips. Again, I have no +idea how to specify the colors. +<p> +And that's all the functions associated with tooltips. More than you'll +ever want to know :) + +<!-- ----------------------------------------------------------------- --> +<sect1> Progress Bars +<p> +Progress bars are used to show the status of an operation. They are pretty +easy to use, as you will see with the code below. But first lets start out +with the call to create a new progress bar. + +<tscreen><verb> +GtkWidget *gtk_progress_bar_new (void); +</verb></tscreen> + +Now that the progress bar has been created we can use it. + +<tscreen><verb> +void gtk_progress_bar_update (GtkProgressBar *pbar, gfloat percentage); +</verb></tscreen> + +The first argument is the progress bar you wish to operate on, and the second +argument is the amount 'completed', meaning the amount the progress bar has +been filled from 0-100% (a real number between 0 and 1). + +Progress Bars are usually used with timeouts or other such functions (see +section on <ref id="sec_timeouts" name="Timeouts, I/O and Idle Functions">) +to give the illusion of multitasking. All will employ +the gtk_progress_bar_update function in the same manner. + +Here is an example of the progress bar, updated using timeouts. This +code also shows you how to reset the Progress Bar. + +<tscreen><verb> +/* progressbar.c */ + +#include <gtk/gtk.h> + +static int ptimer = 0; +int pstat = TRUE; + +/* This function increments and updates the progress bar, it also resets + the progress bar if pstat is FALSE */ +gint progress (gpointer data) +{ + gfloat pvalue; + + /* get the current value of the progress bar */ + pvalue = GTK_PROGRESS_BAR (data)->percentage; + + if ((pvalue >= 1.0) || (pstat == FALSE)) { + pvalue = 0.0; + pstat = TRUE; + } + pvalue += 0.01; + + gtk_progress_bar_update (GTK_PROGRESS_BAR (data), pvalue); + + return TRUE; +} + +/* This function signals a reset of the progress bar */ +void progress_r (void) +{ + pstat = FALSE; +} + +void destroy (GtkWidget *widget, gpointer *data) +{ + gtk_main_quit (); +} + +int main (int argc, char *argv[]) +{ + GtkWidget *window; + GtkWidget *button; + GtkWidget *label; + GtkWidget *table; + GtkWidget *pbar; + + gtk_init (&argc, &argv); + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + + gtk_signal_connect (GTK_OBJECT (window), "delete_event", + GTK_SIGNAL_FUNC (destroy), NULL); + + gtk_container_border_width (GTK_CONTAINER (window), 10); + + table = gtk_table_new(3,2,TRUE); + gtk_container_add (GTK_CONTAINER (window), table); + + label = gtk_label_new ("Progress Bar Example"); + gtk_table_attach_defaults(GTK_TABLE(table), label, 0,2,0,1); + gtk_widget_show(label); + + /* Create a new progress bar, pack it into the table, and show it */ + pbar = gtk_progress_bar_new (); + gtk_table_attach_defaults(GTK_TABLE(table), pbar, 0,2,1,2); + gtk_widget_show (pbar); + + /* Set the timeout to handle automatic updating of the progress bar */ + ptimer = gtk_timeout_add (100, progress, pbar); + + /* This button signals the progress bar to be reset */ + button = gtk_button_new_with_label ("Reset"); + gtk_signal_connect (GTK_OBJECT (button), "clicked", + GTK_SIGNAL_FUNC (progress_r), NULL); + gtk_table_attach_defaults(GTK_TABLE(table), button, 0,1,2,3); + gtk_widget_show(button); + + button = gtk_button_new_with_label ("Cancel"); + gtk_signal_connect (GTK_OBJECT (button), "clicked", + GTK_SIGNAL_FUNC (destroy), NULL); + + gtk_table_attach_defaults(GTK_TABLE(table), button, 1,2,2,3); + gtk_widget_show (button); + + gtk_widget_show(table); + gtk_widget_show(window); + + gtk_main (); + + return 0; +} +</verb></tscreen> + +In this small program there are four areas that concern the general operation +of Progress Bars, we will look at them in the order they are called. + +<tscreen><verb> +pbar = gtk_progress_bar_new (); +</verb></tscreen> + +This code creates a new progress bar, called pbar. + +<tscreen><verb> +ptimer = gtk_timeout_add (100, progress, pbar); +</verb></tscreen> + +This code, uses timeouts to enable a constant time interval, timeouts are +not necessary in the use of Progress Bars. + +<tscreen><verb> +pvalue = GTK_PROGRESS_BAR (data)->percentage; +</verb></tscreen> + +This code assigns the current value of the percentage bar to pvalue. + +<tscreen><verb> +gtk_progress_bar_update (GTK_PROGRESS_BAR (data), pvalue); +</verb></tscreen> + +Finally, this code updates the progress bar with the value of pvalue + +And that is all there is to know about Progress Bars, enjoy. + +<!-- ----------------------------------------------------------------- --> +<sect1> Dialogs +<p> + +The Dialog widget is very simple, and is actually just a window with a few +things pre-packed into it for you. The structure for a Dialog is: + +<tscreen><verb> +struct GtkDialog +{ + GtkWindow window; + + GtkWidget *vbox; + GtkWidget *action_area; +}; +</verb></tscreen> + +So you see, it simple creates a window, and then packs a vbox into the top, +then a seperator, and then an hbox for the "action_area". + +The Dialog widget can be used for pop-up messages to the user, and +other similar tasks. It is really basic, and there is only one +function for the dialog box, which is: + +<tscreen><verb> +GtkWidget* gtk_dialog_new (void); +</verb></tscreen> + +So to create a new dialog box, use, + +<tscreen><verb> +GtkWidget window; +window = gtk_dialog_new (); +</verb></tscreen> + +This will create the dialog box, and it is now up to you to use it. +you could pack a button in the action_area by doing something like so: + +<tscreen><verb> +button = ... +gtk_box_pack_start (GTK_BOX (GTK_DIALOG (window)->action_area), button, + TRUE, TRUE, 0); +gtk_widget_show (button); +</verb></tscreen> + +And you could add to the vbox area by packing, for instance, a label +in it, try something like this: + +<tscreen><verb> +label = gtk_label_new ("Dialogs are groovy"); +gtk_box_pack_start (GTK_BOX (GTK_DIALOG (window)->vbox), label, TRUE, + TRUE, 0); +gtk_widget_show (label); +</verb></tscreen> + +As an example in using the dialog box, you could put two buttons in +the action_area, a Cancel button and an Ok button, and a label in the vbox +area, asking the user a question or giving an error etc. Then you could +attach a different signal to each of the buttons and perform the +operation the user selects. + +<!-- ----------------------------------------------------------------- --> +<sect1> Pixmaps +<p> +Pixmaps are data structures that contain pictures. These pictures can be +used in various places, but most visibly as icons on the X-Windows desktop, +or as cursors. A bitmap is a 2-color pixmap. + +To use pixmaps in GTK, we must first build a GdkPixmap structure using +routines from the GDK layer. Pixmaps can either be created from in-memory +data, or from data read from a file. We'll go through each of the calls +to create a pixmap. + +<tscreen><verb> +GdkPixmap *gdk_bitmap_create_from_data( GdkWindow *window, + gchar *data, + gint width, + gint height ); +</verb></tscreen> +<p> +This routine is used to create a single-plane pixmap (2 colors) from data in +memory. Each bit of the data represents whether that pixel is off or on. +Width and height are in pixels. The GdkWindow pointer is to the current +window, since a pixmap resources are meaningful only in the context of the +screen where it is to be displayed. + +<tscreen><verb> +GdkPixmap* gdk_pixmap_create_from_data( GdkWindow *window, + gchar *data, + gint width, + gint height, + gint depth, + GdkColor *fg, + GdkColor *bg ); +</verb></tscreen> + +This is used to create a pixmap of the given depth (number of colors) from +the bitmap data specified. fg and bg are the foreground and background +color to use. + +<tscreen><verb> +GdkPixmap* gdk_pixmap_create_from_xpm( GdkWindow *window, + GdkBitmap **mask, + GdkColor *transparent_color, + const gchar *filename ); +</verb></tscreen> + +XPM format is a readable pixmap representation for the X Window System. It +is widely used and many different utilities are available for creating image +files in this format. The file specified by filename must contain an image +in that format and it is loaded into the pixmap structure. The mask specifies +what bits of the pixmap are opaque. All other bits are colored using the +color specified by transparent_color. An example using this follows below. + +<tscreen><verb> +GdkPixmap* gdk_pixmap_create_from_xpm_d (GdkWindow *window, + GdkBitmap **mask, + GdkColor *transparent_color, + gchar **data); +</verb></tscreen> + +Small images can be incorporated into a program as data in the XPM format. +A pixmap is created using this data, instead of reading it from a file. +An example of such data is + +<tscreen><verb> +/* XPM */ +static const char * xpm_data[] = { +"16 16 3 1", +" c None", +". c #000000000000", +"X c #FFFFFFFFFFFF", +" ", +" ...... ", +" .XXX.X. ", +" .XXX.XX. ", +" .XXX.XXX. ", +" .XXX..... ", +" .XXXXXXX. ", +" .XXXXXXX. ", +" .XXXXXXX. ", +" .XXXXXXX. ", +" .XXXXXXX. ", +" .XXXXXXX. ", +" .XXXXXXX. ", +" ......... ", +" ", +" "}; +</verb></tscreen> + +<tscreen><verb> +void gdk_pixmap_destroy( GdkPixmap *pixmap ); +</verb></tscreen> +<p> +When we're done using a pixmap and not likely to reuse it again soon, +it is a good idea to release the resource using gdk_pixmap_destroy. Pixmaps +should be considered a precious resource. + + +Once we've created a pixmap, we can display it as a GTK widget. We must +create a pixmap widget to contain the GDK pixmap. This is done using + +<tscreen><verb> +GtkWidget* gtk_pixmap_new( GdkPixmap *pixmap, + GdkBitmap *mask ); +</verb></tscreen> +<p> +The other pixmap widget calls are + +<tscreen><verb> +guint gtk_pixmap_get_type( void ); +void gtk_pixmap_set( GtkPixmap *pixmap, + GdkPixmap *val, + GdkBitmap *mask); +void gtk_pixmap_get( GtkPixmap *pixmap, + GdkPixmap **val, + GdkBitmap **mask); +</verb></tscreen> +<p> +gtk_pixmap_set is used to change the pixmap that the widget is currently +managing. Val is the pixmap created using GDK. + +The following is an example of using a pixmap in a button. + +<tscreen><verb> +/* pixmap.c */ + +#include <gtk/gtk.h> + + +/* XPM data of Open-File icon */ +static const char * xpm_data[] = { +"16 16 3 1", +" c None", +". c #000000000000", +"X c #FFFFFFFFFFFF", +" ", +" ...... ", +" .XXX.X. ", +" .XXX.XX. ", +" .XXX.XXX. ", +" .XXX..... ", +" .XXXXXXX. ", +" .XXXXXXX. ", +" .XXXXXXX. ", +" .XXXXXXX. ", +" .XXXXXXX. ", +" .XXXXXXX. ", +" .XXXXXXX. ", +" ......... ", +" ", +" "}; + + +/* when invoked (via signal delete_event), terminates the application. + */ +void close_application( GtkWidget *widget, gpointer *data ) { + gtk_main_quit(); +} + + +/* is invoked when the button is clicked. It just prints a message. + */ +void button_clicked( GtkWidget *widget, gpointer *data ) { + printf( "button clicked\n" ); +} + +int main( int argc, char *argv[] ) +{ + /* GtkWidget is the storage type for widgets */ + GtkWidget *window, *pixmapwid, *button; + GdkPixmap *pixmap; + GdkBitmap *mask; + GtkStyle *style; + + /* create the main window, and attach delete_event signal to terminating + the application */ + gtk_init( &argc, &argv ); + window = gtk_window_new( GTK_WINDOW_TOPLEVEL ); + gtk_signal_connect( GTK_OBJECT (window), "delete_event", + GTK_SIGNAL_FUNC (close_application), NULL ); + gtk_container_border_width( GTK_CONTAINER (window), 10 ); + gtk_widget_show( window ); + + /* now for the pixmap from gdk */ + style = gtk_widget_get_style( window ); + pixmap = gdk_pixmap_create_from_xpm_d( window->window, &mask, + &style->bg[GTK_STATE_NORMAL], + (gchar **)xpm_data ); + + /* a pixmap widget to contain the pixmap */ + pixmapwid = gtk_pixmap_new( pixmap, mask ); + gtk_widget_show( pixmapwid ); + + /* a button to contain the pixmap widget */ + button = gtk_button_new(); + gtk_container_add( GTK_CONTAINER(button), pixmapwid ); + gtk_container_add( GTK_CONTAINER(window), button ); + gtk_widget_show( button ); + + gtk_signal_connect( GTK_OBJECT(button), "clicked", + GTK_SIGNAL_FUNC(button_clicked), NULL ); + + /* show the window */ + gtk_main (); + + return 0; +} +</verb></tscreen> + + +To load a file from an XPM data file called icon0.xpm in the current +directory, we would have created the pixmap thus + +<tscreen><verb> + /* load a pixmap from a file */ + pixmap = gdk_pixmap_create_from_xpm( window->window, &mask, + &style->bg[GTK_STATE_NORMAL], + "./icon0.xpm" ); + pixmapwid = gtk_pixmap_new( pixmap, mask ); + gtk_widget_show( pixmapwid ); + gtk_container_add( GTK_CONTAINER(window), pixmapwid ); +</verb></tscreen> + + + +Using Shapes +<p> +A disadvantage of using pixmaps is that the displayed object is always +rectangular, regardless of the image. We would like to create desktops +and applications with icons that have more natural shapes. For example, +for a game interface, we would like to have round buttons to push. The +way to do this is using shaped windows. + +A shaped window is simply a pixmap where the background pixels are +transparent. This way, when the background image is multi-colored, we +don't overwrite it with a rectangular, non-matching border around our +icon. The following example displays a full wheelbarrow image on the +desktop. + +<tscreen><verb> +/* wheelbarrow.c */ + +#include <gtk/gtk.h> + +/* XPM */ +static char * WheelbarrowFull_xpm[] = { +"48 48 64 1", +" c None", +". c #DF7DCF3CC71B", +"X c #965875D669A6", +"o c #71C671C671C6", +"O c #A699A289A699", +"+ c #965892489658", +"@ c #8E38410330C2", +"# c #D75C7DF769A6", +"$ c #F7DECF3CC71B", +"% c #96588A288E38", +"& c #A69992489E79", +"* c #8E3886178E38", +"= c #104008200820", +"- c #596510401040", +"; c #C71B30C230C2", +": c #C71B9A699658", +"> c #618561856185", +", c #20811C712081", +"< c #104000000000", +"1 c #861720812081", +"2 c #DF7D4D344103", +"3 c #79E769A671C6", +"4 c #861782078617", +"5 c #41033CF34103", +"6 c #000000000000", +"7 c #49241C711040", +"8 c #492445144924", +"9 c #082008200820", +"0 c #69A618611861", +"q c #B6DA71C65144", +"w c #410330C238E3", +"e c #CF3CBAEAB6DA", +"r c #71C6451430C2", +"t c #EFBEDB6CD75C", +"y c #28A208200820", +"u c #186110401040", +"i c #596528A21861", +"p c #71C661855965", +"a c #A69996589658", +"s c #30C228A230C2", +"d c #BEFBA289AEBA", +"f c #596545145144", +"g c #30C230C230C2", +"h c #8E3882078617", +"j c #208118612081", +"k c #38E30C300820", +"l c #30C2208128A2", +"z c #38E328A238E3", +"x c #514438E34924", +"c c #618555555965", +"v c #30C2208130C2", +"b c #38E328A230C2", +"n c #28A228A228A2", +"m c #41032CB228A2", +"M c #104010401040", +"N c #492438E34103", +"B c #28A2208128A2", +"V c #A699596538E3", +"C c #30C21C711040", +"Z c #30C218611040", +"A c #965865955965", +"S c #618534D32081", +"D c #38E31C711040", +"F c #082000000820", +" ", +" .XoO ", +" +@#$%o& ", +" *=-;#::o+ ", +" >,<12#:34 ", +" 45671#:X3 ", +" +89<02qwo ", +"e* >,67;ro ", +"ty> 459@>+&& ", +"$2u+ ><ipas8* ", +"%$;=* *3:.Xa.dfg> ", +"Oh$;ya *3d.a8j,Xe.d3g8+ ", +" Oh$;ka *3d$a8lz,,xxc:.e3g54 ", +" Oh$;kO *pd$%svbzz,sxxxxfX..&wn> ", +" Oh$@mO *3dthwlsslszjzxxxxxxx3:td8M4 ", +" Oh$@g& *3d$XNlvvvlllm,mNwxxxxxxxfa.:,B* ", +" Oh$@,Od.czlllllzlmmqV@V#V@fxxxxxxxf:%j5& ", +" Oh$1hd5lllslllCCZrV#r#:#2AxxxxxxxxxcdwM* ", +" OXq6c.%8vvvllZZiqqApA:mq:Xxcpcxxxxxfdc9* ", +" 2r<6gde3bllZZrVi7S@SV77A::qApxxxxxxfdcM ", +" :,q-6MN.dfmZZrrSS:#riirDSAX@Af5xxxxxfevo", +" +A26jguXtAZZZC7iDiCCrVVii7Cmmmxxxxxx%3g", +" *#16jszN..3DZZZZrCVSA2rZrV7Dmmwxxxx&en", +" p2yFvzssXe:fCZZCiiD7iiZDiDSSZwwxx8e*>", +" OA1<jzxwwc:$d%NDZZZZCCCZCCZZCmxxfd.B ", +" 3206Bwxxszx%et.eaAp77m77mmmf3&eeeg* ", +" @26MvzxNzvlbwfpdettttttttttt.c,n& ", +" *;16=lsNwwNwgsvslbwwvccc3pcfu<o ", +" p;<69BvwwsszslllbBlllllllu<5+ ", +" OS0y6FBlvvvzvzss,u=Blllj=54 ", +" c1-699Blvlllllu7k96MMMg4 ", +" *10y8n6FjvllllB<166668 ", +" S-kg+>666<M<996-y6n<8* ", +" p71=4 m69996kD8Z-66698&& ", +" &i0ycm6n4 ogk17,0<6666g ", +" N-k-<> >=01-kuu666> ", +" ,6ky& &46-10ul,66, ", +" Ou0<> o66y<ulw<66& ", +" *kk5 >66By7=xu664 ", +" <<M4 466lj<Mxu66o ", +" *>> +66uv,zN666* ", +" 566,xxj669 ", +" 4666FF666> ", +" >966666M ", +" oM6668+ ", +" *4 ", +" ", +" "}; + + +/* when invoked (via signal delete_event), terminates the application. + */ +void close_application( GtkWidget *widget, gpointer *data ) { + gtk_main_quit(); +} + +int main (int argc, char *argv[]) +{ + /* GtkWidget is the storage type for widgets */ + GtkWidget *window, *pixmap, *fixed; + GdkPixmap *gdk_pixmap; + GdkBitmap *mask; + GtkStyle *style; + GdkGC *gc; + + /* create the main window, and attach delete_event signal to terminate + the application. Note that the main window will not have a titlebar + since we're making it a popup. */ + gtk_init (&argc, &argv); + window = gtk_window_new( GTK_WINDOW_POPUP ); + gtk_signal_connect (GTK_OBJECT (window), "delete_event", + GTK_SIGNAL_FUNC (close_application), NULL); + gtk_widget_show (window); + + /* now for the pixmap and the pixmap widget */ + style = gtk_widget_get_default_style(); + gc = style->black_gc; + gdk_pixmap = gdk_pixmap_create_from_xpm_d( window->window, &mask, + &style->bg[GTK_STATE_NORMAL], + WheelbarrowFull_xpm ); + pixmap = gtk_pixmap_new( gdk_pixmap, mask ); + gtk_widget_show( pixmap ); + + /* To display the pixmap, we use a fixed widget to place the pixmap */ + fixed = gtk_fixed_new(); + gtk_widget_set_usize( fixed, 200, 200 ); + gtk_fixed_put( GTK_FIXED(fixed), pixmap, 0, 0 ); + gtk_container_add( GTK_CONTAINER(window), fixed ); + gtk_widget_show( fixed ); + + /* This masks out everything except for the image itself */ + gtk_widget_shape_combine_mask( window, mask, 0, 0 ); + + /* show the window */ + gtk_widget_set_uposition( window, 20, 400 ); + gtk_widget_show( window ); + gtk_main (); + + return 0; +} +</verb></tscreen> +<p> +To make the wheelbarrow image sensitive, we could attach the button press +event signal to make it do something. The following few lines would make +the picture sensitive to a mouse button being pressed which makes the +application terminate. + +<tscreen><verb> +gtk_widget_set_events( window, + gtk_widget_get_events( window ) | + GDK_BUTTON_PRESS_MASK ); + +gtk_signal_connect( GTK_OBJECT(window), "button_press_event", + GTK_SIGNAL_FUNC(close_application), NULL ); +</verb></tscreen> + +<!-- ----------------------------------------------------------------- --> +<sect1>Rulers +<p> +Ruler widgets are used to indicate the location of the mouse pointer +in a given window. A window can have a vertical ruler spanning across +the width and a horizontal ruler spanning down the height. A small +triangular indicator on the ruler shows the exact location of the +pointer relative to the ruler. + +A ruler must first be created. Horizontal and vertical rulers are +created using + +<tscreen><verb> +GtkWidget *gtk_hruler_new(void); /* horizontal ruler */ +GtkWidget *gtk_vruler_new(void); /* vertical ruler */ +</verb></tscreen> + +Once a ruler is created, we can define the unit of measurement. Units +of measure for rulers can be GTK_PIXELS, GTK_INCHES or +GTK_CENTIMETERS. This is set using + +<tscreen><verb> +void gtk_ruler_set_metric( GtkRuler *ruler, + GtkMetricType metric ); +</verb></tscreen> + +The default measure is GTK_PIXELS. + +<tscreen><verb> +gtk_ruler_set_metric( GTK_RULER(ruler), GTK_PIXELS ); +</verb></tscreen> + +Other important characteristics of a ruler are how to mark the units +of scale and where the position indicator is initially placed. These +are set for a ruler using + +<tscreen><verb> +void gtk_ruler_set_range (GtkRuler *ruler, + gfloat lower, + gfloat upper, + gfloat position, + gfloat max_size); +</verb></tscreen> + +The lower and upper arguments define the extents of the ruler, and +max_size is the largest possible number that will be displayed. +Position defines the initial position of the pointer indicator within +the ruler. + +A vertical ruler can span an 800 pixel wide window thus + +<tscreen><verb> +gtk_ruler_set_range( GTK_RULER(vruler), 0, 800, 0, 800); +</verb></tscreen> + +The markings displayed on the ruler will be from 0 to 800, with +a number for every 100 pixels. If instead we wanted the ruler to +range from 7 to 16, we would code + +<tscreen><verb> +gtk_ruler_set_range( GTK_RULER(vruler), 7, 16, 0, 20); +</verb></tscreen> + +The indicator on the ruler is a small triangular mark that indicates +the position of the pointer relative to the ruler. If the ruler is +used to follow the mouse pointer, the motion_notify_event signal +should be connected to the motion_notify_event method of the ruler. +To follow all mouse movements within a window area, we would use + +<tscreen><verb> +#define EVENT_METHOD(i, x) GTK_WIDGET_CLASS(GTK_OBJECT(i)->klass)->x + +gtk_signal_connect_object( GTK_OBJECT(area), "motion_notify_event", + (GtkSignalFunc)EVENT_METHOD(ruler, motion_notify_event), + GTK_OBJECT(ruler) ); +</verb></tscreen> + +The following example creates a drawing area with a horizontal ruler +above it and a vertical ruler to the left of it. The size of the +drawing area is 600 pixels wide by 400 pixels high. The horizontal +ruler spans from 7 to 13 with a mark every 100 pixels, while the +vertical ruler spans from 0 to 400 with a mark every 100 pixels. +Placement of the drawing area and the rulers are done using a table. + +<tscreen><verb> +/* rulers.c */ + +#include <gtk/gtk.h> + +#define EVENT_METHOD(i, x) GTK_WIDGET_CLASS(GTK_OBJECT(i)->klass)->x + +#define XSIZE 600 +#define YSIZE 400 + +/* this routine gets control when the close button is clicked + */ +void close_application( GtkWidget *widget, gpointer *data ) { + gtk_main_quit(); +} + + +/* the main routine + */ +int main( int argc, char *argv[] ) { + GtkWidget *window, *table, *area, *hrule, *vrule; + + /* initialize gtk and create the main window */ + gtk_init( &argc, &argv ); + + window = gtk_window_new( GTK_WINDOW_TOPLEVEL ); + gtk_signal_connect (GTK_OBJECT (window), "delete_event", + GTK_SIGNAL_FUNC( close_application ), NULL); + gtk_container_border_width (GTK_CONTAINER (window), 10); + + /* create a table for placing the ruler and the drawing area */ + table = gtk_table_new( 3, 2, FALSE ); + gtk_container_add( GTK_CONTAINER(window), table ); + + area = gtk_drawing_area_new(); + gtk_drawing_area_size( (GtkDrawingArea *)area, XSIZE, YSIZE ); + gtk_table_attach( GTK_TABLE(table), area, 1, 2, 1, 2, + GTK_EXPAND|GTK_FILL, GTK_FILL, 0, 0 ); + gtk_widget_set_events( area, GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK ); + + /* The horizontal ruler goes on top. As the mouse moves across the drawing area, + a motion_notify_event is passed to the appropriate event handler for the ruler. */ + hrule = gtk_hruler_new(); + gtk_ruler_set_metric( GTK_RULER(hrule), GTK_PIXELS ); + gtk_ruler_set_range( GTK_RULER(hrule), 7, 13, 0, 20 ); + gtk_signal_connect_object( GTK_OBJECT(area), "motion_notify_event", + (GtkSignalFunc)EVENT_METHOD(hrule, motion_notify_event), + GTK_OBJECT(hrule) ); + /* GTK_WIDGET_CLASS(GTK_OBJECT(hrule)->klass)->motion_notify_event, */ + gtk_table_attach( GTK_TABLE(table), hrule, 1, 2, 0, 1, + GTK_EXPAND|GTK_SHRINK|GTK_FILL, GTK_FILL, 0, 0 ); + + /* The vertical ruler goes on the left. As the mouse moves across the drawing area, + a motion_notify_event is passed to the appropriate event handler for the ruler. */ + vrule = gtk_vruler_new(); + gtk_ruler_set_metric( GTK_RULER(vrule), GTK_PIXELS ); + gtk_ruler_set_range( GTK_RULER(vrule), 0, YSIZE, 10, YSIZE ); + gtk_signal_connect_object( GTK_OBJECT(area), "motion_notify_event", + (GtkSignalFunc) + GTK_WIDGET_CLASS(GTK_OBJECT(vrule)->klass)->motion_notify_event, + GTK_OBJECT(vrule) ); + gtk_table_attach( GTK_TABLE(table), vrule, 0, 1, 1, 2, + GTK_FILL, GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0 ); + + /* now show everything */ + gtk_widget_show( area ); + gtk_widget_show( hrule ); + gtk_widget_show( vrule ); + gtk_widget_show( table ); + gtk_widget_show( window ); + gtk_main(); + + return 0; +} +</verb></tscreen> + +<!-- ----------------------------------------------------------------- --> +<sect1>Statusbars +<p> +Statusbars are simple widgets used to display a text message. They keep a stack +of the messages pushed onto them, so that popping the current message +will re-display the previous text message. + +In order to allow different parts of an application to use the same statusbar to display +messages, the statusbar widget issues Context Identifiers which are used to identify +different 'users'. The message on top of the stack is the one displayed, no matter what context +it is in. Messages are stacked in last-in-first-out order, not context identifier order. + +A statusbar is created with a call to: +<tscreen><verb> +GtkWidget* gtk_statusbar_new (void); +</verb></tscreen> + +A new Context Identifier is requested using a call to the following function with a short +textual description of the context: +<tscreen><verb> +guint gtk_statusbar_get_context_id (GtkStatusbar *statusbar, + const gchar *context_description); +</verb></tscreen> + +There are three functions that can operate on statusbars. +<tscreen><verb> +guint gtk_statusbar_push (GtkStatusbar *statusbar, + guint context_id, + gchar *text); + +void gtk_statusbar_pop (GtkStatusbar *statusbar) + guint context_id); +void gtk_statusbar_remove (GtkStatusbar *statusbar, + guint context_id, + guint message_id); +</verb></tscreen> + +The first, gtk_statusbar_push, is used to add a new message to the statusbar. +It returns a Message Identifier, which can be passed later to the function gtk_statusbar_remove +to remove the message with the given Message and Context Identifiers from the statusbar's stack. + +The function gtk_statusbar_pop removes the message highest in the stack with the given +Context Identifier. + +The following example creates a statusbar and two buttons, one for pushing items +onto the statusbar, and one for popping the last item back off. + +<tscreen><verb> +/* statusbar.c */ + +#include <gtk/gtk.h> +#include <glib.h> + +GtkWidget *status_bar; + +void push_item (GtkWidget *widget, gpointer *data) +{ + static int count = 1; + char buff[20]; + + g_snprintf(buff, 20, "Item %d", count++); + gtk_statusbar_push( GTK_STATUSBAR(status_bar), (guint) &data, buff); + + return; +} + +void pop_item (GtkWidget *widget, gpointer *data) +{ + gtk_statusbar_pop( GTK_STATUSBAR(status_bar), (guint) &data ); + return; +} + +int main (int argc, char *argv[]) +{ + + GtkWidget *window; + GtkWidget *vbox; + GtkWidget *button; + + int context_id; + + gtk_init (&argc, &argv); + + /* create a new window */ + window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_widget_set_usize( GTK_WIDGET (window), 200, 100); + gtk_window_set_title(GTK_WINDOW (window), "GTK Statusbar Example"); + gtk_signal_connect(GTK_OBJECT (window), "delete_event", + (GtkSignalFunc) gtk_exit, NULL); + + vbox = gtk_vbox_new(FALSE, 1); + gtk_container_add(GTK_CONTAINER(window), vbox); + gtk_widget_show(vbox); + + status_bar = gtk_statusbar_new(); + gtk_box_pack_start (GTK_BOX (vbox), status_bar, TRUE, TRUE, 0); + gtk_widget_show (status_bar); + + context_id = gtk_statusbar_get_context_id( GTK_STATUSBAR(status_bar), "Statusbar example"); + + button = gtk_button_new_with_label("push item"); + gtk_signal_connect(GTK_OBJECT(button), "clicked", + GTK_SIGNAL_FUNC (push_item), &context_id); + gtk_box_pack_start(GTK_BOX(vbox), button, TRUE, TRUE, 2); + gtk_widget_show(button); + + button = gtk_button_new_with_label("pop last item"); + gtk_signal_connect(GTK_OBJECT(button), "clicked", + GTK_SIGNAL_FUNC (pop_item), &context_id); + gtk_box_pack_start(GTK_BOX(vbox), button, TRUE, TRUE, 2); + gtk_widget_show(button); + + /* always display the window as the last step so it all splashes on + * the screen at once. */ + gtk_widget_show(window); + + gtk_main (); + + return 0; +} +</verb></tscreen> + +<!-- ----------------------------------------------------------------- --> +<sect1>Text Entries +<p> +The Entry widget allows text to be typed and displayed in a single line text box. +The text may be set with functions calls that allow new text to replace, +prepend or append the current contents of the Entry widget. + +There are two functions for creating Entry widgets: +<tscreen><verb> +GtkWidget* gtk_entry_new (void); + +GtkWidget* gtk_entry_new_with_max_length (guint16 max); +</verb></tscreen> + +The first just creates a new Entry widget, whilst the second creates a new Entry and +sets a limit on the length of the text within the Entry.. + +The maximum length of the text within an entry widget may be changed by a call to the following +function. If the current text is longer than this maximum, then it is upto us to alter the Entries +contents appropriately. + +<tscreen><verb> +void gtk_entry_set_max_length (GtkEntry *entry, + guint16 max); +</verb></tscreen> + +There are several functions for altering the text which is currently within the Entry widget. +<tscreen><verb> +void gtk_entry_set_text (GtkEntry *entry, + const gchar *text); +void gtk_entry_append_text (GtkEntry *entry, + const gchar *text); +void gtk_entry_prepend_text (GtkEntry *entry, + const gchar *text); +</verb></tscreen> + +The function gtk_entry_set_text sets the contents of the Entry widget, replacing the +current contents. The functions gtk_entry_append_text and gtk_entry_prepend_text allow +the current contents to be appended and prepended to. + +The next function allows the current insertion point to be set. +<tscreen><verb> +void gtk_entry_set_position (GtkEntry *entry, + gint position); +</verb></tscreen> + +The contents of the Entry can be retrieved by using a call to the following function. This +is useful in the callback functions described below. +<tscreen><verb> +gchar* gtk_entry_get_text (GtkEntry *entry); +</verb></tscreen> + +If we don't want the contents of the Entry to be changed by someone typing into it, we +can change it's edittable state. +<tscreen><verb> +void gtk_entry_set_editable (GtkEntry *entry, + gboolean editable); +</verb></tscreen> + +This function allows us to toggle the edittable state of the Entry widget by passing in +TRUE or FALSE values for the editable argument. + +If we are using the Entry where we don't want the text entered to be visible, for +example when a password is being entered, we can use the following function, which +also takes a boolean flag. +<tscreen><verb> +void gtk_entry_set_visibility (GtkEntry *entry, + gboolean visible); +</verb></tscreen> + +A region of the text may be set as selected by using the following function. This would +most often be used after setting some default text in an Entry, making it easy for the user +to remove it. +<tscreen><verb> +void gtk_entry_select_region (GtkEntry *entry, + gint start, + gint end); +</verb></tscreen> + +If we want to catch when the user has entered text, we can connect to the +<tt/activate/ or <tt/changed/ signal. Activate is raised when the user hits +the enter key within the Entry widget. Changed is raised when the text changes at all, +e.g. for every character entered or removed. + +The following code is an example of using an Entry widget. +<tscreen><verb> +/* entry.c */ + +#include <gtk/gtk.h> + +void enter_callback(GtkWidget *widget, GtkWidget *entry) +{ + gchar *entry_text; + entry_text = gtk_entry_get_text(GTK_ENTRY(entry)); + printf("Entry contents: %s\n", entry_text); +} + +void entry_toggle_editable (GtkWidget *checkbutton, + GtkWidget *entry) +{ + gtk_entry_set_editable(GTK_ENTRY(entry), + GTK_TOGGLE_BUTTON(checkbutton)->active); +} + +void entry_toggle_visibility (GtkWidget *checkbutton, + GtkWidget *entry) +{ + gtk_entry_set_visibility(GTK_ENTRY(entry), + GTK_TOGGLE_BUTTON(checkbutton)->active); +} + +int main (int argc, char *argv[]) +{ + + GtkWidget *window; + GtkWidget *vbox, *hbox; + GtkWidget *entry; + GtkWidget *button; + GtkWidget *check; + + gtk_init (&argc, &argv); + + /* create a new window */ + window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_widget_set_usize( GTK_WIDGET (window), 200, 100); + gtk_window_set_title(GTK_WINDOW (window), "GTK Entry"); + gtk_signal_connect(GTK_OBJECT (window), "delete_event", + (GtkSignalFunc) gtk_exit, NULL); + + vbox = gtk_vbox_new (FALSE, 0); + gtk_container_add (GTK_CONTAINER (window), vbox); + gtk_widget_show (vbox); + + entry = gtk_entry_new_with_max_length (50); + gtk_signal_connect(GTK_OBJECT(entry), "activate", + GTK_SIGNAL_FUNC(enter_callback), + entry); + gtk_entry_set_text (GTK_ENTRY (entry), "hello"); + gtk_entry_append_text (GTK_ENTRY (entry), " world"); + gtk_entry_select_region (GTK_ENTRY (entry), + 0, GTK_ENTRY(entry)->text_length); + gtk_box_pack_start (GTK_BOX (vbox), entry, TRUE, TRUE, 0); + gtk_widget_show (entry); + + hbox = gtk_hbox_new (FALSE, 0); + gtk_container_add (GTK_CONTAINER (vbox), hbox); + gtk_widget_show (hbox); + + check = gtk_check_button_new_with_label("Editable"); + gtk_box_pack_start (GTK_BOX (hbox), check, TRUE, TRUE, 0); + gtk_signal_connect (GTK_OBJECT(check), "toggled", + GTK_SIGNAL_FUNC(entry_toggle_editable), entry); + gtk_toggle_button_set_state(GTK_TOGGLE_BUTTON(check), TRUE); + gtk_widget_show (check); + + check = gtk_check_button_new_with_label("Visible"); + gtk_box_pack_start (GTK_BOX (hbox), check, TRUE, TRUE, 0); + gtk_signal_connect (GTK_OBJECT(check), "toggled", + GTK_SIGNAL_FUNC(entry_toggle_visibility), entry); + gtk_toggle_button_set_state(GTK_TOGGLE_BUTTON(check), TRUE); + gtk_widget_show (check); + + button = gtk_button_new_with_label ("Close"); + gtk_signal_connect_object (GTK_OBJECT (button), "clicked", + GTK_SIGNAL_FUNC(gtk_exit), + GTK_OBJECT (window)); + gtk_box_pack_start (GTK_BOX (vbox), button, TRUE, TRUE, 0); + GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT); + gtk_widget_grab_default (button); + gtk_widget_show (button); + + gtk_widget_show(window); + + gtk_main(); + return(0); +} +</verb></tscreen> + +<!-- ***************************************************************** --> +<sect> Container Widgets +<!-- ***************************************************************** --> + +<!-- ----------------------------------------------------------------- --> +<sect1> Notebooks +<p> +The NoteBook Widget is a collection of 'pages' that overlap each other, +each page contains different information. This widget has become more common +lately in GUI programming, and it is a good way to show blocks similar +information that warrant separation in their display. + +The first function call you will need to know, as you can probably +guess by now, is used to create a new notebook widget. + +<tscreen><verb> +GtkWidget* gtk_notebook_new (void); +</verb></tscreen> + +Once the notebook has been created, there are 12 functions that +operate on the notebook widget. Let's look at them individually. + +The first one we will look at is how to position the page indicators. +These page indicators or 'tabs' as they are referred to, can be positioned +in four ways; top, bottom, left, or right. + +<tscreen><verb> +void gtk_notebook_set_tab_pos (GtkNotebook *notebook, GtkPositionType pos); +</verb></tscreen> + +GtkPostionType will be one of the following, and they are pretty self explanatory. +<itemize> +<item> GTK_POS_LEFT +<item> GTK_POS_RIGHT +<item> GTK_POS_TOP +<item> GTK_POS_BOTTOM +</itemize> + +GTK_POS_TOP is the default. + +Next we will look at how to add pages to the notebook. There are three +ways to add pages to the NoteBook. Let's look at the first two together as +they are quite similar. + +<tscreen><verb> +void gtk_notebook_append_page (GtkNotebook *notebook, GtkWidget *child, GtkWidget *tab_label); + +void gtk_notebook_prepend_page (GtkNotebook *notebook, GtkWidget *child, GtkWidget *tab_label); +</verb></tscreen> + +These functions add pages to the notebook by inserting them from the +back of the notebook (append), or the front of the notebook (prepend). +*child is the widget that is placed within the notebook page, and *tab_label is +the label for the page being added. + +The final function for adding a page to the notebook contains all of +the properties of the previous two, but it allows you to specify what position +you want the page to be in the notebook. + +<tscreen><verb> +void gtk_notebook_insert_page (GtkNotebook *notebook, GtkWidget *child, GtkWidget *tab_label, gint position); +</verb></tscreen> + +The parameters are the same as _append_ and _prepend_ except it +contains an extra parameter, position. This parameter is used to specify what +place this page will inserted to. + +Now that we know how to add a page, lets see how we can remove a page +from the notebook. + +<tscreen><verb> +void gtk_notebook_remove_page (GtkNotebook *notebook, gint page_num); +</verb></tscreen> + +This function takes the page specified by page_num and removes it from +the widget *notebook. + +To find out what the current page is in a notebook use the function: + +<tscreen><verb> +gint gtk_notebook_current_page (GtkNotebook *notebook); +</verb></tscreen> + +These next two functions are simple calls to move the notebook page +forward or backward. Simply provide the respective function call with the +notebook widget you wish to operate on. Note: When the NoteBook is currently +on the last page, and gtk_notebook_next_page is called, the notebook will +wrap back to the first page. Likewise, if the NoteBook is on the first page, +and gtk_notebook_prev_page is called, the notebook will wrap to the last page. + +<tscreen><verb> +void gtk_notebook_next_page (GtkNoteBook *notebook); +void gtk_notebook_prev_page (GtkNoteBook *notebook); +</verb></tscreen> + +This next function sets the 'active' page. If you wish the +notebook to be opened to page 5 for example, you would use this function. +Without using this function, the notebook defaults to the first page. + +<tscreen><verb> +void gtk_notebook_set_page (GtkNotebook *notebook, gint page_num); +</verb></tscreen> + +The next two functions add or remove the notebook page tabs and the +notebook border respectively. + +<tscreen><verb> +void gtk_notebook_set_show_tabs (GtkNotebook *notebook, gint show_tabs); +void gtk_notebook_set_show_border (GtkNotebook *notebook, gint show_border); +</verb></tscreen> + +show_tabs and show_border can both be either TRUE or FALSE (0 or 1). + +Now lets look at an example, it is expanded from the testgtk.c code +that comes with the GTK distribution, and it shows all 13 functions. This +small program, creates a window with a notebook and six buttons. The notebook +contains 11 pages, added in three different ways, appended, inserted, and +prepended. The buttons allow you rotate the tab positions, add/remove the tabs +and border, remove a page, change pages in both a forward and backward manner, +and exit the program. + +<tscreen><verb> +/* notebook.c */ + +#include <gtk/gtk.h> + +/* This function rotates the position of the tabs */ +void rotate_book (GtkButton *button, GtkNotebook *notebook) +{ + gtk_notebook_set_tab_pos (notebook, (notebook->tab_pos +1) %4); +} + +/* Add/Remove the page tabs and the borders */ +void tabsborder_book (GtkButton *button, GtkNotebook *notebook) +{ + gint tval = FALSE; + gint bval = FALSE; + if (notebook->show_tabs == 0) + tval = TRUE; + if (notebook->show_border == 0) + bval = TRUE; + + gtk_notebook_set_show_tabs (notebook, tval); + gtk_notebook_set_show_border (notebook, bval); +} + +/* Remove a page from the notebook */ +void remove_book (GtkButton *button, GtkNotebook *notebook) +{ + gint page; + + page = gtk_notebook_current_page(notebook); + gtk_notebook_remove_page (notebook, page); + /* Need to refresh the widget -- + This forces the widget to redraw itself. */ + gtk_widget_draw(GTK_WIDGET(notebook), NULL); +} + +void delete (GtkWidget *widget, gpointer *data) +{ + gtk_main_quit (); +} + +int main (int argc, char *argv[]) +{ + GtkWidget *window; + GtkWidget *button; + GtkWidget *table; + GtkWidget *notebook; + GtkWidget *frame; + GtkWidget *label; + GtkWidget *checkbutton; + int i; + char bufferf[32]; + char bufferl[32]; + + gtk_init (&argc, &argv); + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + + gtk_signal_connect (GTK_OBJECT (window), "delete_event", + GTK_SIGNAL_FUNC (delete), NULL); + + gtk_container_border_width (GTK_CONTAINER (window), 10); + + table = gtk_table_new(2,6,TRUE); + gtk_container_add (GTK_CONTAINER (window), table); + + /* Create a new notebook, place the position of the tabs */ + notebook = gtk_notebook_new (); + gtk_notebook_set_tab_pos (GTK_NOTEBOOK (notebook), GTK_POS_TOP); + gtk_table_attach_defaults(GTK_TABLE(table), notebook, 0,6,0,1); + gtk_widget_show(notebook); + + /* lets append a bunch of pages to the notebook */ + for (i=0; i < 5; i++) { + sprintf(bufferf, "Append Frame %d", i+1); + sprintf(bufferl, "Page %d", i+1); + + frame = gtk_frame_new (bufferf); + gtk_container_border_width (GTK_CONTAINER (frame), 10); + gtk_widget_set_usize (frame, 100, 75); + gtk_widget_show (frame); + + label = gtk_label_new (bufferf); + gtk_container_add (GTK_CONTAINER (frame), label); + gtk_widget_show (label); + + label = gtk_label_new (bufferl); + gtk_notebook_append_page (GTK_NOTEBOOK (notebook), frame, label); + } + + + /* now lets add a page to a specific spot */ + checkbutton = gtk_check_button_new_with_label ("Check me please!"); + gtk_widget_set_usize(checkbutton, 100, 75); + gtk_widget_show (checkbutton); + + label = gtk_label_new ("Add spot"); + gtk_container_add (GTK_CONTAINER (checkbutton), label); + gtk_widget_show (label); + label = gtk_label_new ("Add page"); + gtk_notebook_insert_page (GTK_NOTEBOOK (notebook), checkbutton, label, 2); + + /* Now finally lets prepend pages to the notebook */ + for (i=0; i < 5; i++) { + sprintf(bufferf, "Prepend Frame %d", i+1); + sprintf(bufferl, "PPage %d", i+1); + + frame = gtk_frame_new (bufferf); + gtk_container_border_width (GTK_CONTAINER (frame), 10); + gtk_widget_set_usize (frame, 100, 75); + gtk_widget_show (frame); + + label = gtk_label_new (bufferf); + gtk_container_add (GTK_CONTAINER (frame), label); + gtk_widget_show (label); + + label = gtk_label_new (bufferl); + gtk_notebook_prepend_page (GTK_NOTEBOOK(notebook), frame, label); + } + + /* Set what page to start at (page 4) */ + gtk_notebook_set_page (GTK_NOTEBOOK(notebook), 3); + + + /* create a bunch of buttons */ + button = gtk_button_new_with_label ("close"); + gtk_signal_connect_object (GTK_OBJECT (button), "clicked", + GTK_SIGNAL_FUNC (delete), NULL); + gtk_table_attach_defaults(GTK_TABLE(table), button, 0,1,1,2); + gtk_widget_show(button); + + button = gtk_button_new_with_label ("next page"); + gtk_signal_connect_object (GTK_OBJECT (button), "clicked", + (GtkSignalFunc) gtk_notebook_next_page, + GTK_OBJECT (notebook)); + gtk_table_attach_defaults(GTK_TABLE(table), button, 1,2,1,2); + gtk_widget_show(button); + + button = gtk_button_new_with_label ("prev page"); + gtk_signal_connect_object (GTK_OBJECT (button), "clicked", + (GtkSignalFunc) gtk_notebook_prev_page, + GTK_OBJECT (notebook)); + gtk_table_attach_defaults(GTK_TABLE(table), button, 2,3,1,2); + gtk_widget_show(button); + + button = gtk_button_new_with_label ("tab position"); + gtk_signal_connect_object (GTK_OBJECT (button), "clicked", + (GtkSignalFunc) rotate_book, GTK_OBJECT(notebook)); + gtk_table_attach_defaults(GTK_TABLE(table), button, 3,4,1,2); + gtk_widget_show(button); + + button = gtk_button_new_with_label ("tabs/border on/off"); + gtk_signal_connect_object (GTK_OBJECT (button), "clicked", + (GtkSignalFunc) tabsborder_book, + GTK_OBJECT (notebook)); + gtk_table_attach_defaults(GTK_TABLE(table), button, 4,5,1,2); + gtk_widget_show(button); + + button = gtk_button_new_with_label ("remove page"); + gtk_signal_connect_object (GTK_OBJECT (button), "clicked", + (GtkSignalFunc) remove_book, + GTK_OBJECT(notebook)); + gtk_table_attach_defaults(GTK_TABLE(table), button, 5,6,1,2); + gtk_widget_show(button); + + gtk_widget_show(table); + gtk_widget_show(window); + + gtk_main (); + + return 0; +} +</verb></tscreen> +<p> +Hopefully this helps you on your way with creating notebooks for your +GTK applications. + +<!-- ----------------------------------------------------------------- --> +<sect1> Scrolled Windows +<p> +Scrolled windows are used to create a scrollable area inside a real window. +You may insert any types of widgets to these scrolled windows, and they will +all be accessable regardless of the size by using the scrollbars. + +The following function is used to create a new scolled window. + +<tscreen><verb> +GtkWidget* gtk_scrolled_window_new (GtkAdjustment *hadjustment, + GtkAdjustment *vadjustment); +</verb></tscreen> +<p> +Where the first argument is the adjustment for the horizontal +direction, and the second, the adjustment for the vertical direction. +These are almost always set to NULL. + +<tscreen><verb> +void gtk_scrolled_window_set_policy (GtkScrolledWindow *scrolled_window, + GtkPolicyType hscrollbar_policy, + GtkPolicyType vscrollbar_policy); +</verb></tscreen> + +This sets the policy to be used with respect to the scrollbars. +The first arguement is the scrolled window you wish to change. The second +sets the policiy for the horizontal scrollbar, and the third, +the vertical scrollbar. + +The policy may be one of GTK_POLICY AUTOMATIC, or GTK_POLICY_ALWAYS. +GTK_POLICY_AUTOMATIC will automatically decide whether you need +scrollbars, wheras GTK_POLICY_ALWAYS will always leave the scrollbars +there. + +Here is a simple example that packs 100 toggle buttons into a scrolled window. +I've only commented on the parts that may be new to you. + +<tscreen><verb> +/* scrolledwin.c */ + +#include <gtk/gtk.h> + +void destroy(GtkWidget *widget, gpointer *data) +{ + gtk_main_quit(); +} + +int main (int argc, char *argv[]) +{ + static GtkWidget *window; + GtkWidget *scrolled_window; + GtkWidget *table; + GtkWidget *button; + char buffer[32]; + int i, j; + + gtk_init (&argc, &argv); + + /* Create a new dialog window for the scrolled window to be + * packed into. A dialog is just like a normal window except it has a + * vbox and a horizontal seperator packed into it. It's just a shortcut + * for creating dialogs */ + window = gtk_dialog_new (); + gtk_signal_connect (GTK_OBJECT (window), "destroy", + (GtkSignalFunc) destroy, NULL); + gtk_window_set_title (GTK_WINDOW (window), "dialog"); + gtk_container_border_width (GTK_CONTAINER (window), 0); + gtk_widget_set_usize(window, 300, 300); + + /* create a new scrolled window. */ + scrolled_window = gtk_scrolled_window_new (NULL, NULL); + + gtk_container_border_width (GTK_CONTAINER (scrolled_window), 10); + + /* the policy is one of GTK_POLICY AUTOMATIC, or GTK_POLICY_ALWAYS. + * GTK_POLICY_AUTOMATIC will automatically decide whether you need + * scrollbars, wheras GTK_POLICY_ALWAYS will always leave the scrollbars + * there. The first one is the horizontal scrollbar, the second, + * the vertical. */ + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window), + GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS); + /* The dialog window is created with a vbox packed into it. */ + gtk_box_pack_start (GTK_BOX (GTK_DIALOG(window)->vbox), scrolled_window, + TRUE, TRUE, 0); + gtk_widget_show (scrolled_window); + + /* create a table of 10 by 10 squares. */ + table = gtk_table_new (10, 10, FALSE); + + /* set the spacing to 10 on x and 10 on y */ + gtk_table_set_row_spacings (GTK_TABLE (table), 10); + gtk_table_set_col_spacings (GTK_TABLE (table), 10); + + /* pack the table into the scrolled window */ + gtk_container_add (GTK_CONTAINER (scrolled_window), table); + gtk_widget_show (table); + + /* this simply creates a grid of toggle buttons on the table + * to demonstrate the scrolled window. */ + for (i = 0; i < 10; i++) + for (j = 0; j < 10; j++) { + sprintf (buffer, "button (%d,%d)\n", i, j); + button = gtk_toggle_button_new_with_label (buffer); + gtk_table_attach_defaults (GTK_TABLE (table), button, + i, i+1, j, j+1); + gtk_widget_show (button); + } + + /* Add a "close" button to the bottom of the dialog */ + button = gtk_button_new_with_label ("close"); + gtk_signal_connect_object (GTK_OBJECT (button), "clicked", + (GtkSignalFunc) gtk_widget_destroy, + GTK_OBJECT (window)); + + /* this makes it so the button is the default. */ + + GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT); + gtk_box_pack_start (GTK_BOX (GTK_DIALOG (window)->action_area), button, TRUE, TRUE, 0); + + /* This grabs this button to be the default button. Simply hitting + * the "Enter" key will cause this button to activate. */ + gtk_widget_grab_default (button); + gtk_widget_show (button); + + gtk_widget_show (window); + + gtk_main(); + + return(0); +} +</verb></tscreen> +<p> +Try playing with resizing the window. You'll notice how the scrollbars +react. You may also wish to use the gtk_widget_set_usize() call to set the default +size of the window or other widgets. + +<!-- ***************************************************************** --> +<sect> List Widgets +<!-- ***************************************************************** --> + +<p> +The GtkList widget is designed to act as a vertical container for widgets +that should be of the type GtkListItem. + +A GtkList widget has its own window to receive events and it's own +background color which is usualy white. As it is directly derived from a +GtkContainer it can be treated as such by using the GTK_CONTAINER(List) +macro, see the GtkContainer widget for more on this. +One should already be familar whith the usage of a GList and its +related functions g_list_*() to be able to use the GtkList widget to +its fully extends. + +There is one field inside the structure definition of the GtkList widget +that will be of greater interest to us, this is: + +<tscreen><verb> +struct _GtkList +{ + ... + GList *selection; + guint selection_mode; + ... +}; +</verb></tscreen> + +The selection field of a GtkList points to a linked list of all items +that are cureently selected, or `NULL' if the selection is empty. +So to learn about the current selection we read the GTK_LIST()->selection +field, but do not modify it since the internal fields are maintained by +the gtk_list_*() functions. + +The selection_mode of the GtkList determines the selection facilities +of a GtkList and therefore the contents of the GTK_LIST()->selection +field: + +The selection_mode may be one of the following: +<itemize> +<item> GTK_SELECTION_SINGLE - The selection is either `NULL' + or contains a GList* pointer + for a single selected item. + +<item> GTK_SELECTION_BROWSE - The selection is `NULL' if the list + contains no widgets or insensitive + ones only, otherwise it contains + a GList pointer for one GList + structure, and therefore exactly + one list item. + +<item> GTK_SELECTION_MULTIPLE - The selection is `NULL' if no list + items are selected or a GList pointer + for the first selected item. That + in turn points to a GList structure + for the second selected item and so + on. + +<item> GTK_SELECTION_EXTENDED - The selection is always `NULL'. +</itemize> +<p> +The default is GTK_SELECTION_MULTIPLE. + +<!-- ----------------------------------------------------------------- --> +<sect1> Signals +<p> +<tscreen><verb> +void selection_changed (GtkList *LIST) +</verb></tscreen> + +This signal will be invoked whenever a the selection field +of a GtkList has changed. This happens when a child of +the GtkList got selected or unselected. + +<tscreen><verb> +void select_child (GtkList *LIST, GtkWidget *CHILD) +</verb></tscreen> + +This signal is invoked when a child of the GtkList is about +to get selected. This happens mainly on calls to +gtk_list_select_item(), gtk_list_select_child(), button presses +and sometimes indirectly triggered on some else occasions where +children get added to or removed from the GtkList. + +<tscreen><verb> +void unselect_child (GtkList *LIST, GtkWidget *CHILD) +</verb></tscreen> + +This signal is invoked when a child of the GtkList is about +to get unselected. This happens mainly on calls to +gtk_list_unselect_item(), gtk_list_unselect_child(), button presses +and sometimes indirectly triggered on some else occasions where +children get added to or removed from the GtkList. + +<!-- ----------------------------------------------------------------- --> +<sect1> Functions +<p> +<tscreen><verb> +guint gtk_list_get_type (void) +</verb></tscreen> + +Returns the `GtkList' type identifier. + +<tscreen><verb> +GtkWidget* gtk_list_new (void) +</verb></tscreen> + +Create a new `GtkList' object. The new widget is +returned as a pointer to a `GtkWidget' object. +`NULL' is returned on failure. + +<tscreen><verb> +void gtk_list_insert_items (GtkList *LIST, GList *ITEMS, gint POSITION) +</verb></tscreen> + +Insert list items into the LIST, starting at POSITION. +ITEMS is a doubly linked list where each nodes data +pointer is expected to point to a newly created GtkListItem. +The GList nodes of ITEMS are taken over by the LIST. + +<tscreen><verb> +void gtk_list_append_items (GtkList *LIST, GList *ITEMS) +</verb></tscreen> + +Insert list items just like gtk_list_insert_items() at the end +of the LIST. The GList nodes of ITEMS are taken over by the LIST. + +<tscreen><verb> +void gtk_list_prepend_items (GtkList *LIST, GList *ITEMS) +</verb></tscreen> + +Insert list items just like gtk_list_insert_items() at the very +beginning of the LIST. The GList nodes of ITEMS are taken over +by the LIST. + +<tscreen><verb> +void gtk_list_remove_items (GtkList *LIST, GList *ITEMS) +</verb></tscreen> + +Remove list items from the LIST. ITEMS is a doubly linked +list where each nodes data pointer is expected to point to a +direct child of LIST. It is the callers responsibility to make a +call to g_list_free(ITEMS) afterwards. Also the caller has to +destroy the list items himself. + +<tscreen><verb> +void gtk_list_clear_items (GtkList *LIST, gint START, gint END) +</verb></tscreen> + +Remove and destroy list items from the LIST. a widget is affected if +its current position within LIST is in the range specified by START +and END. + +<tscreen><verb> +void gtk_list_select_item (GtkList *LIST, gint ITEM) +</verb></tscreen> + +Invoke the select_child signal for a list item +specified through its current position within LIST. + +<tscreen><verb> +void gtk_list_unselect_item (GtkList *LIST, gint ITEM) +</verb></tscreen> + +Invoke the unselect_child signal for a list item +specified through its current position within LIST. + +<tscreen><verb> +void gtk_list_select_child (GtkList *LIST, GtkWidget *CHILD) +</verb></tscreen> + +Invoke the select_child signal for the specified CHILD. + +<tscreen><verb> +void gtk_list_unselect_child (GtkList *LIST, GtkWidget *CHILD) +</verb></tscreen> + +Invoke the unselect_child signal for the specified CHILD. + +<tscreen><verb> +gint gtk_list_child_position (GtkList *LIST, GtkWidget *CHILD) +</verb></tscreen> + +Return the position of CHILD within LIST. `-1' is returned on failure. + +<tscreen><verb> +void gtk_list_set_selection_mode (GtkList *LIST, GtkSelectionMode MODE) +</verb></tscreen> + +Set LIST to the selection mode MODE wich can be of GTK_SELECTION_SINGLE, +GTK_SELECTION_BROWSE, GTK_SELECTION_MULTIPLE or GTK_SELECTION_EXTENDED. + +<tscreen><verb> +GtkList* GTK_LIST (gpointer OBJ) +</verb></tscreen> + +Cast a generic pointer to `GtkList*'. *Note Standard Macros::, for +more info. + +<tscreen><verb> +GtkListClass* GTK_LIST_CLASS (gpointer CLASS) +</verb></tscreen> + +Cast a generic pointer to `GtkListClass*'. *Note Standard Macros::, +for more info. + +<tscreen><verb> +gint GTK_IS_LIST (gpointer OBJ) +</verb></tscreen> + +Determine if a generic pointer refers to a `GtkList' object. *Note +Standard Macros::, for more info. + +<!-- ----------------------------------------------------------------- --> +<sect1> Example +<p> +Following is an example program that will print out the changes +of the selection of a GtkList, and lets you "arrest" list items +into a prison by selecting them with the rightmost mouse button: + +<tscreen><verb> +/* list.c */ + +/* include the gtk+ header files + * include stdio.h, we need that for the printf() function + */ +#include <gtk/gtk.h> +#include <stdio.h> + +/* this is our data identification string to store + * data in list items + */ +const gchar *list_item_data_key="list_item_data"; + + +/* prototypes for signal handler that we are going to connect + * to the GtkList widget + */ +static void sigh_print_selection (GtkWidget *gtklist, + gpointer func_data); +static void sigh_button_event (GtkWidget *gtklist, + GdkEventButton *event, + GtkWidget *frame); + + +/* main function to set up the user interface */ + +gint main (int argc, gchar *argv[]) +{ + GtkWidget *separator; + GtkWidget *window; + GtkWidget *vbox; + GtkWidget *scrolled_window; + GtkWidget *frame; + GtkWidget *gtklist; + GtkWidget *button; + GtkWidget *list_item; + GList *dlist; + guint i; + gchar buffer[64]; + + + /* initialize gtk+ (and subsequently gdk) */ + + gtk_init(&argc, &argv); + + + /* create a window to put all the widgets in + * connect gtk_main_quit() to the "destroy" event of + * the window to handle window manager close-window-events + */ + window=gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_window_set_title(GTK_WINDOW(window), "GtkList Example"); + gtk_signal_connect(GTK_OBJECT(window), + "destroy", + GTK_SIGNAL_FUNC(gtk_main_quit), + NULL); + + + /* inside the window we need a box to arrange the widgets + * vertically */ + vbox=gtk_vbox_new(FALSE, 5); + gtk_container_border_width(GTK_CONTAINER(vbox), 5); + gtk_container_add(GTK_CONTAINER(window), vbox); + gtk_widget_show(vbox); + + /* this is the scolled window to put the GtkList widget inside */ + scrolled_window=gtk_scrolled_window_new(NULL, NULL); + gtk_widget_set_usize(scrolled_window, 250, 150); + gtk_container_add(GTK_CONTAINER(vbox), scrolled_window); + gtk_widget_show(scrolled_window); + + /* create the GtkList widget + * connect the sigh_print_selection() signal handler + * function to the "selection_changed" signal of the GtkList + * to print out the selected items each time the selection + * has changed */ + gtklist=gtk_list_new(); + gtk_container_add(GTK_CONTAINER(scrolled_window), gtklist); + gtk_widget_show(gtklist); + gtk_signal_connect(GTK_OBJECT(gtklist), + "selection_changed", + GTK_SIGNAL_FUNC(sigh_print_selection), + NULL); + + /* we create a "Prison" to put a list item in ;) + */ + frame=gtk_frame_new("Prison"); + gtk_widget_set_usize(frame, 200, 50); + gtk_container_border_width(GTK_CONTAINER(frame), 5); + gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_OUT); + gtk_container_add(GTK_CONTAINER(vbox), frame); + gtk_widget_show(frame); + + /* connect the sigh_button_event() signal handler to the GtkList + * wich will handle the "arresting" of list items + */ + gtk_signal_connect(GTK_OBJECT(gtklist), + "button_release_event", + GTK_SIGNAL_FUNC(sigh_button_event), + frame); + + /* create a separator + */ + separator=gtk_hseparator_new(); + gtk_container_add(GTK_CONTAINER(vbox), separator); + gtk_widget_show(separator); + + /* finaly create a button and connect it´s "clicked" signal + * to the destroyment of the window + */ + button=gtk_button_new_with_label("Close"); + gtk_container_add(GTK_CONTAINER(vbox), button); + gtk_widget_show(button); + gtk_signal_connect_object(GTK_OBJECT(button), + "clicked", + GTK_SIGNAL_FUNC(gtk_widget_destroy), + GTK_OBJECT(window)); + + + /* now we create 5 list items, each having it´s own + * label and add them to the GtkList using gtk_container_add() + * also we query the text string from the label and + * associate it with the list_item_data_key for each list item + */ + for (i=0; i<5; i++) { + GtkWidget *label; + gchar *string; + + sprintf(buffer, "ListItemContainer with Label #%d", i); + label=gtk_label_new(buffer); + list_item=gtk_list_item_new(); + gtk_container_add(GTK_CONTAINER(list_item), label); + gtk_widget_show(label); + gtk_container_add(GTK_CONTAINER(gtklist), list_item); + gtk_widget_show(list_item); + gtk_label_get(GTK_LABEL(label), &string); + gtk_object_set_data(GTK_OBJECT(list_item), + list_item_data_key, + string); + } + /* here, we are creating another 5 labels, this time + * we use gtk_list_item_new_with_label() for the creation + * we can´t query the text string from the label because + * we don´t have the labels pointer and therefore + * we just associate the list_item_data_key of each + * list item with the same text string + * for adding of the list items we put them all into a doubly + * linked list (GList), and then add them by a single call to + * gtk_list_append_items() + * because we use g_list_prepend() to put the items into the + * doubly linked list, their order will be descending (instead + * of ascending when using g_list_append()) + */ + dlist=NULL; + for (; i<10; i++) { + sprintf(buffer, "List Item with Label %d", i); + list_item=gtk_list_item_new_with_label(buffer); + dlist=g_list_prepend(dlist, list_item); + gtk_widget_show(list_item); + gtk_object_set_data(GTK_OBJECT(list_item), + list_item_data_key, + "ListItem with integrated Label"); + } + gtk_list_append_items(GTK_LIST(gtklist), dlist); + + /* finaly we want to see the window, don´t we? ;) + */ + gtk_widget_show(window); + + /* fire up the main event loop of gtk + */ + gtk_main(); + + /* we get here after gtk_main_quit() has been called which + * happens if the main window gets destroyed + */ + return 0; +} + +/* this is the signal handler that got connected to button + * press/release events of the GtkList + */ +void +sigh_button_event (GtkWidget *gtklist, + GdkEventButton *event, + GtkWidget *frame) +{ + /* we only do something if the third (rightmost mouse button + * was released + */ + if (event->type==GDK_BUTTON_RELEASE && + event->button==3) { + GList *dlist, *free_list; + GtkWidget *new_prisoner; + + /* fetch the currently selected list item which + * will be our next prisoner ;) + */ + dlist=GTK_LIST(gtklist)->selection; + if (dlist) + new_prisoner=GTK_WIDGET(dlist->data); + else + new_prisoner=NULL; + + /* look for already prisoned list items, we + * will put them back into the list + * remember to free the doubly linked list that + * gtk_container_children() returns + */ + dlist=gtk_container_children(GTK_CONTAINER(frame)); + free_list=dlist; + while (dlist) { + GtkWidget *list_item; + + list_item=dlist->data; + + gtk_widget_reparent(list_item, gtklist); + + dlist=dlist->next; + } + g_list_free(free_list); + + /* if we have a new prisoner, remove him from the + * GtkList and put him into the frame "Prison" + * we need to unselect the item before + */ + if (new_prisoner) { + GList static_dlist; + + static_dlist.data=new_prisoner; + static_dlist.next=NULL; + static_dlist.prev=NULL; + + gtk_list_unselect_child(GTK_LIST(gtklist), + new_prisoner); + gtk_widget_reparent(new_prisoner, frame); + } + } +} + +/* this is the signal handler that gets called if GtkList + * emits the "selection_changed" signal + */ +void +sigh_print_selection (GtkWidget *gtklist, + gpointer func_data) +{ + GList *dlist; + + /* fetch the doubly linked list of selected items + * of the GtkList, remember to treat this as read-only! + */ + dlist=GTK_LIST(gtklist)->selection; + + /* if there are no selected items there is nothing more + * to do than just telling the user so + */ + if (!dlist) { + g_print("Selection cleared\n"); + return; + } + /* ok, we got a selection and so we print it + */ + g_print("The selection is a "); + + /* get the list item from the doubly linked list + * and then query the data associated with list_item_data_key + * we then just print it + */ + while (dlist) { + GtkObject *list_item; + gchar *item_data_string; + + list_item=GTK_OBJECT(dlist->data); + item_data_string=gtk_object_get_data(list_item, + list_item_data_key); + g_print("%s ", item_data_string); + + dlist=dlist->next; + } + g_print("\n"); +} +</verb></tscreen> + +<!-- ----------------------------------------------------------------- --> +<sect1> List Item Widget +<p> +The GtkListItem widget is designed to act as a container holding up +to one child, providing functions for selection/deselection just like +the GtkList widget requires them for its children. + +A GtkListItem has its own window to receive events and has its own +background color which is usualy white. + +As it is directly derived from a +GtkItem it can be treated as such by using the GTK_ITEM(ListItem) +macro, see the GtkItem widget for more on this. +Usualy a GtkListItem just holds a label to identify e.g. a filename +within a GtkList -- therefore the convenient function +gtk_list_item_new_with_label() is provided. The same effect can be +achieved by creating a GtkLabel on its own, setting its alignment +to xalign=0 and yalign=0.5 with a subsequent container addition +to the GtkListItem. + +As one is not forced to add a GtkLabel to a GtkListItem, you could +also add a GtkVBox or a GtkArrow etc. to the GtkListItem. + +<!-- ----------------------------------------------------------------- --> +<sect1> Signals +<p> +A GtkListItem does not create new signals on its own, but inherits +the signals of a GtkItem. *Note GtkItem::, for more info. + +<!-- ----------------------------------------------------------------- --> +<sect1> Functions +<p> + +<tscreen><verb> +guint gtk_list_item_get_type (void) +</verb></tscreen> + +Returns the `GtkListItem' type identifier. + +<tscreen><verb> +GtkWidget* gtk_list_item_new (void) +</verb></tscreen> + +Create a new `GtkListItem' object. The new widget is +returned as a pointer to a `GtkWidget' object. +`NULL' is returned on failure. + +<tscreen><verb> +GtkWidget* gtk_list_item_new_with_label (gchar *LABEL) +</verb></tscreen> + +Create a new `GtkListItem' object, having a single GtkLabel as +the sole child. The new widget is returned as a pointer to a +`GtkWidget' object. +`NULL' is returned on failure. + +<tscreen><verb> +void gtk_list_item_select (GtkListItem *LIST_ITEM) +</verb></tscreen> + +This function is basicaly a wrapper around a call to +gtk_item_select (GTK_ITEM (list_item)) which will emit the +select signal. +*Note GtkItem::, for more info. + +<tscreen><verb> +void gtk_list_item_deselect (GtkListItem *LIST_ITEM) +</verb></tscreen> + +This function is basicaly a wrapper around a call to +gtk_item_deselect (GTK_ITEM (list_item)) which will emit the +deselect signal. +*Note GtkItem::, for more info. + +<tscreen><verb> +GtkListItem* GTK_LIST_ITEM (gpointer OBJ) +</verb></tscreen> + +Cast a generic pointer to `GtkListItem*'. *Note Standard Macros::, +for more info. + +<tscreen><verb> +GtkListItemClass* GTK_LIST_ITEM_CLASS (gpointer CLASS) +</verb></tscreen> + +Cast a generic pointer to `GtkListItemClass*'. *Note Standard +Macros::, for more info. + +<tscreen><verb> +gint GTK_IS_LIST_ITEM (gpointer OBJ) +</verb></tscreen> + +Determine if a generic pointer refers to a `GtkListItem' object. +*Note Standard Macros::, for more info. + +<!-- ----------------------------------------------------------------- --> +<sect1> Example +<p> +Please see the GtkList example on this, which covers the usage of a +GtkListItem as well. + +<!-- ***************************************************************** --> +<sect> File Selections +<!-- ***************************************************************** --> +<p> +The file selection widget is a quick and simple way to display a File +dialog box. It comes complete with Ok, Cancel, and Help buttons, a great way +to cut down on programming time. + +To create a new file selection box use: + +<tscreen><verb> +GtkWidget* gtk_file_selection_new (gchar *title); +</verb></tscreen> + +To set the filename, for example to bring up a specific directory, or +give a default filename, use this function: + +<tscreen><verb> +void gtk_file_selection_set_filename (GtkFileSelection *filesel, gchar *filename); +</verb></tscreen> + +To grab the text that the user has entered or clicked on, use this +function: + +<tscreen><verb> +gchar* gtk_file_selection_get_filename (GtkFileSelection *filesel); +</verb></tscreen> + +There are also pointers to the widgets contained within the file +selection widget. These are: + +<itemize> +<item>dir_list +<item>file_list +<item>selection_entry +<item>selection_text +<item>main_vbox +<item>ok_button +<item>cancel_button +<item>help_button +</itemize> + +Most likely you will want to use the ok_button, cancel_button, and +help_button pointers in signaling their use. + +Included here is an example stolen from testgtk.c, modified to run +on it's own. As you will see, there is nothing much to creating a file +selection widget. While, in this example, the Help button appears on the +screen, it does nothing as there is not a signal attached to it. + +<tscreen><verb> +/* filesel.c */ + +#include <gtk/gtk.h> + +/* Get the selected filename and print it to the console */ +void file_ok_sel (GtkWidget *w, GtkFileSelection *fs) +{ + g_print ("%s\n", gtk_file_selection_get_filename (GTK_FILE_SELECTION (fs))); +} + +void destroy (GtkWidget *widget, gpointer *data) +{ + gtk_main_quit (); +} + +int main (int argc, char *argv[]) +{ + GtkWidget *filew; + + gtk_init (&argc, &argv); + + /* Create a new file selection widget */ + filew = gtk_file_selection_new ("File selection"); + + gtk_signal_connect (GTK_OBJECT (filew), "destroy", + (GtkSignalFunc) destroy, &filew); + /* Connect the ok_button to file_ok_sel function */ + gtk_signal_connect (GTK_OBJECT (GTK_FILE_SELECTION (filew)->ok_button), + "clicked", (GtkSignalFunc) file_ok_sel, filew ); + + /* Connect the cancel_button to destroy the widget */ + gtk_signal_connect_object (GTK_OBJECT (GTK_FILE_SELECTION (filew)->cancel_button), + "clicked", (GtkSignalFunc) gtk_widget_destroy, + GTK_OBJECT (filew)); + + /* Lets set the filename, as if this were a save dialog, and we are giving + a default filename */ + gtk_file_selection_set_filename (GTK_FILE_SELECTION(filew), + "penguin.png"); + + gtk_widget_show(filew); + gtk_main (); + return 0; +} +</verb></tscreen> + +<!-- ***************************************************************** --> +<sect>Menu Widgets +<!-- ***************************************************************** --> + +<p> +There are two ways to create menus, there's the easy way, and there's the +hard way. Both have their uses, but you can usually use the menufactory +(the easy way). The "hard" way is to create all the menus using the calls +directly. The easy way is to use the gtk_menu_factory calls. This is +much simpler, but there are advantages and disadvantages to each approach. + +The menufactory is much easier to use, and to add new menus to, although +writing a few wrapper functions to create menus using the manual method +could go a long way towards usability. With the menufactory, it is not +possible to add images or the character '/' to the menus. + +<!-- ----------------------------------------------------------------- --> +<sect1>Manual Menu Creation +<p> +In the true tradition of teaching, we'll show you the hard +way first. <tt>:)</> +<p> +There are three widgets that go into making a menubar and submenus: +<itemize> +<item>a menu item, which is what the user wants to select, e.g. 'Save' +<item>a menu, which acts as a container for the menu items, and +<item>a menubar, which is a container for each of the individual menus, +</itemize> + +This is slightly complicated by the fact that menu item widgets are used for two different things. They are +both the widets that are packed into the menu, and the widget that is packed into the menubar, which, +when selected, activiates the menu. + +Let's look at the functions that are used to create menus and menubars. +This first function is used to create a new menubar. + +<tscreen><verb> +GtkWidget *gtk_menu_bar_new(void); +</verb></tscreen> + +This rather self explanatory function creates a new menubar. You use +gtk_container_add to pack this into a window, or the box_pack functions to +pack it into a box - the same as buttons. + +<tscreen><verb> +GtkWidget *gtk_menu_new(); +</verb></tscreen> + +This function returns a pointer to a new menu, it is never actually shown +(with gtk_widget_show), it is just a container for the menu items. Hopefully this will +become more clear when you look at the example below. +<p> +The next two calls are used to create menu items that are packed into +the menu (and menubar). + +<tscreen><verb> +GtkWidget *gtk_menu_item_new(); +</verb></tscreen> + +and + +<tscreen><verb> +GtkWidget *gtk_menu_item_new_with_label(const char *label); +</verb></tscreen> + +These calls are used to create the menu items that are to be displayed. +Remember to differentiate between a "menu" as created with gtk_menu_new +and a "menu item" as created by the gtk_menu_item_new functions. The +menu item will be an actual button with an associated action, +whereas a menu will be a container holding menu items. + +The gtk_menu_new_with_label and gtk_menu_new functions are just as you'd expect after +reading about the buttons. One creates a new menu item with a label +already packed into it, and the other just creates a blank menu item. + +Once you've created a menu item you have to put it into a menu. This is done using the function +gtk_menu_append. In order to capture when the item is selected by the user, we need to connect +to the <tt/activate/ signal in the usual way. +So, if we wanted to create a standard <tt/File/ menu, with the options <tt/Open/, +<tt/Save/ and <tt/Quit/ the code would look something like + +<tscreen><verb> +file_menu = gtk_menu_new(); /* Don't need to show menus */ + +/* Create the menu items */ +open_item = gtk_menu_item_new_with_label("Open"); +save_item = gtk_menu_item_new_with_label("Save"); +quit_item = gtk_menu_item_new_with_label("Quit"); + +/* Add them to the menu */ +gtk_menu_append( GTK_MENU(file_menu), open_item); +gtk_menu_append( GTK_MENU(file_menu), save_item); +gtk_menu_append( GTK_MENU(file_menu), quit_item); + +/* Attach the callback functions to the activate signal */ +gtk_signal_connect_object( GTK_OBJECT(open_items), "activate", + GTK_SIGNAL_FUNC(menuitem_response), (gpointer) "file.open"); +gtk_signal_connect_object( GTK_OBJECT(save_items), "activate", + GTK_SIGNAL_FUNC(menuitem_response), (gpointer) "file.save"); + +/* We can attach the Quit menu item to our exit function */ +gtk_signal_connect_object( GTK_OBJECT(quit_items), "activate", + GTK_SIGNAL_FUNC(destroy), (gpointer) "file.quit"); + +/* We do need to show menu items */ +gtk_widget_show( open_item ); +gtk_widget_show( save_item ); +gtk_widget_show( quit_item ); +</verb></tscreen> + +At this point we have our menu. Now we need to create a menubar and a menu item for the <tt/File/ entry, +to which we add our menu. The code looks like this + +<tscreen><verb> +menu_bar = gtk_menu_bar_new(); +gtk_container_add( GTK_CONTAINER(window), menu_bar); +gtk_widget_show( menu_bar ); + +file_item = gtk_menu_item_new_with_label("File"); +gtk_widget_show(file_item); +</verb></tscreen> + +Now we need to associate the menu with <tt/file_item/. This is done with the function + +<tscreen> +void gtk_menu_item_set_submenu( GtkMenuItem *menu_item, + GtkWidget *submenu); +</tscreen> + +So, our example would continue with + +<tscreen><verb> +gtk_menu_item_set_submenu( GTK_MENU_ITEM(file_item), file_menu); +</verb></tscreen> + +All that is left to do is to add the menu to the menubar, which is accomplished using the function + +<tscreen> +void gtk_menu_bar_append( GtkMenuBar *menu_bar, GtkWidget *menu_item); +</tscreen> + +which in our case looks like this: + +<tscreen><verb> +gtk_menu_bar_append( menu_bar, file_item ); +</verb></tscreen> + +If we wanted the menu right justified on the menubar, such as help menus often are, we can +use the following function (again on <tt/file_item/ in the current example) before attaching +it to the menubar. +<tscreen><verb> +void gtk_menu_item_right_justify (GtkMenuItem *menu_item); +</verb></tscreen> + +Here is a summary of the steps needed to create a menu bar with menus attached: +<itemize> +<item> Create a new menu using gtk_menu_new() +<item> Use multiple calls to gtk_menu_item_new() for each item you wish to have on + your menu. And use gtk_menu_append() to put each of these new items on + to the menu. +<item> Create a menu item using gtk_menu_item_new(). This will be the root of + the menu, the text appearing here will be on the menubar itself. +<item> Use gtk_menu_item_set_submenu() to attach the menu to + the root menu item (The one created in the above step). +<item> Create a new menubar using gtk_menu_bar_new. This step only needs + to be done once when creating a series of menus on one menu bar. +<item> Use gtk_menu_bar_append to put the root menu onto the menubar. +</itemize> +<p> +Creating a popup menu is nearly the same. The difference is that the +menu is not posted `automatically' by a menubar, but explicitly +by calling the function gtk_menu_popup() from a button-press event, for example. +Take these steps: +<itemize> +<item>Create an event handling function. It needs to have the prototype +<tscreen> +static gint handler(GtkWidget *widget, GdkEvent *event); +</tscreen> +and it will use the event to find out where to pop up the menu. +<item>In the event handler, if event is a mouse button press, treat +<tt>event</tt> as a button event (which it is) and use it as +shown in the sample code to pass information to gtk_menu_popup(). +<item>Bind that event handler to a widget with +<tscreen> +gtk_signal_connect_object(GTK_OBJECT(widget), "event", + GTK_SIGNAL_FUNC (handler), GTK_OBJECT(menu)); +</tscreen> +where <tt>widget</tt> is the widget you are binding to, <tt>handler</tt> +is the handling function, and <tt>menu</tt> is a menu created with +gtk_menu_new(). This can be a menu which is also posted by a menu bar, +as shown in the sample code. +</itemize> + +<!-- ----------------------------------------------------------------- --> +<sect1>Manual Menu Example +<p> +That should about do it. Let's take a look at an example to help clarify. + +<tscreen><verb> +/* menu.c */ + +#include <gtk/gtk.h> + +static gint button_press (GtkWidget *, GdkEvent *); +static void menuitem_response (gchar *); + +int main (int argc, char *argv[]) +{ + + GtkWidget *window; + GtkWidget *menu; + GtkWidget *menu_bar; + GtkWidget *root_menu; + GtkWidget *menu_items; + GtkWidget *vbox; + GtkWidget *button; + char buf[128]; + int i; + + gtk_init (&argc, &argv); + + /* create a new window */ + window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_widget_set_usize( GTK_WIDGET (window), 200, 100); + gtk_window_set_title(GTK_WINDOW (window), "GTK Menu Test"); + gtk_signal_connect(GTK_OBJECT (window), "delete_event", + (GtkSignalFunc) gtk_exit, NULL); + + /* Init the menu-widget, and remember -- never + * gtk_show_widget() the menu widget!! + * This is the menu that holds the menu items, the one that + * will pop up when you click on the "Root Menu" in the app */ + menu = gtk_menu_new(); + + /* Next we make a little loop that makes three menu-entries for "test-menu". + * Notice the call to gtk_menu_append. Here we are adding a list of + * menu items to our menu. Normally, we'd also catch the "clicked" + * signal on each of the menu items and setup a callback for it, + * but it's omitted here to save space. */ + + for(i = 0; i < 3; i++) + { + /* Copy the names to the buf. */ + sprintf(buf, "Test-undermenu - %d", i); + + /* Create a new menu-item with a name... */ + menu_items = gtk_menu_item_new_with_label(buf); + + /* ...and add it to the menu. */ + gtk_menu_append(GTK_MENU (menu), menu_items); + + /* Do something interesting when the menuitem is selected */ + gtk_signal_connect_object(GTK_OBJECT(menu_items), "activate", + GTK_SIGNAL_FUNC(menuitem_response), (gpointer) g_strdup(buf)); + + /* Show the widget */ + gtk_widget_show(menu_items); + } + + /* This is the root menu, and will be the label + * displayed on the menu bar. There won't be a signal handler attached, + * as it only pops up the rest of the menu when pressed. */ + root_menu = gtk_menu_item_new_with_label("Root Menu"); + + gtk_widget_show(root_menu); + + /* Now we specify that we want our newly created "menu" to be the menu + * for the "root menu" */ + gtk_menu_item_set_submenu(GTK_MENU_ITEM (root_menu), menu); + + /* A vbox to put a menu and a button in: */ + vbox = gtk_vbox_new(FALSE, 0); + gtk_container_add(GTK_CONTAINER(window), vbox); + gtk_widget_show(vbox); + + /* Create a menu-bar to hold the menus and add it to our main window */ + menu_bar = gtk_menu_bar_new(); + gtk_box_pack_start(GTK_BOX(vbox), menu_bar, FALSE, FALSE, 2); + gtk_widget_show(menu_bar); + + /* Create a button to which to attach menu as a popup */ + button = gtk_button_new_with_label("press me"); + gtk_signal_connect_object(GTK_OBJECT(button), "event", + GTK_SIGNAL_FUNC (button_press), GTK_OBJECT(menu)); + gtk_box_pack_end(GTK_BOX(vbox), button, TRUE, TRUE, 2); + gtk_widget_show(button); + + /* And finally we append the menu-item to the menu-bar -- this is the + * "root" menu-item I have been raving about =) */ + gtk_menu_bar_append(GTK_MENU_BAR (menu_bar), root_menu); + + /* always display the window as the last step so it all splashes on + * the screen at once. */ + gtk_widget_show(window); + + gtk_main (); + + return 0; +} + + + +/* Respond to a button-press by posting a menu passed in as widget. + * + * Note that the "widget" argument is the menu being posted, NOT + * the button that was pressed. + */ + +static gint button_press (GtkWidget *widget, GdkEvent *event) +{ + + if (event->type == GDK_BUTTON_PRESS) { + GdkEventButton *bevent = (GdkEventButton *) event; + gtk_menu_popup (GTK_MENU(widget), NULL, NULL, NULL, NULL, + bevent->button, bevent->time); + /* Tell calling code that we have handled this event; the buck + * stops here. */ + return TRUE; + } + + /* Tell calling code that we have not handled this event; pass it on. */ + return FALSE; +} + + +/* Print a string when a menu item is selected */ + +static void menuitem_response (gchar *string) +{ + printf("%s\n", string); +} +</verb></tscreen> + +You may also set a menu item to be insensitive and, using an accelerator +table, bind keys to menu functions. + +<!-- ----------------------------------------------------------------- --> +<sect1>Using GtkMenuFactory +<p> +Now that we've shown you the hard way, here's how you do it using the +gtk_menu_factory calls. + +<!-- ----------------------------------------------------------------- --> +<sect1>Menu Factory Example +<p> +Here is an example using the GTK menu factory. This is the first file, +menufactory.h. We keep a separate menufactory.c and mfmain.c because of the global variables used +in the menufactory.c file. + +<tscreen><verb> +/* menufactory.h */ + +#ifndef __MENUFACTORY_H__ +#define __MENUFACTORY_H__ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +void get_main_menu (GtkWidget **menubar, GtkAcceleratorTable **table); +void menus_create(GtkMenuEntry *entries, int nmenu_entries); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __MENUFACTORY_H__ */ +</verb></tscreen> +<p> +And here is the menufactory.c file. + +<tscreen><verb> +/* menufactory.c */ + +#include <gtk/gtk.h> +#include <strings.h> + +#include "mfmain.h" + + +static void menus_remove_accel(GtkWidget * widget, gchar * signal_name, gchar * path); +static gint menus_install_accel(GtkWidget * widget, gchar * signal_name, gchar key, gchar modifiers, gchar * path); +void menus_init(void); +void menus_create(GtkMenuEntry * entries, int nmenu_entries); + + +/* this is the GtkMenuEntry structure used to create new menus. The + * first member is the menu definition string. The second, the + * default accelerator key used to access this menu function with + * the keyboard. The third is the callback function to call when + * this menu item is selected (by the accelerator key, or with the + * mouse.) The last member is the data to pass to your callback function. + */ + +static GtkMenuEntry menu_items[] = +{ + {"<Main>/File/New", "<control>N", NULL, NULL}, + {"<Main>/File/Open", "<control>O", NULL, NULL}, + {"<Main>/File/Save", "<control>S", NULL, NULL}, + {"<Main>/File/Save as", NULL, NULL, NULL}, + {"<Main>/File/<separator>", NULL, NULL, NULL}, + {"<Main>/File/Quit", "<control>Q", file_quit_cmd_callback, "OK, I'll quit"}, + {"<Main>/Options/Test", NULL, NULL, NULL} +}; + +/* calculate the number of menu_item's */ +static int nmenu_items = sizeof(menu_items) / sizeof(menu_items[0]); + +static int initialize = TRUE; +static GtkMenuFactory *factory = NULL; +static GtkMenuFactory *subfactory[1]; +static GHashTable *entry_ht = NULL; + +void get_main_menu(GtkWidget ** menubar, GtkAcceleratorTable ** table) +{ + if (initialize) + menus_init(); + + if (menubar) + *menubar = subfactory[0]->widget; + if (table) + *table = subfactory[0]->table; +} + +void menus_init(void) +{ + if (initialize) { + initialize = FALSE; + + factory = gtk_menu_factory_new(GTK_MENU_FACTORY_MENU_BAR); + subfactory[0] = gtk_menu_factory_new(GTK_MENU_FACTORY_MENU_BAR); + + gtk_menu_factory_add_subfactory(factory, subfactory[0], "<Main>"); + menus_create(menu_items, nmenu_items); + } +} + +void menus_create(GtkMenuEntry * entries, int nmenu_entries) +{ + char *accelerator; + int i; + + if (initialize) + menus_init(); + + if (entry_ht) + for (i = 0; i < nmenu_entries; i++) { + accelerator = g_hash_table_lookup(entry_ht, entries[i].path); + if (accelerator) { + if (accelerator[0] == '\0') + entries[i].accelerator = NULL; + else + entries[i].accelerator = accelerator; + } + } + gtk_menu_factory_add_entries(factory, entries, nmenu_entries); + + for (i = 0; i < nmenu_entries; i++) + if (entries[i].widget) { + gtk_signal_connect(GTK_OBJECT(entries[i].widget), "install_accelerator", + (GtkSignalFunc) menus_install_accel, + entries[i].path); + gtk_signal_connect(GTK_OBJECT(entries[i].widget), "remove_accelerator", + (GtkSignalFunc) menus_remove_accel, + entries[i].path); + } +} + +static gint menus_install_accel(GtkWidget * widget, gchar * signal_name, gchar key, gchar modifiers, gchar * path) +{ + char accel[64]; + char *t1, t2[2]; + + accel[0] = '\0'; + if (modifiers & GDK_CONTROL_MASK) + strcat(accel, "<control>"); + if (modifiers & GDK_SHIFT_MASK) + strcat(accel, "<shift>"); + if (modifiers & GDK_MOD1_MASK) + strcat(accel, "<alt>"); + + t2[0] = key; + t2[1] = '\0'; + strcat(accel, t2); + + if (entry_ht) { + t1 = g_hash_table_lookup(entry_ht, path); + g_free(t1); + } else + entry_ht = g_hash_table_new(g_str_hash, g_str_equal); + + g_hash_table_insert(entry_ht, path, g_strdup(accel)); + + return TRUE; +} + +static void menus_remove_accel(GtkWidget * widget, gchar * signal_name, gchar * path) +{ + char *t; + + if (entry_ht) { + t = g_hash_table_lookup(entry_ht, path); + g_free(t); + + g_hash_table_insert(entry_ht, path, g_strdup("")); + } +} + +void menus_set_sensitive(char *path, int sensitive) +{ + GtkMenuPath *menu_path; + + if (initialize) + menus_init(); + + menu_path = gtk_menu_factory_find(factory, path); + if (menu_path) + gtk_widget_set_sensitive(menu_path->widget, sensitive); + else + g_warning("Unable to set sensitivity for menu which doesn't exist: %s", path); +} + +</verb></tscreen> +<p> +And here's the mfmain.h + +<tscreen><verb> +/* mfmain.h */ + +#ifndef __MFMAIN_H__ +#define __MFMAIN_H__ + + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +void file_quit_cmd_callback(GtkWidget *widget, gpointer data); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __MFMAIN_H__ */ +</verb></tscreen> +<p> +And mfmain.c + +<tscreen><verb> +/* mfmain.c */ + +#include <gtk/gtk.h> + +#include "mfmain.h" +#include "menufactory.h" + + +int main(int argc, char *argv[]) +{ + GtkWidget *window; + GtkWidget *main_vbox; + GtkWidget *menubar; + + GtkAcceleratorTable *accel; + + gtk_init(&argc, &argv); + + window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_signal_connect(GTK_OBJECT(window), "destroy", + GTK_SIGNAL_FUNC(file_quit_cmd_callback), + "WM destroy"); + gtk_window_set_title(GTK_WINDOW(window), "Menu Factory"); + gtk_widget_set_usize(GTK_WIDGET(window), 300, 200); + + main_vbox = gtk_vbox_new(FALSE, 1); + gtk_container_border_width(GTK_CONTAINER(main_vbox), 1); + gtk_container_add(GTK_CONTAINER(window), main_vbox); + gtk_widget_show(main_vbox); + + get_main_menu(&menubar, &accel); + gtk_window_add_accelerator_table(GTK_WINDOW(window), accel); + gtk_box_pack_start(GTK_BOX(main_vbox), menubar, FALSE, TRUE, 0); + gtk_widget_show(menubar); + + gtk_widget_show(window); + gtk_main(); + + return(0); +} + +/* This is just to demonstrate how callbacks work when using the + * menufactory. Often, people put all the callbacks from the menus + * in a separate file, and then have them call the appropriate functions + * from there. Keeps it more organized. */ +void file_quit_cmd_callback (GtkWidget *widget, gpointer data) +{ + g_print ("%s\n", (char *) data); + gtk_exit(0); +} +</verb></tscreen> +<p> +And a makefile so it'll be easier to compile it. + +<tscreen><verb> +# Makefile.mf + +CC = gcc +PROF = -g +C_FLAGS = -Wall $(PROF) -L/usr/local/include -DDEBUG +L_FLAGS = $(PROF) -L/usr/X11R6/lib -L/usr/local/lib +L_POSTFLAGS = -lgtk -lgdk -lglib -lXext -lX11 -lm +PROGNAME = menufactory + +O_FILES = menufactory.o mfmain.o + +$(PROGNAME): $(O_FILES) + rm -f $(PROGNAME) + $(CC) $(L_FLAGS) -o $(PROGNAME) $(O_FILES) $(L_POSTFLAGS) + +.c.o: + $(CC) -c $(C_FLAGS) $< + +clean: + rm -f core *.o $(PROGNAME) nohup.out +distclean: clean + rm -f *~ +</verb></tscreen> +<p> +For now, there's only this example. An explanation and lots 'o' comments +will follow later. + +<!-- ***************************************************************** --> +<sect> Undocumented Widgets +<!-- ***************************************************************** --> + +<p> +These all require authors! :) Please consider contributing to our tutorial. + +If you must use one of these widgets that are undocumented, I strongly +suggest you take a look at their respective header files in the GTK distro. +GTK's function names are very descriptive. Once you have an understanding +of how things work, it's not easy to figure out how to use a widget simply +by looking at it's function declarations. This, along with a few examples +from others' code, and it should be no problem. + +When you do come to understand all the functions of a new undocumented +widget, please consider writing a tutorial on it so others may benifit from +your time. + +<!-- ----------------------------------------------------------------- --> +<sect1> Color Selections + +<!-- ----------------------------------------------------------------- --> +<sect1> Range Controls + +<!-- ----------------------------------------------------------------- --> +<sect1> Text Boxes +<p> + +<!-- ----------------------------------------------------------------- --> +<sect1> Previews +<p> + +(This may need to be rewritten to follow the style of the rest of the tutorial) + +<tscreen><verb> + +Previews serve a number of purposes in GIMP/GTK. The most important one is +this. High quality images may take up to tens of megabytes of memory - easy! +Any operation on an image that big is bound to take a long time. If it takes +you 5-10 trial-and-errors (i.e. 10-20 steps, since you have to revert after +you make an error) to choose the desired modification, it make take you +literally hours to make the right one - if you don't run out of memory +first. People who have spent hours in color darkrooms know the feeling. +Previews to the rescue! + +But the annoyance of the delay is not the only issue. Oftentimes it is +helpful to compare the Before and After versions side-by-side or at least +back-to-back. If you're working with big images and 10 second delays, +obtaining the Before and After impressions is, to say the least, difficult. +For 30M images (4"x6", 600dpi, 24 bit) the side-by-side comparison is right +out for most people, while back-to-back is more like back-to-1001, 1002, +..., 1010-back! Previews to the rescue! + +But there's more. Previews allow for side-by-side pre-previews. In other +words, you write a plug-in (e.g. the filterpack simulation) which would have +a number of here's-what-it-would-look-like-if-you-were-to-do-this previews. +An approach like this acts as a sort of a preview palette and is very +effective fow subtle changes. Let's go previews! + +There's more. For certain plug-ins real-time image-specific human +intervention maybe necessary. In the SuperNova plug-in, for example, the +user is asked to enter the coordinates of the center of the future +supernova. The easiest way to do this, really, is to present the user with a +preview and ask him to intereactively select the spot. Let's go previews! + +Finally, a couple of misc uses. One can use previews even when not working +with big images. For example, they are useful when rendering compicated +patterns. (Just check out the venerable Diffraction plug-in + many other +ones!) As another example, take a look at the colormap rotation plug-in +(work in progress). You can also use previews for little logo's inside you +plug-ins and even for an image of yourself, The Author. Let's go previews! + +When Not to Use Previews + +Don't use previews for graphs, drawing etc. GDK is much faster for that. Use +previews only for rendered images! + +Let's go previews! + +You can stick a preview into just about anything. In a vbox, an hbox, a +table, a button, etc. But they look their best in tight frames around them. +Previews by themselves do not have borders and look flat without them. (Of +course, if the flat look is what you want...) Tight frames provide the +necessary borders. + + [Image][Image] + +Previews in many ways are like any other widgets in GTK (whatever that +means) except they possess an addtional feature: they need to be filled with +some sort of an image! First, we will deal exclusively with the GTK aspect +of previews and then we'll discuss how to fill them. + +GtkWidget *preview! + +Without any ado: + + /* Create a preview widget, + set its size, an show it */ +GtkWidget *preview; +preview=gtk_preview_new(GTK_PREVIEW_COLOR) + /*Other option: + GTK_PREVIEW_GRAYSCALE);*/ +gtk_preview_size (GTK_PREVIEW (preview), WIDTH, HEIGHT); +gtk_widget_show(preview); +my_preview_rendering_function(preview); + +Oh yeah, like I said, previews look good inside frames, so how about: + +GtkWidget *create_a_preview(int Width, + int Height, + int Colorfulness) +{ + GtkWidget *preview; + GtkWidget *frame; + + frame = gtk_frame_new(NULL); + gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN); + gtk_container_border_width (GTK_CONTAINER(frame),0); + gtk_widget_show(frame); + + preview=gtk_preview_new (Colorfulness?GTK_PREVIEW_COLOR + :GTK_PREVIEW_GRAYSCALE); + gtk_preview_size (GTK_PREVIEW (preview), Width, Height); + gtk_container_add(GTK_CONTAINER(frame),preview); + gtk_widget_show(preview); + + my_preview_rendering_function(preview); + return frame; +} + +That's my basic preview. This routine returns the "parent" frame so you can +place it somewhere else in your interface. Of course, you can pass the +parent frame to this routine as a parameter. In many situations, however, +the contents of the preview are changed continually by your application. In +this case you may want to pass a pointer to the preview to a +"create_a_preview()" and thus have control of it later. + +One more important note that may one day save you a lot of time. Sometimes +it is desirable to label you preview. For example, you may label the preview +containing the original image as "Original" and the one containing the +modified image as "Less Original". It might occure to you to pack the +preview along with the appropriate label into a vbox. The unexpected caveat +is that if the label is wider than the preview (which may happen for a +variety of reasons unforseeable to you, from the dynamic decision on the +size of the preview to the size of the font) the frame expands and no longer +fits tightly over the preview. The same problem can probably arise in other +situations as well. + + [Image] + +The solution is to place the preview and the label into a 2x1 table and by +attaching them with the following paramters (this is one possible variations +of course. The key is no GTK_FILL in the second attachment): + +gtk_table_attach(GTK_TABLE(table),label,0,1,0,1, + 0, + GTK_EXPAND|GTK_FILL, + 0,0); +gtk_table_attach(GTK_TABLE(table),frame,0,1,1,2, + GTK_EXPAND, + GTK_EXPAND, + 0,0); + + +And here's the result: + + [Image] + +Misc + +Making a preview clickable is achieved most easily by placing it in a +button. It also adds a nice border around the preview and you may not even +need to place it in a frame. See the Filter Pack Simulation plug-in for an +example. + +This is pretty much it as far as GTK is concerned. + +Filling In a Preview + +In order to familiarize ourselves with the basics of filling in previews, +let's create the following pattern (contrived by trial and error): + + [Image] + +void +my_preview_rendering_function(GtkWidget *preview) +{ +#define SIZE 100 +#define HALF (SIZE/2) + + guchar *row=(guchar *) malloc(3*SIZE); /* 3 bits per dot */ + gint i, j; /* Coordinates */ + double r, alpha, x, y; + + if (preview==NULL) return; /* I usually add this when I want */ + /* to avoid silly crashes. You */ + /* should probably make sure that */ + /* everything has been nicely */ + /* initialized! */ + for (j=0; j < ABS(cos(2*alpha)) ) { /* Are we inside the shape? */ + /* glib.h contains ABS(x). */ + row[i*3+0] = sqrt(1-r)*255; /* Define Red */ + row[i*3+1] = 128; /* Define Green */ + row[i*3+2] = 224; /* Define Blue */ + } /* "+0" is for alignment! */ + else { + row[i*3+0] = r*255; + row[i*3+1] = ABS(sin((float)i/SIZE*2*PI))*255; + row[i*3+2] = ABS(sin((float)j/SIZE*2*PI))*255; + } + } + gtk_preview_draw_row( GTK_PREVIEW(preview),row,0,j,SIZE); + /* Insert "row" into "preview" starting at the point with */ + /* coordinates (0,j) first column, j_th row extending SIZE */ + /* pixels to the right */ + } + + free(row); /* save some space */ + gtk_widget_draw(preview,NULL); /* what does this do? */ + gdk_flush(); /* or this? */ +} + +Non-GIMP users can have probably seen enough to do a lot of things already. +For the GIMP users I have a few pointers to add. + +Image Preview + +It is probably wize to keep a reduced version of the image around with just +enough pixels to fill the preview. This is done by selecting every n'th +pixel where n is the ratio of the size of the image to the size of the +preview. All further operations (including filling in the previews) are then +performed on the reduced number of pixels only. The following is my +implementation of reducing the image. (Keep in mind that I've had only basic +C!) + +(UNTESTED CODE ALERT!!!) + +typedef struct { + gint width; + gint height; + gint bbp; + guchar *rgb; + guchar *mask; +} ReducedImage; + +enum { + SELECTION_ONLY, + SELCTION_IN_CONTEXT, + ENTIRE_IMAGE +}; + +ReducedImage *Reduce_The_Image(GDrawable *drawable, + GDrawable *mask, + gint LongerSize, + gint Selection) +{ + /* This function reduced the image down to the the selected preview size */ + /* The preview size is determine by LongerSize, i.e. the greater of the */ + /* two dimentions. Works for RGB images only! */ + gint RH, RW; /* Reduced height and reduced width */ + gint width, height; /* Width and Height of the area being reduced */ + gint bytes=drawable->bpp; + ReducedImage *temp=(ReducedImage *)malloc(sizeof(ReducedImage)); + + guchar *tempRGB, *src_row, *tempmask, *src_mask_row,R,G,B; + gint i, j, whichcol, whichrow, x1, x2, y1, y2; + GPixelRgn srcPR, srcMask; + gint NoSelectionMade=TRUE; /* Assume that we're dealing with the entire */ + /* image. */ + + gimp_drawable_mask_bounds (drawable->id, &x1, &y1, &x2, &y2); + width = x2-x1; + height = y2-y1; + /* If there's a SELECTION, we got its bounds!) + + if (width != drawable->width && height != drawable->height) + NoSelectionMade=FALSE; + /* Become aware of whether the user has made an active selection */ + /* This will become important later, when creating a reduced mask. */ + + /* If we want to preview the entire image, overrule the above! */ + /* Of course, if no selection has been made, this does nothing! */ + if (Selection==ENTIRE_IMAGE) { + x1=0; + x2=drawable->width; + y1=0; + y2=drawable->height; + } + + /* If we want to preview a selection with some surronding area we */ + /* have to expand it a little bit. Consider it a bit of a riddle. */ + if (Selection==SELECTION_IN_CONTEXT) { + x1=MAX(0, x1-width/2.0); + x2=MIN(drawable->width, x2+width/2.0); + y1=MAX(0, y1-height/2.0); + y2=MIN(drawable->height, y2+height/2.0); + } + + /* How we can determine the width and the height of the area being */ + /* reduced. */ + width = x2-x1; + height = y2-y1; + + /* The lines below determine which dimension is to be the longer */ + /* side. The idea borrowed from the supernova plug-in. I suspect I */ + /* could've thought of it myself, but the truth must be told. */ + /* Plagiarism stinks! */ + if (width>height) { + RW=LongerSize; + RH=(float) height * (float) LongerSize/ (float) width; + } + else { + RH=LongerSize; + RW=(float)width * (float) LongerSize/ (float) height; + } + + /* The intire image is stretched into a string! */ + tempRGB = (guchar *) malloc(RW*RH*bytes); + tempmask = (guchar *) malloc(RW*RH); + + gimp_pixel_rgn_init (&srcPR, drawable, x1, y1, width, height, FALSE, FALSE); + gimp_pixel_rgn_init (&srcMask, mask, x1, y1, width, height, FALSE, FALSE); + + /* Grab enough to save a row of image and a row of mask. */ + src_row = (guchar *) malloc (width*bytes); + src_mask_row = (guchar *) malloc (width); + + for (i=0; i < RH; i++) { + whichrow=(float)i*(float)height/(float)RH; + gimp_pixel_rgn_get_row (&srcPR, src_row, x1, y1+whichrow, width); + gimp_pixel_rgn_get_row (&srcMask, src_mask_row, x1, y1+whichrow, width); + + for (j=0; j < RW; j++) { + whichcol=(float)j*(float)width/(float)RW; + + /* No selection made = each point is completely selected! */ + if (NoSelectionMade) + tempmask[i*RW+j]=255; + else + tempmask[i*RW+j]=src_mask_row[whichcol]; + + /* Add the row to the one long string which now contains the image! */ + tempRGB[i*RW*bytes+j*bytes+0]=src_row[whichcol*bytes+0]; + tempRGB[i*RW*bytes+j*bytes+1]=src_row[whichcol*bytes+1]; + tempRGB[i*RW*bytes+j*bytes+2]=src_row[whichcol*bytes+2]; + + /* Hold on to the alpha as well */ + if (bytes==4) + tempRGB[i*RW*bytes+j*bytes+3]=src_row[whichcol*bytes+3]; + } + } + temp->bpp=bytes; + temp->width=RW; + temp->height=RH; + temp->rgb=tempRGB; + temp->mask=tempmask; + return temp; +} + +The following is a preview function which used the same ReducedImage type! +Note that it uses fakes transparancy (if one is present by means of +fake_transparancy which is defined as follows: + +gint fake_transparency(gint i, gint j) +{ + if ( ((i%20)- 10) * ((j%20)- 10)>0 ) + return 64; + else + return 196; +} + +Now here's the preview function: + +void +my_preview_render_function(GtkWidget *preview, + gint changewhat, + gint changewhich) +{ + gint Inten, bytes=drawable->bpp; + gint i, j, k; + float partial; + gint RW=reduced->width; + gint RH=reduced->height; + guchar *row=malloc(bytes*RW);; + + + for (i=0; i < RH; i++) { + for (j=0; j < RW; j++) { + + row[j*3+0] = reduced->rgb[i*RW*bytes + j*bytes + 0]; + row[j*3+1] = reduced->rgb[i*RW*bytes + j*bytes + 1]; + row[j*3+2] = reduced->rgb[i*RW*bytes + j*bytes + 2]; + + if (bytes==4) + for (k=0; k<3; k++) { + float transp=reduced->rgb[i*RW*bytes+j*bytes+3]/255.0; + row[3*j+k]=transp*a[3*j+k]+(1-transp)*fake_transparency(i,j); + } + } + gtk_preview_draw_row( GTK_PREVIEW(preview),row,0,i,RW); + } + + free(a); + gtk_widget_draw(preview,NULL); + gdk_flush(); +} + +Applicable Routines + +guint gtk_preview_get_type (void); +/* No idea */ +void gtk_preview_uninit (void); +/* No idea */ +GtkWidget* gtk_preview_new (GtkPreviewType type); +/* Described above */ +void gtk_preview_size (GtkPreview *preview, + gint width, + gint height); +/* Allows you to resize an existing preview. */ +/* Apparantly there's a bug in GTK which makes */ +/* this process messy. A way to clean up a mess */ +/* is to manually resize the window containing */ +/* the preview after resizing the preview. */ + +void gtk_preview_put (GtkPreview *preview, + GdkWindow *window, + GdkGC *gc, + gint srcx, + gint srcy, + gint destx, + gint desty, + gint width, + gint height); +/* No idea */ + +void gtk_preview_put_row (GtkPreview *preview, + guchar *src, + guchar *dest, + gint x, + gint y, + gint w); +/* No idea */ + +void gtk_preview_draw_row (GtkPreview *preview, + guchar *data, + gint x, + gint y, + gint w); +/* Described in the text */ + +void gtk_preview_set_expand (GtkPreview *preview, + gint expand); +/* No idea */ + +/* No clue for any of the below but */ +/* should be standard for most widgets */ +void gtk_preview_set_gamma (double gamma); +void gtk_preview_set_color_cube (guint nred_shades, + guint ngreen_shades, + guint nblue_shades, + guint ngray_shades); +void gtk_preview_set_install_cmap (gint install_cmap); +void gtk_preview_set_reserved (gint nreserved); +GdkVisual* gtk_preview_get_visual (void); +GdkColormap* gtk_preview_get_cmap (void); +GtkPreviewInfo* gtk_preview_get_info (void); + +That's all, folks! + +</verb></tscreen> + +<!-- ----------------------------------------------------------------- --> +<sect1> Curves +<p> + +<!-- ***************************************************************** --> +<sect>The EventBox Widget<label id="sec_The_EventBox_Widget"> +<!-- ***************************************************************** --> + +<p> +Some gtk widgets don't have associated X windows, so they just draw on +thier parents. Because of this, they cannot recieve events +and if they are incorrectly sized, they don't clip so you can get +messy overwritting etc. If you require more from these widgets, the +EventBox is for you. + +At first glance, the EventBox widget might appear to be totally +useless. It draws nothing on the screen and responds to no +events. However, it does serve a function - it provides an X window for +its child widget. This is important as many GTK widgets do not +have an associated X window. Not having an X window saves memory and +improves performance, but also has some drawbacks. A widget without an +X window cannot receive events, and does not perform any clipping on +it's contents. Although the name ``EventBox'' emphasizes the +event-handling function, the widget also can be used for clipping. +(And more ... see the example below.) + +<p> +To create a new EventBox widget, use: + +<tscreen><verb> +GtkWidget* gtk_event_box_new (void); +</verb></tscreen> + +<p> +A child widget can then be added to this EventBox: + +<tscreen><verb> +gtk_container_add (GTK_CONTAINER(event_box), widget); +</verb></tscreen> + +<p> +The following example demonstrates both uses of an EventBox - a label +is created that clipped to a small box, and set up so that a +mouse-click on the label causes the program to exit. + +<tscreen><verb> +/* eventbox.c */ + +#include <gtk/gtk.h> + +int +main (int argc, char *argv[]) +{ + GtkWidget *window; + GtkWidget *event_box; + GtkWidget *label; + + gtk_init (&argc, &argv); + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + + gtk_window_set_title (GTK_WINDOW (window), "Event Box"); + + gtk_signal_connect (GTK_OBJECT (window), "destroy", + GTK_SIGNAL_FUNC (gtk_exit), NULL); + + gtk_container_border_width (GTK_CONTAINER (window), 10); + + /* Create an EventBox and add it to our toplevel window */ + + event_box = gtk_event_box_new (); + gtk_container_add (GTK_CONTAINER(window), event_box); + gtk_widget_show (event_box); + + /* Create a long label */ + + label = gtk_label_new ("Click here to quit, quit, quit, quit, quit"); + gtk_container_add (GTK_CONTAINER (event_box), label); + gtk_widget_show (label); + + /* Clip it short. */ + gtk_widget_set_usize (label, 110, 20); + + /* And bind an action to it */ + gtk_widget_set_events (event_box, GDK_BUTTON_PRESS_MASK); + gtk_signal_connect (GTK_OBJECT(event_box), "button_press_event", + GTK_SIGNAL_FUNC (gtk_exit), NULL); + + /* Yet one more thing you need an X window for ... */ + + gtk_widget_realize (event_box); + gdk_window_set_cursor (event_box->window, gdk_cursor_new (GDK_HAND1)); + + gtk_widget_show (window); + + gtk_main (); + + return 0; +} +</verb></tscreen> + +<!-- ***************************************************************** --> +<sect>Setting Widget Attributes<label id="sec_setting_widget_attributes"> +<!-- ***************************************************************** --> + +<p> +This describes the functions used to operate on widgets. These can be used +to set style, padding, size etc. + +(Maybe I should make a whole section on accelerators.) + +<tscreen><verb> +void gtk_widget_install_accelerator (GtkWidget *widget, + GtkAcceleratorTable *table, + gchar *signal_name, + gchar key, + guint8 modifiers); + +void gtk_widget_remove_accelerator (GtkWidget *widget, + GtkAcceleratorTable *table, + gchar *signal_name); + +void gtk_widget_activate (GtkWidget *widget); + +void gtk_widget_set_name (GtkWidget *widget, + gchar *name); +gchar* gtk_widget_get_name (GtkWidget *widget); + +void gtk_widget_set_sensitive (GtkWidget *widget, + gint sensitive); + +void gtk_widget_set_style (GtkWidget *widget, + GtkStyle *style); + +GtkStyle* gtk_widget_get_style (GtkWidget *widget); + +GtkStyle* gtk_widget_get_default_style (void); + +void gtk_widget_set_uposition (GtkWidget *widget, + gint x, + gint y); +void gtk_widget_set_usize (GtkWidget *widget, + gint width, + gint height); + +void gtk_widget_grab_focus (GtkWidget *widget); + +void gtk_widget_show (GtkWidget *widget); + +void gtk_widget_hide (GtkWidget *widget); +</verb></tscreen> + +<!-- ***************************************************************** --> +<sect>Timeouts, IO and Idle Functions<label id="sec_timeouts"> +<!-- ***************************************************************** --> + +<!-- ----------------------------------------------------------------- --> +<sect1>Timeouts +<p> +You may be wondering how you make GTK do useful work when in gtk_main. +Well, you have several options. Using the following functions you can +create a timeout function that will be called every "interval" milliseconds. + +<tscreen><verb> +gint gtk_timeout_add (guint32 interval, + GtkFunction function, + gpointer data); +</verb></tscreen> + +The first argument is the number of milliseconds +between calls to your function. The second argument is the function +you wish to have called, and +the third, the data passed to this callback function. The return value is +an integer "tag" which may be used to stop the timeout by calling: + +<tscreen><verb> +void gtk_timeout_remove (gint tag); +</verb></tscreen> + +You may also stop the timeout function by returning zero or FALSE from +your callback function. Obviously this means if you want your function to +continue to be called, it should return a non-zero value, ie TRUE. + +The declaration of your callback should look something like this: + +<tscreen><verb> +gint timeout_callback (gpointer data); +</verb></tscreen> + +<!-- ----------------------------------------------------------------- --> +<sect1>Monitoring IO +<p> +Another nifty feature of GTK, is the ability to have it check for data on a +file descriptor for you (as returned by open(2) or socket(2)). This is +especially useful for networking applications. The function: + +<tscreen><verb> +gint gdk_input_add (gint source, + GdkInputCondition condition, + GdkInputFunction function, + gpointer data); +</verb></tscreen> + +Where the first argument is the file descriptor you wish to have watched, +and the second specifies what you want GDK to look for. This may be one of: +<p> +GDK_INPUT_READ - Call your function when there is data ready for reading on +your file descriptor. +<p> +GDK_INPUT_WRITE - Call your function when the file descriptor is ready for +writing. +<p> +As I'm sure you've figured out already, the third argument is the function +you wish to have called when the above conditions are satisfied, and the +fourth is the data to pass to this function. +<p> +The return value is a tag that may be used to stop GDK from monitoring this +file descriptor using the following function. +<p> +<tscreen><verb> +void gdk_input_remove (gint tag); +</verb></tscreen> +<p> +The callback function should be declared: +<p> +<tscreen><verb> +void input_callback (gpointer data, gint source, + GdkInputCondition condition); +</verb></tscreen> +<p> + +<!-- ----------------------------------------------------------------- --> +<sect1>Idle Functions +<p> +What if you have a function you want called when nothing else is +happening ? + +<tscreen><verb> +gint gtk_idle_add (GtkFunction function, + gpointer data); +</verb></tscreen> + +This causes GTK to call the specified function whenever nothing else is +happening. + +<tscreen><verb> +void gtk_idle_remove (gint tag); +</verb></tscreen> +<p> +I won't explain the meaning of the arguments as they follow very much like +the ones above. The function pointed to by the first argument to +gtk_idle_add will be called whenever the opportunity arises. As with the +others, returning FALSE will stop the idle function from being called. + +<!-- ***************************************************************** --> +<sect>Managing Selections +<!-- ***************************************************************** --> + +<!-- ----------------------------------------------------------------- --> +<sect1> Overview + +<p> + +One type of interprocess communication supported by GTK is +<em>selections</em>. A selection identifies a chunk of data, for +instance, a portion of text, selected by the user in some fashion, for +instance, by dragging with the mouse. Only one application on a +display, (he <em>owner</em>_ can own a particular selection at one +time, so when a selection is claimed by one application, the previous +owner must indicate to the user that selection has been +relinquished. Other applications can request the contents of a +selection in different forms, called <em>targets</em>. There can be +any number of selections, but most X applications only handle one, the +<em>primary selection</em>. + +<p> +In most cases, it isn't necessary for a GTK application to deal with +selections itself. The standard widgets, such as the Entry widget, +already have the capability to claim the selection when appropriate +(e.g., when the user drags over text), and to retrieve the contents of +the selection owned by another widget, or another application (e.g., +when the user clicks the second mouse button). However, there may be +cases in which you want to give other widgets the ability to supply +the selection, or you wish to retrieve targets not supported by +default. + +<p> +A fundamental concept needed to understand selection handling is that +of the <em>atom</em>. An atom is an integer that uniquely identifies a +string (on a certain display). Certain atoms are predefined by the X +server, and in some cases there are constants in in <tt>gtk.h</tt> +corresponding to these atoms. For instance the constant +<tt>GDK_PRIMARY_SELECTION</tt> corresponds to the string "PRIMARY". +In other cases, you should use the functions +<tt>gdk_atom_intern()</tt>, to get the atom corresponding to a string, +and <tt>gdk_atom_name()</tt>, to get the name of an atom. Both +selections and targets are identifed by atoms. + +<!-- ----------------------------------------------------------------- --> +<sect1> Retrieving the selection + +<p> + +Retrieving the selection is an asynchronous process. To start the +process, you call: + +<tscreen><verb> +gint gtk_selection_convert (GtkWidget *widget, + GdkAtom selection, + GdkAtom target, + guint32 time) +</verb</tscreen> + +This <em>converts</em> the selection into the form specified by +<tt/target/. If it all possible, the time field should be the time +from the event that triggered the selection. This helps make sure that +events occur in the order that the user requested them. + However, if it is not available (for instance, if the conversion was +triggered by a "clicked" signal), then you can use the constant +<tt>GDK_CURRENT_TIME</tt>. + +<p> +When the selection owner responds to the request, a +"selection_received" signal is sent to your application. The handler +for this signal receives a pointer to a <tt>GtkSelectionData</tt> +structure, which is defined as: + +<tscreen><verb> +struct _GtkSelectionData +{ + GdkAtom selection; + GdkAtom target; + GdkAtom type; + gint format; + guchar *data; + gint length; +}; +</verb></tscreen> + +<tt>selection</tt> and <tt>target</tt> are the values you gave in your +<tt>gtk_selection_convert()</tt> call. <tt>type</tt> is an atom that +identifies the type of data returned by the selection owner. Some +possible values are "STRING", a string of latin-1 characters, "ATOM", +a series of atoms, "INTEGER", an integer, etc. Most targets can only +return one type. <tt/format/ gives the length of the units (for +instance characters) in bits. Usually, you don't care about this when +receiving data. <tt>data</tt> is a pointer to the returned data, and +<tt>length</tt> gives the length of the returned data, in bytes. If +<tt>length</tt> is negative, then an error occurred and the selection +could not be retrieved. This might happen if no application owned the +selection, or if you requested a target that the application didn't +support. The buffer is actually guaranteed to be one byte longer than +<tt>length</tt>; the extra byte will always be zero, so it isn't +necessary to make a copy of strings just to null terminate them. + +<p> +In the following example, we retrieve the special target "TARGETS", +which is a list of all targets into which the selection can be +converted. + +<tscreen><verb> +/* gettargets.c */ + +#include <gtk/gtk.h> + +void selection_received (GtkWidget *widget, + GtkSelectionData *selection_data, + gpointer data); + +/* Signal handler invoked when user clicks on the "Get Targets" button */ +void +get_targets (GtkWidget *widget, gpointer data) +{ + static GdkAtom targets_atom = GDK_NONE; + + /* Get the atom corresonding to the string "TARGETS" */ + if (targets_atom == GDK_NONE) + targets_atom = gdk_atom_intern ("TARGETS", FALSE); + + /* And request the "TARGETS" target for the primary selection */ + gtk_selection_convert (widget, GDK_SELECTION_PRIMARY, targets_atom, + GDK_CURRENT_TIME); +} + +/* Signal handler called when the selections owner returns the data */ +void +selection_received (GtkWidget *widget, GtkSelectionData *selection_data, + gpointer data) +{ + GdkAtom *atoms; + GList *item_list; + int i; + + /* **** IMPORTANT **** Check to see if retrieval succeeded */ + if (selection_data->length < 0) + { + g_print ("Selection retrieval failed\n"); + return; + } + /* Make sure we got the data in the expected form */ + if (selection_data->type != GDK_SELECTION_TYPE_ATOM) + { + g_print ("Selection \"TARGETS\" was not returned as atoms!\n"); + return; + } + + /* Print out the atoms we received */ + atoms = (GdkAtom *)selection_data->data; + + item_list = NULL; + for (i=0; i<selection_data->length/sizeof(GdkAtom); i++) + { + char *name; + name = gdk_atom_name (atoms[i]); + if (name != NULL) + g_print ("%s\n",name); + else + g_print ("(bad atom)\n"); + } + + return; +} + +int +main (int argc, char *argv[]) +{ + GtkWidget *window; + GtkWidget *button; + + gtk_init (&argc, &argv); + + /* Create the toplevel window */ + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_window_set_title (GTK_WINDOW (window), "Event Box"); + gtk_container_border_width (GTK_CONTAINER (window), 10); + + gtk_signal_connect (GTK_OBJECT (window), "destroy", + GTK_SIGNAL_FUNC (gtk_exit), NULL); + + /* Create a button the user can click to get targets */ + + button = gtk_button_new_with_label ("Get Targets"); + gtk_container_add (GTK_CONTAINER (window), button); + + gtk_signal_connect (GTK_OBJECT(button), "clicked", + GTK_SIGNAL_FUNC (get_targets), NULL); + gtk_signal_connect (GTK_OBJECT(button), "selection_received", + GTK_SIGNAL_FUNC (selection_received), NULL); + + gtk_widget_show (button); + gtk_widget_show (window); + + gtk_main (); + + return 0; +} +</verb></tscreen> + +<!-- ----------------------------------------------------------------- --> +<sect1> Supplying the selection + +<p> + +Supplying the selection is a bit more complicated. You must register +handlers that will be called when your selection is requested. For +each selection/target pair you will handle, you make a call to: + +<tscreen><verb> +void gtk_selection_add_handler (GtkWidget *widget, + GdkAtom selection, + GdkAtom target, + GtkSelectionFunction function, + GtkRemoveFunction remove_func, + gpointer data); +</verb></tscreen> + +<tt/widget/, <tt/selection/, and <tt/target/ identify the requests +this handler will manage. <tt/remove_func/ if not +NULL, will be called when the signal handler is removed. This is +useful, for instance, for interpreted languages which need to +keep track of a reference count for <tt/data/. + +<p> +The callback function has the signature: + +<tscreen><verb> +typedef void (*GtkSelectionFunction) (GtkWidget *widget, + GtkSelectionData *selection_data, + gpointer data); + +</verb></tscreen> + +The GtkSelectionData is the same as above, but this time, we're +responsible for filling in the fields <tt/type/, <tt/format/, +<tt/data/, and <tt/length/. (The <tt/format/ field is actually +important here - the X server uses it to figure out whether the data +needs to be byte-swapped or not. Usually it will be 8 - <em/i.e./ a +character - or 32 - <em/i.e./ a. integer.) This is done by calling the +function: + +<tscreen><verb> +void gtk_selection_data_set (GtkSelectionData *selection_data, + GdkAtom type, + gint format, + guchar *data, + gint length); +</verb></tscreen> + +This function takes care of properly making a copy of the data so that +you don't have to worry about keeping it around. (You should not fill +in the fields of the GtkSelectionData structure by hand.) + +<p> +When prompted by the user, you claim ownership of the selection by +calling: + +<tscreen><verb> +gint gtk_selection_owner_set (GtkWidget *widget, + GdkAtom selection, + guint32 time); +</verb></tscreen> + +If another application claims ownership of the selection, you will +receive a "selection_clear_event". + +As an example of supplying the selection, the following program adds +selection functionality to a toggle button. When the toggle button is +depressed, the program claims the primary selection. The only target +supported (aside from certain targets like "TARGETS" supplied by GTK +itself), is the "STRING" target. When this target is requested, a +string representation of the time is returned. + +<tscreen><verb> +/* setselection.c */ + +#include <gtk/gtk.h> +#include <time.h> + +/* Callback when the user toggles the selection */ +void +selection_toggled (GtkWidget *widget, gint *have_selection) +{ + if (GTK_TOGGLE_BUTTON(widget)->active) + { + *have_selection = gtk_selection_owner_set (widget, + GDK_SELECTION_PRIMARY, + GDK_CURRENT_TIME); + /* if claiming the selection failed, we return the button to + the out state */ + if (!*have_selection) + gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON(widget), FALSE); + } + else + { + if (*have_selection) + { + /* Before clearing the selection by setting the owner to NULL, + we check if we are the actual owner */ + if (gdk_selection_owner_get (GDK_SELECTION_PRIMARY) == widget->window) + gtk_selection_owner_set (NULL, GDK_SELECTION_PRIMARY, + GDK_CURRENT_TIME); + *have_selection = FALSE; + } + } +} + +/* Called when another application claims the selection */ +gint +selection_clear (GtkWidget *widget, GdkEventSelection *event, + gint *have_selection) +{ + *have_selection = FALSE; + gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON(widget), FALSE); + + return TRUE; +} + +/* Supplies the current time as the selection. */ +void +selection_handle (GtkWidget *widget, + GtkSelectionData *selection_data, + gpointer data) +{ + gchar *timestr; + time_t current_time; + + current_time = time (NULL); + timestr = asctime (localtime(&current_time)); + /* When we return a single string, it should not be null terminated. + That will be done for us */ + + gtk_selection_data_set (selection_data, GDK_SELECTION_TYPE_STRING, + 8, timestr, strlen(timestr)); +} + +int +main (int argc, char *argv[]) +{ + GtkWidget *window; + + GtkWidget *selection_button; + + static int have_selection = FALSE; + + gtk_init (&argc, &argv); + + /* Create the toplevel window */ + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_window_set_title (GTK_WINDOW (window), "Event Box"); + gtk_container_border_width (GTK_CONTAINER (window), 10); + + gtk_signal_connect (GTK_OBJECT (window), "destroy", + GTK_SIGNAL_FUNC (gtk_exit), NULL); + + /* Create a toggle button to act as the selection */ + + selection_button = gtk_toggle_button_new_with_label ("Claim Selection"); + gtk_container_add (GTK_CONTAINER (window), selection_button); + gtk_widget_show (selection_button); + + gtk_signal_connect (GTK_OBJECT(selection_button), "toggled", + GTK_SIGNAL_FUNC (selection_toggled), &have_selection); + gtk_signal_connect (GTK_OBJECT(selection_button), "selection_clear_event", + GTK_SIGNAL_FUNC (selection_clear), &have_selection); + + gtk_selection_add_handler (selection_button, GDK_SELECTION_PRIMARY, + GDK_SELECTION_TYPE_STRING, + selection_handle, NULL); + + gtk_widget_show (selection_button); + gtk_widget_show (window); + + gtk_main (); + + return 0; +} +</verb></tscreen> + + +<!-- ***************************************************************** --> +<sect>glib<label id="sec_glib"> +<!-- ***************************************************************** --> + +<p> +glib provides many useful functions and definitions available for use +when creating GDK +and GTK applications. I will list them all here with a brief explanation. +Many are duplicates of standard libc functions so I won't go into +detail on those. This is mostly to be used as a reference, so you know what is +available for use. + +<!-- ----------------------------------------------------------------- --> +<sect1>Definitions +<p> +Definitions for the extremes of many of the standard types are: + +<tscreen><verb> +G_MINFLOAT +G_MAXFLOAT +G_MINDOUBLE +G_MAXDOUBLE +G_MINSHORT +G_MAXSHORT +G_MININT +G_MAXINT +G_MINLONG +G_MAXLONG +</verb></tscreen> + +Also, the following typedefs. The ones left unspecified are dynamically set +depending on the architecture. Remember to avoid counting on the size of a +pointer if you want to be portable! Eg, a pointer on an Alpha is 8 bytes, but 4 +on Intel. + +<tscreen><verb> +char gchar; +short gshort; +long glong; +int gint; +char gboolean; + +unsigned char guchar; +unsigned short gushort; +unsigned long gulong; +unsigned int guint; + +float gfloat; +double gdouble; +long double gldouble; + +void* gpointer; + +gint8 +guint8 +gint16 +guint16 +gint32 +guint32 +</verb></tscreen> + +<!-- ----------------------------------------------------------------- --> +<sect1>Doubly Linked Lists +<p> +The following functions are used to create, manage, and destroy doubly +linked lists. I assume you know what linked lists are, as it is beyond the scope +of this document to explain them. Of course, it's not required that you +know these for general use of GTK, but they are nice to know. + +<tscreen><verb> +GList* g_list_alloc (void); + +void g_list_free (GList *list); + +void g_list_free_1 (GList *list); + +GList* g_list_append (GList *list, + gpointer data); + +GList* g_list_prepend (GList *list, + gpointer data); + +GList* g_list_insert (GList *list, + gpointer data, + gint position); + +GList* g_list_remove (GList *list, + gpointer data); + +GList* g_list_remove_link (GList *list, + GList *link); + +GList* g_list_reverse (GList *list); + +GList* g_list_nth (GList *list, + gint n); + +GList* g_list_find (GList *list, + gpointer data); + +GList* g_list_last (GList *list); + +GList* g_list_first (GList *list); + +gint g_list_length (GList *list); + +void g_list_foreach (GList *list, + GFunc func, + gpointer user_data); +</verb></tscreen> + +<!-- ----------------------------------------------------------------- --> +<sect1>Singly Linked Lists +<p> +Many of the above functions for singly linked lists are identical to the +above. Here is a complete list: +<tscreen><verb> +GSList* g_slist_alloc (void); + +void g_slist_free (GSList *list); + +void g_slist_free_1 (GSList *list); + +GSList* g_slist_append (GSList *list, + gpointer data); + +GSList* g_slist_prepend (GSList *list, + gpointer data); + +GSList* g_slist_insert (GSList *list, + gpointer data, + gint position); + +GSList* g_slist_remove (GSList *list, + gpointer data); + +GSList* g_slist_remove_link (GSList *list, + GSList *link); + +GSList* g_slist_reverse (GSList *list); + +GSList* g_slist_nth (GSList *list, + gint n); + +GSList* g_slist_find (GSList *list, + gpointer data); + +GSList* g_slist_last (GSList *list); + +gint g_slist_length (GSList *list); + +void g_slist_foreach (GSList *list, + GFunc func, + gpointer user_data); + +</verb></tscreen> + +<!-- ----------------------------------------------------------------- --> +<sect1>Memory Management +<p> +<tscreen><verb> +gpointer g_malloc (gulong size); +</verb></tscreen> + +This is a replacement for malloc(). You do not need to check the return +vaule as it is done for you in this function. + +<tscreen><verb> +gpointer g_malloc0 (gulong size); +</verb></tscreen> + +Same as above, but zeroes the memory before returning a pointer to it. + +<tscreen><verb> +gpointer g_realloc (gpointer mem, + gulong size); +</verb></tscreen> + +Relocates "size" bytes of memory starting at "mem". Obviously, the memory should have been +previously allocated. + +<tscreen><verb> +void g_free (gpointer mem); +</verb></tscreen> + +Frees memory. Easy one. + +<tscreen><verb> +void g_mem_profile (void); +</verb></tscreen> + +Dumps a profile of used memory, but requries that you add #define +MEM_PROFILE to the top of glib/gmem.c and re-make and make install. + +<tscreen><verb> +void g_mem_check (gpointer mem); +</verb></tscreen> + +Checks that a memory location is valid. Requires you add #define +MEM_CHECK to the top of gmem.c and re-make and make install. + +<!-- ----------------------------------------------------------------- --> +<sect1>Timers +<p> +Timer functions.. + +<tscreen><verb> +GTimer* g_timer_new (void); + +void g_timer_destroy (GTimer *timer); + +void g_timer_start (GTimer *timer); + +void g_timer_stop (GTimer *timer); + +void g_timer_reset (GTimer *timer); + +gdouble g_timer_elapsed (GTimer *timer, + gulong *microseconds); +</verb></tscreen> + +<!-- ----------------------------------------------------------------- --> +<sect1>String Handling +<p> +A whole mess of string handling functions. They all look very interesting, and +probably better for many purposes than the standard C string functions, but +require documentation. + +<tscreen><verb> +GString* g_string_new (gchar *init); +void g_string_free (GString *string, + gint free_segment); + +GString* g_string_assign (GString *lval, + gchar *rval); + +GString* g_string_truncate (GString *string, + gint len); + +GString* g_string_append (GString *string, + gchar *val); + +GString* g_string_append_c (GString *string, + gchar c); + +GString* g_string_prepend (GString *string, + gchar *val); + +GString* g_string_prepend_c (GString *string, + gchar c); + +void g_string_sprintf (GString *string, + gchar *fmt, + ...); + +void g_string_sprintfa (GString *string, + gchar *fmt, + ...); +</verb></tscreen> + +<!-- ----------------------------------------------------------------- --> +<sect1>Utility and Error Functions +<p> +<tscreen><verb> +gchar* g_strdup (const gchar *str); +</verb></tscreen> + +Replacement strdup function. Copies the +original strings contents to newly allocated memory, and returns a pointer to it. + +<tscreen><verb> +gchar* g_strerror (gint errnum); +</verb></tscreen> + +I recommend using this for all error messages. It's much nicer, and more +portable than perror() or others. The output is usually of the form: + +<tscreen><verb> +program name:function that failed:file or further description:strerror +</verb></tscreen> + +Here's an example of one such call used in our hello_world program: + +<tscreen><verb> +g_print("hello_world:open:%s:%s\n", filename, g_strerror(errno)); +</verb></tscreen> + +<tscreen><verb> +void g_error (gchar *format, ...); +</verb></tscreen> + +Prints an error message. The format is just like printf, but it +prepends "** ERROR **: " to your message, and exits the program. +Use only for fatal errors. + +<tscreen><verb> +void g_warning (gchar *format, ...); +</verb></tscreen> + +Same as above, but prepends "** WARNING **: ", and does not exit the +program. + +<tscreen><verb> +void g_message (gchar *format, ...); +</verb></tscreen> + +Prints "message: " prepended to the string you pass in. + +<tscreen><verb> +void g_print (gchar *format, ...); +</verb></tscreen> + +Replacement for printf(). + +And our last function: + +<tscreen><verb> +gchar* g_strsignal (gint signum); +</verb></tscreen> + +Prints out the name of the Unix system signal given the signal number. +Useful in generic signal handling functions. + +All of the above are more or less just stolen from glib.h. If anyone cares +to document any function, just send me an email! + +<!-- ***************************************************************** --> +<sect>GTK's rc Files +<!-- ***************************************************************** --> + +<p> +GTK has it's own way of dealing with application defaults, by using rc +files. These can be used to set the colors of just about any widget, and +can also be used to tile pixmaps onto the background of some widgets. + +<!-- ----------------------------------------------------------------- --> +<sect1>Functions For rc Files +<p> +When your application starts, you should include a call to: +<tscreen><verb> +void gtk_rc_parse (char *filename); +</verb></tscreen> +<p> +Passing in the filename of your rc file. This will cause GTK to parse this +file, and use the style settings for the widget types defined there. +<p> +If you wish to have a special set of widgets that can take on a different +style from others, or any other logical division of widgets, use a call to: +<tscreen><verb> +void gtk_widget_set_name (GtkWidget *widget, + gchar *name); +</verb></tscreen> +<p> +Passing your newly created widget as the first argument, and the name +you wish to give it as the second. This will allow you to change the +attributes of this widget by name through the rc file. +<p> +If we use a call something like this: + +<tscreen><verb> +button = gtk_button_new_with_label ("Special Button"); +gtk_widget_set_name (button, "special button"); +</verb></tscreen> +<p> +Then this button is given the name "special button" and may be addressed by +name in the rc file as "special button.GtkButton". [<--- Verify ME!] +<p> +The example rc file below, sets the properties of the main window, and lets +all children of that main window inherit the style described by the "main +button" style. The code used in the application is: + +<tscreen><verb> +window = gtk_window_new (GTK_WINDOW_TOPLEVEL); +gtk_widget_set_name (window, "main window"); +</verb></tscreen> +<p> +And then the style is defined in the rc file using: + +<tscreen><verb> +widget "main window.*GtkButton*" style "main_button" +</verb></tscreen> +<p> +Which sets all the GtkButton widgets in the "main window" to the +"main_buttons" style as defined in the rc file. +<p> +As you can see, this is a fairly powerful and flexible system. Use your +imagination as to how best to take advantage of this. + +<!-- ----------------------------------------------------------------- --> +<sect1>GTK's rc File Format +<p> +The format of the GTK file is illustrated in the example below. This is +the testgtkrc file from the GTK distribution, but I've added a +few comments and things. You may wish to include this explanation +your application to allow the user to fine tune his application. +<p> +There are several directives to change the attributes of a widget. +<itemize> +<item>fg - Sets the foreground color of a widget. +<item>bg - Sets the background color of a widget. +<item>bg_pixmap - Sets the background of a widget to a tiled pixmap. +<item>font - Sets the font to be used with the given widget. +</itemize> +<p> +In addition to this, there are several states a widget can be in, and you +can set different colors, pixmaps and fonts for each state. These states are: +<itemize> +<item>NORMAL - The normal state of a widget, without the mouse over top of +it, and not being pressed etc. +<item>PRELIGHT - When the mouse is over top of the widget, colors defined +using this state will be in effect. +<item>ACTIVE - When the widget is pressed or clicked it will be active, and +the attributes assigned by this tag will be in effect. +<item>INSENSITIVE - When a widget is set insensitive, and cannot be +activated, it will take these attributes. +<item>SELECTED - When an object is selected, it takes these attributes. +</itemize> +<p> +When using the "fg" and "bg" keywords to set the colors of widgets, the +format is: +<tscreen><verb> +fg[<STATE>] = { Red, Green, Blue } +</verb></tscreen> +<p> +Where STATE is one of the above states (PRELIGHT, ACTIVE etc), and the Red, +Green and Blue are values in the range of 0 - 1.0, { 1.0, 1.0, 1.0 } being +white. +They must be in float form, or they will register as 0, so a straight +"1" will not work, it must +be "1.0". A straight "0" is fine because it doesn't matter if it's not +recognized. Unrecognized values are set to 0. +<p> +bg_pixmap is very similar to the above, except the colors are replaced by a +filename. + +pixmap_path is a list of paths seperated by ":"'s. These paths will be +searched for any pixmap you specify. + +<p> +The font directive is simply: +<tscreen><verb> +font = "<font name>" +</verb></tscreen> +<p> +Where the only hard part is figuring out the font string. Using xfontsel or +similar utility should help. +<p> +The "widget_class" sets the style of a class of widgets. These classes are +listed in the widget overview on the class hierarchy. +<p> +The "widget" directive sets a specificaly named set of widgets to a +given style, overriding any style set for the given widget class. +These widgets are registered inside the application using the +gtk_widget_set_name() call. This allows you to specify the attributes of a +widget on a per widget basis, rather than setting the attributes of an +entire widget class. I urge you to document any of these special widgets so +users may customize them. +<p> +When the keyword "<tt>parent</>" is used as an attribute, the widget will take on +the attributes of it's parent in the application. +<p> +When defining a style, you may assign the attributes of a previously defined +style to this new one. +<tscreen><verb> +style "main_button" = "button" +{ + font = "-adobe-helvetica-medium-r-normal--*-100-*-*-*-*-*-*" + bg[PRELIGHT] = { 0.75, 0, 0 } +} +</verb></tscreen> +<p> +This example takes the "button" style, and creates a new "main_button" style +simply by changing the font and prelight background color of the "button" +style. +<p> +Of course, many of these attributes don't apply to all widgets. It's a +simple matter of common sense really. Anything that could apply, should. + +<!-- ----------------------------------------------------------------- --> +<sect1>Example rc file +<p> + +<tscreen><verb> +# pixmap_path "<dir 1>:<dir 2>:<dir 3>:..." +# +pixmap_path "/usr/include/X11R6/pixmaps:/home/imain/pixmaps" +# +# style <name> [= <name>] +# { +# <option> +# } +# +# widget <widget_set> style <style_name> +# widget_class <widget_class_set> style <style_name> + + +# Here is a list of all the possible states. Note that some do not apply to +# certain widgets. +# +# NORMAL - The normal state of a widget, without the mouse over top of +# it, and not being pressed etc. +# +# PRELIGHT - When the mouse is over top of the widget, colors defined +# using this state will be in effect. +# +# ACTIVE - When the widget is pressed or clicked it will be active, and +# the attributes assigned by this tag will be in effect. +# +# INSENSITIVE - When a widget is set insensitive, and cannot be +# activated, it will take these attributes. +# +# SELECTED - When an object is selected, it takes these attributes. +# +# Given these states, we can set the attributes of the widgets in each of +# these states using the following directives. +# +# fg - Sets the foreground color of a widget. +# fg - Sets the background color of a widget. +# bg_pixmap - Sets the background of a widget to a tiled pixmap. +# font - Sets the font to be used with the given widget. +# + +# This sets a style called "button". The name is not really important, as +# it is assigned to the actual widgets at the bottom of the file. + +style "window" +{ + #This sets the padding around the window to the pixmap specified. + #bg_pixmap[<STATE>] = "<pixmap filename>" + bg_pixmap[NORMAL] = "warning.xpm" +} + +style "scale" +{ + #Sets the foreground color (font color) to red when in the "NORMAL" + #state. + + fg[NORMAL] = { 1.0, 0, 0 } + + #Sets the background pixmap of this widget to that of it's parent. + bg_pixmap[NORMAL] = "<parent>" +} + +style "button" +{ + # This shows all the possible states for a button. The only one that + # doesn't apply is the SELECTED state. + + fg[PRELIGHT] = { 0, 1.0, 1.0 } + bg[PRELIGHT] = { 0, 0, 1.0 } + bg[ACTIVE] = { 1.0, 0, 0 } + fg[ACTIVE] = { 0, 1.0, 0 } + bg[NORMAL] = { 1.0, 1.0, 0 } + fg[NORMAL] = { .99, 0, .99 } + bg[INSENSITIVE] = { 1.0, 1.0, 1.0 } + fg[INSENSITIVE] = { 1.0, 0, 1.0 } +} + +# In this example, we inherit the attributes of the "button" style and then +# override the font and background color when prelit to create a new +# "main_button" style. + +style "main_button" = "button" +{ + font = "-adobe-helvetica-medium-r-normal--*-100-*-*-*-*-*-*" + bg[PRELIGHT] = { 0.75, 0, 0 } +} + +style "toggle_button" = "button" +{ + fg[NORMAL] = { 1.0, 0, 0 } + fg[ACTIVE] = { 1.0, 0, 0 } + + # This sets the background pixmap of the toggle_button to that of it's + # parent widget (as defined in the application). + bg_pixmap[NORMAL] = "<parent>" +} + +style "text" +{ + bg_pixmap[NORMAL] = "marble.xpm" + fg[NORMAL] = { 1.0, 1.0, 1.0 } +} + +style "ruler" +{ + font = "-adobe-helvetica-medium-r-normal--*-80-*-*-*-*-*-*" +} + +# pixmap_path "~/.pixmaps" + +# These set the widget types to use the styles defined above. +# The widget types are listed in the class hierarchy, but could probably be +# just listed in this document for the users reference. + +widget_class "GtkWindow" style "window" +widget_class "GtkDialog" style "window" +widget_class "GtkFileSelection" style "window" +widget_class "*Gtk*Scale" style "scale" +widget_class "*GtkCheckButton*" style "toggle_button" +widget_class "*GtkRadioButton*" style "toggle_button" +widget_class "*GtkButton*" style "button" +widget_class "*Ruler" style "ruler" +widget_class "*GtkText" style "text" + +# This sets all the buttons that are children of the "main window" to +# the main_buton style. These must be documented to be taken advantage of. +widget "main window.*GtkButton*" style "main_button" +</verb></tscreen> + +<!-- ***************************************************************** --> +<sect>Writing Your Own Widgets +<!-- ***************************************************************** --> + +<!-- ----------------------------------------------------------------- --> +<sect1> Overview +<p> +Although the GTK distribution comes with many types of widgets that +should cover most basic needs, there may come a time when you need to +create your own new widget type. Since GTK uses widget inheretence +extensively, and there is already a widget that +is close to what you want, it is often possible to make a useful new widget type in +just a few lines of code. But before starting work on a new widget, check +around first to make sure that someone has not already written +it. This will prevent duplication of effort and keep the number of +GTK widgets out there to a minimum, which will help keep both the code +and the interface of different applications consistent. As a flip side +to this, once you finish your widget, announce it to the world so +other people can benefit. The best place to do this is probably the +<tt>gtk-list</tt>. + +Complete sources for the example widgets are available at the place you +got this tutorial, or from: + +<htmlurl url="http://www.msc.cornell.edu/~otaylor/gtk-gimp/tutorial" +name="http://www.msc.cornell.edu/~otaylor/gtk-gimp/tutorial"> + + +<!-- ----------------------------------------------------------------- --> +<sect1> The Anatomy Of A Widget + +<p> +In order to create a new widget, it is important to have an +understanding of how GTK objects work. This section is just meant as a +brief overview. See the reference documentation for the details. + +<p> +GTK widgets are implemented in an object oriented fashion. However, +they are implemented in standard C. This greatly improves portability +and stability over using current generation C++ compilers; however, +it does mean that the widget writer has to pay attention to some of +the implementation details. The information common to all instances of +one class of widgets (e.g., to all Button widgets) is stored in the +<em>class structure</em>. There is only one copy of this in +which is stored information about the class's signals +(which act like virtual functions in C). To support inheritance, the +first field in the class structure must be a copy of the parent's +class structure. The declaration of the class structure of GtkButtton +looks like: + +<tscreen><verb> +struct _GtkButtonClass +{ + GtkContainerClass parent_class; + + void (* pressed) (GtkButton *button); + void (* released) (GtkButton *button); + void (* clicked) (GtkButton *button); + void (* enter) (GtkButton *button); + void (* leave) (GtkButton *button); +}; +</verb></tscreen> + +<p> +When a button is treated as a container (for instance, when it is +resized), its class structure can be casted to GtkContainerClass, and +the relevant fields used to handle the signals. + +<p> +There is also a structure for each widget that is created on a +per-instance basis. This structure has fields to store information that +is different for each instance of the widget. We'll call this +structure the <em>object structure</em>. For the Button class, it looks +like: + +<tscreen><verb> +struct _GtkButton +{ + GtkContainer container; + + GtkWidget *child; + + guint in_button : 1; + guint button_down : 1; +}; +</verb></tscreen> + +<p> +Note that, similar to the class structure, the first field is the +object structure of the parent class, so that this structure can be +casted to the parent class's object structure as needed. + +<!-- ----------------------------------------------------------------- --> +<sect1> Creating a Composite widget + +<!-- ----------------------------------------------------------------- --> +<sect2> Introduction + +<p> +One type of widget that you may be interested in creating is a +widget that is merely an aggregate of other GTK widgets. This type of +widget does nothing that couldn't be done without creating new +widgets, but provides a convenient way of packaging user interface +elements for reuse. The FileSelection and ColorSelection widgets in +the standard distribution are examples of this type of widget. + +<p> +The example widget that we'll create in this section is the Tictactoe +widget, a 3x3 array of toggle buttons which triggers a signal when all +three buttons in a row, column, or on one of the diagonals are +depressed. + +<!-- ----------------------------------------------------------------- --> +<sect2> Choosing a parent class + +<p> +The parent class for a composite widget is typically the container +class that holds all of the elements of the composite widget. For +example, the parent class of the FileSelection widget is the +Dialog class. Since our buttons will be arranged in a table, it +might seem natural to make our parent class the GtkTable +class. Unfortunately, this turns out not to work. The creation of a +widget is divided among two functions - a <tt/WIDGETNAME_new()/ +function that the user calls, and a <tt/WIDGETNAME_init()/ function +which does the basic work of initializing the widget which is +independent of the arguments passed to the <tt/_new()/ +function. Descendent widgets only call the <tt/_init/ function of +their parent widget. But this division of labor doesn't work well for +tables, which when created, need to know the number of rows and +columns in the table. Unless we want to duplicate most of the +functionality of <tt/gtk_table_new()/ in our Tictactoe widget, we had +best avoid deriving it from GtkTable. For that reason, we derive it +from GtkVBox instead, and stick our table inside the VBox. + +<!-- ----------------------------------------------------------------- --> +<sect2> The header file + +<p> +Each widget class has a header file which declares the object and +class structures for that widget, along with public functions. +A couple of features are worth pointing out. To prevent duplicate +definitions, we wrap the entire header file in: + +<tscreen><verb> +#ifndef __TICTACTOE_H__ +#define __TICTACTOE_H__ +. +. +. +#endif /* __TICTACTOE_H__ */ +</verb></tscreen> + +And to keep C++ programs that include the header file happy, in: + +<tscreen><verb> +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ +. +. +. +#ifdef __cplusplus +} +#endif /* __cplusplus */ +</verb></tscreen> + +Along with the functions and structures, we declare three standard +macros in our header file, <tt/TICTACTOE(obj)/, +<tt/TICTACTOE_CLASS(klass)/, and <tt/IS_TICTACTOE(obj)/, which cast a +pointer into a pointer to the the object or class structure, and check +if an object is a Tictactoe widget respectively. + +<p> +Here is the complete header file: + +<tscreen><verb> +/* tictactoe.h */ + +#ifndef __TICTACTOE_H__ +#define __TICTACTOE_H__ + +#include <gdk/gdk.h> +#include <gtk/gtkvbox.h> + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#define TICTACTOE(obj) GTK_CHECK_CAST (obj, tictactoe_get_type (), Tictactoe) +#define TICTACTOE_CLASS(klass) GTK_CHECK_CLASS_CAST (klass, tictactoe_get_type (), TictactoeClass) +#define IS_TICTACTOE(obj) GTK_CHECK_TYPE (obj, tictactoe_get_type ()) + + +typedef struct _Tictactoe Tictactoe; +typedef struct _TictactoeClass TictactoeClass; + +struct _Tictactoe +{ + GtkVBox vbox; + + GtkWidget *buttons[3][3]; +}; + +struct _TictactoeClass +{ + GtkVBoxClass parent_class; + + void (* tictactoe) (Tictactoe *ttt); +}; + +guint tictactoe_get_type (void); +GtkWidget* tictactoe_new (void); +void tictactoe_clear (Tictactoe *ttt); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __TICTACTOE_H__ */ + +</verb></tscreen> + +<!-- ----------------------------------------------------------------- --> +<sect2> The <tt/_get_type()/ function. + +<p> +We now continue on to the implementation of our widget. A core +function for every widget is the function +<tt/WIDGETNAME_get_type()/. This function, when first called, tells +GTK about the widget class, and gets an ID that uniquely identifies +the widget class. Upon subsequent calls, it just returns the ID. + +<tscreen><verb> +guint +tictactoe_get_type () +{ + static guint ttt_type = 0; + + if (!ttt_type) + { + GtkTypeInfo ttt_info = + { + "Tictactoe", + sizeof (Tictactoe), + sizeof (TictactoeClass), + (GtkClassInitFunc) tictactoe_class_init, + (GtkObjectInitFunc) tictactoe_init, + (GtkArgFunc) NULL, + }; + + ttt_type = gtk_type_unique (gtk_vbox_get_type (), &ttt_info); + } + + return ttt_type; +} +</verb></tscreen> + +<p> +The GtkTypeInfo structure has the following definition: + +<tscreen><verb> +struct _GtkTypeInfo +{ + gchar *type_name; + guint object_size; + guint class_size; + GtkClassInitFunc class_init_func; + GtkObjectInitFunc object_init_func; + GtkArgFunc arg_func; +}; +</verb></tscreen> + +<p> +The fields of this structure are pretty self-explanatory. We'll ignore +the <tt/arg_func/ field here: It has an important, but as yet largely +unimplemented, role in allowing widget options to be conveniently set +from interpreted languages. Once GTK has a correctly filled in copy of +this structure, it knows how to create objects of a particular widget +type. + +<!-- ----------------------------------------------------------------- --> +<sect2> The <tt/_class_init()/ function + +<p> +The <tt/WIDGETNAME_class_init()/ function initializes the fields of +the widget's class structure, and sets up any signals for the +class. For our Tictactoe widget it looks like: + +<tscreen><verb> + +enum { + TICTACTOE_SIGNAL, + LAST_SIGNAL +}; + +static gint tictactoe_signals[LAST_SIGNAL] = { 0 }; + +static void +tictactoe_class_init (TictactoeClass *class) +{ + GtkObjectClass *object_class; + + object_class = (GtkObjectClass*) class; + + tictactoe_signals[TICTACTOE_SIGNAL] = gtk_signal_new ("tictactoe", + GTK_RUN_FIRST, + object_class->type, + GTK_SIGNAL_OFFSET (TictactoeClass, tictactoe), + gtk_signal_default_marshaller, GTK_ARG_NONE, 0); + + + gtk_object_class_add_signals (object_class, tictactoe_signals, LAST_SIGNAL); + + class->tictactoe = NULL; +} +</verb></tscreen> + +<p> +Our widget has just one signal, the ``tictactoe'' signal that is +invoked when a row, column, or diagonal is completely filled in. Not +every composite widget needs signals, so if you are reading this for +the first time, you may want to skip to the next section now, as +things are going to get a bit complicated. + +The function: + +<tscreen><verb> +gint gtk_signal_new (gchar *name, + GtkSignalRunType run_type, + gint object_type, + gint function_offset, + GtkSignalMarshaller marshaller, + GtkArgType return_val, + gint nparams, + ...); +</verb></tscreen> + +Creates a new signal. The parameters are: + +<itemize> +<item> <tt/name/: The name of the signal. +<item> <tt/run_type/: Whether the default handler runs before or after +user handlers. Usually this will be <tt/GTK_RUN_FIRST/, or <tt/GTK_RUN_LAST/, +although there are other possibilities. +<item> <tt/object_type/: The ID of the object that this signal applies +to. (It will also apply to that objects descendents) +<item> <tt/function_offset/: The offset within the class structure of +a pointer to the default handler. +<item> <tt/marshaller/: A function that is used to invoke the signal +handler. For signal handlers that have no arguments other than the +object that emitted the signal and user data, we can use the +presupplied marshaller function <tt/gtk_signal_default_marshaller/. +<item> <tt/return_val/: The type of the return val. +<item> <tt/nparams/: The number of parameters of the signal handler +(other than the two default ones mentioned above) +<item> <tt/.../: The types of the parameters. +</itemize> + +When specifying types, the <tt/GtkArgType/ enumeration is used: + +<tscreen><verb> +typedef enum +{ + GTK_ARG_INVALID, + GTK_ARG_NONE, + GTK_ARG_CHAR, + GTK_ARG_SHORT, + GTK_ARG_INT, + GTK_ARG_LONG, + GTK_ARG_POINTER, + GTK_ARG_OBJECT, + GTK_ARG_FUNCTION, + GTK_ARG_SIGNAL +} GtkArgType; +</verb></tscreen> + +<p> +<tt/gtk_signal_new()/ returns a unique integer identifier for the +signal, that we store in the <tt/tictactoe_signals/ array, which we +index using an enumeration. (Conventionally, the enumeration elements +are the signal name, uppercased, but here there would be a conflict +with the <tt/TICTACTOE()/ macro, so we called it <tt/TICTACTOE_SIGNAL/ +instead. + +After creating our signals, we need to tell GTK to associate our +signals with the Tictactoe class. We do that by calling +<tt/gtk_object_class_add_signals()/. We then set the pointer which +points to the default handler for the ``tictactoe'' signal to NULL, +indicating that there is no default action. + +<!-- ----------------------------------------------------------------- --> +<sect2> The <tt/_init()/ function. + +<p> + +Each widget class also needs a function to initialize the object +structure. Usually, this function has the fairly limited role of +setting the fields of the structure to default values. For composite +widgets, however, this function also creates the component widgets. + +<tscreen><verb> + +static void +tictactoe_init (Tictactoe *ttt) +{ + GtkWidget *table; + gint i,j; + + table = gtk_table_new (3, 3, TRUE); + gtk_container_add (GTK_CONTAINER(ttt), table); + gtk_widget_show (table); + + for (i=0;i<3; i++) + for (j=0;j<3; j++) + { + ttt->buttons[i][j] = gtk_toggle_button_new (); + gtk_table_attach_defaults (GTK_TABLE(table), ttt->buttons[i][j], + i, i+1, j, j+1); + gtk_signal_connect (GTK_OBJECT (ttt->buttons[i][j]), "toggled", + GTK_SIGNAL_FUNC (tictactoe_toggle), ttt); + gtk_widget_set_usize (ttt->buttons[i][j], 20, 20); + gtk_widget_show (ttt->buttons[i][j]); + } +} +</verb></tscreen> + +<!-- ----------------------------------------------------------------- --> +<sect2> And the rest... + +<p> + +There is one more function that every widget (except for base widget +types like GtkBin that cannot be instantiated) needs to have - the +function that the user calls to create an object of that type. This is +conventionally called <tt/WIDGETNAME_new()/In some +widgets, thought not for the Tictactoe widgets, this function takes +arguments, and does some setup based on the arguments. The other two +functions are specific to the Tictactoe widget. + +<p> +<tt/tictactoe_clear()/ is a public function that resets all the +buttons in the widget to the up position. Note the use of +<tt/gtk_signal_handler_block_by_data()/ to keep our signal handler for +button toggles from being triggered unnecessarily. + +<p> +<tt/tictactoe_toggle()/ is the signal handler that is invoked when the +user clicks on a button. It checks to see if there are any winning +combinations that involve the toggled button, and if so, emits +the "tictactoe" signal. + +<tscreen><verb> +GtkWidget* +tictactoe_new () +{ + return GTK_WIDGET ( gtk_type_new (tictactoe_get_type ())); +} + +void +tictactoe_clear (Tictactoe *ttt) +{ + int i,j; + + for (i=0;i<3;i++) + for (j=0;j<3;j++) + { + gtk_signal_handler_block_by_data (GTK_OBJECT(ttt->buttons[i][j]), ttt); + gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON (ttt->buttons[i][j]), + FALSE); + gtk_signal_handler_unblock_by_data (GTK_OBJECT(ttt->buttons[i][j]), ttt); + } +} + +static void +tictactoe_toggle (GtkWidget *widget, Tictactoe *ttt) +{ + int i,k; + + static int rwins[8][3] = { { 0, 0, 0 }, { 1, 1, 1 }, { 2, 2, 2 }, + { 0, 1, 2 }, { 0, 1, 2 }, { 0, 1, 2 }, + { 0, 1, 2 }, { 0, 1, 2 } }; + static int cwins[8][3] = { { 0, 1, 2 }, { 0, 1, 2 }, { 0, 1, 2 }, + { 0, 0, 0 }, { 1, 1, 1 }, { 2, 2, 2 }, + { 0, 1, 2 }, { 2, 1, 0 } }; + + int success, found; + + for (k=0; k<8; k++) + { + success = TRUE; + found = FALSE; + + for (i=0;i<3;i++) + { + success = success && + GTK_TOGGLE_BUTTON(ttt->buttons[rwins[k][i]][cwins[k][i]])->active; + found = found || + ttt->buttons[rwins[k][i]][cwins[k][i]] == widget; + } + + if (success && found) + { + gtk_signal_emit (GTK_OBJECT (ttt), + tictactoe_signals[TICTACTOE_SIGNAL]); + break; + } + } +} +</verb></tscreen> + +<p> + +And finally, an example program using our Tictactoe widget: + +<tscreen><verb> +#include <gtk/gtk.h> +#include "tictactoe.h" + +/* Invoked when a row, column or diagonal is completed */ +void +win (GtkWidget *widget, gpointer data) +{ + g_print ("Yay!\n"); + tictactoe_clear (TICTACTOE (widget)); +} + +int +main (int argc, char *argv[]) +{ + GtkWidget *window; + GtkWidget *ttt; + + gtk_init (&argc, &argv); + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + + gtk_window_set_title (GTK_WINDOW (window), "Aspect Frame"); + + gtk_signal_connect (GTK_OBJECT (window), "destroy", + GTK_SIGNAL_FUNC (gtk_exit), NULL); + + gtk_container_border_width (GTK_CONTAINER (window), 10); + + /* Create a new Tictactoe widget */ + ttt = tictactoe_new (); + gtk_container_add (GTK_CONTAINER (window), ttt); + gtk_widget_show (ttt); + + /* And attach to its "tictactoe" signal */ + gtk_signal_connect (GTK_OBJECT (ttt), "tictactoe", + GTK_SIGNAL_FUNC (win), NULL); + + gtk_widget_show (window); + + gtk_main (); + + return 0; +} + +</verb></tscreen> + +<!-- ----------------------------------------------------------------- --> +<sect1> Creating a widget from scratch. + +<!-- ----------------------------------------------------------------- --> +<sect2> Introduction + +<p> + +In this section, we'll learn more about how widgets display themselves +on the screen and interact with events. As an example of this, we'll +create a analog dial widget with a pointer that the user can drag to +set the value. + +<!-- ----------------------------------------------------------------- --> +<sect2> Displaying a widget on the screen + +<p> +There are several steps that are involved in displaying on the screen. +After the widget is created with a call to <tt/WIDGETNAME_new()/, +several more functions are needed: + +<itemize> +<item> <tt/WIDGETNAME_realize()/ is responsible for creating an X +window for the widget if it has one. +<item> <tt/WIDGETNAME_map()/ is invoked after the user calls +<tt/gtk_widget_show()/. It is responsible for making sure the widget +is actually drawn on the screen (<em/mapped/). For a container class, +it must also make calls to <tt/map()/> functions of any child widgets. +<item> <tt/WIDGETNAME_draw()/ is invoked when <tt/gtk_widget_draw()/ +is called for the widget or one of its ancestors. It makes the actual +calls to the drawing functions to draw the widget on the screen. For +container widgets, this function must make calls to +<tt/gtk_widget_draw()/ for its child widgets. +<item> <tt/WIDGETNAME_expose()/ is a handler for expose events for the +widget. It makes the necessary calls to the drawing functions to draw +the exposed portion on the screen. For container widgets, this +function must generate expose events for its child widgets which don't +have their own windows. (If they have their own windows, then X will +generate the necessary expose events) +</itemize> + +<p> +You might notice that the last two functions are quite similar - each +is responsible for drawing the widget on the screen. In fact many +types of widgets don't really care about the difference between the +two. The default <tt/draw()/ function in the widget class simply +generates a synthetic expose event for the redrawn area. However, some +types of widgets can save work by distinguishing between the two +functions. For instance, if a widget has multiple X windows, then +since expose events identify the exposed window, it can redraw only +the affected window, which is not possible for calls to <tt/draw()/. + +<p> +Container widgets, even if they don't care about the difference for +themselves, can't simply use the default <tt/draw()/ function because +their child widgets might care about the difference. However, +it would be wasteful to duplicate the drawing code between the two +functions. The convention is that such widgets have a function called +<tt/WIDGETNAME_paint()/ that does the actual work of drawing the +widget, that is then called by the <tt/draw()/ and <tt/expose()/ +functions. + +<p> +In our example approach, since the dial widget is not a container +widget, and only has a single window, we can take the simplest +approach and use the default <tt/draw()/ function and only implement +an <tt/expose()/ function. + +<!-- ----------------------------------------------------------------- --> +<sect2> The origins of the Dial Widget + +<p> +Just as all land animals are just variants on the first amphibian that +crawled up out of the mud, Gtk widgets tend to start off as variants +of some other, previously written widget. Thus, although this section +is entilted ``Creating a Widget from Scratch'', the Dial widget really +began with the source code for the Range widget. This was picked as a +starting point because it would be nice if our Dial had the same +interface as the Scale widgets which are just specialized descendents +of the Range widget. So, though the source code is presented below in +finished form, it should not be implied that it was written, <em>deus +ex machina</em> in this fashion. Also, if you aren't yet familiar with +how scale widgets work from the application writer's point of view, it +would be a good idea to look them over before continuing. + +<!-- ----------------------------------------------------------------- --> +<sect2> The Basics + +<p> +Quite a bit of our widget should look pretty familiar from the +Tictactoe widget. First, we have a header file: + +<tscreen><verb> +/* GTK - The GIMP Toolkit + * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free + * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef __GTK_DIAL_H__ +#define __GTK_DIAL_H__ + +#include <gdk/gdk.h> +#include <gtk/gtkadjustment.h> +#include <gtk/gtkwidget.h> + + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +#define GTK_DIAL(obj) GTK_CHECK_CAST (obj, gtk_dial_get_type (), GtkDial) +#define GTK_DIAL_CLASS(klass) GTK_CHECK_CLASS_CAST (klass, gtk_dial_get_type (), GtkDialClass) +#define GTK_IS_DIAL(obj) GTK_CHECK_TYPE (obj, gtk_dial_get_type ()) + + +typedef struct _GtkDial GtkDial; +typedef struct _GtkDialClass GtkDialClass; + +struct _GtkDial +{ + GtkWidget widget; + + /* update policy (GTK_UPDATE_[CONTINUOUS/DELAYED/DISCONTINUOUS]) */ + guint policy : 2; + + /* Button currently pressed or 0 if none */ + guint8 button; + + /* Dimensions of dial components */ + gint radius; + gint pointer_width; + + /* ID of update timer, or 0 if none */ + guint32 timer; + + /* Current angle */ + gfloat angle; + + /* Old values from adjustment stored so we know when something changes */ + gfloat old_value; + gfloat old_lower; + gfloat old_upper; + + /* The adjustment object that stores the data for this dial */ + GtkAdjustment *adjustment; +}; + +struct _GtkDialClass +{ + GtkWidgetClass parent_class; +}; + + +GtkWidget* gtk_dial_new (GtkAdjustment *adjustment); +guint gtk_dial_get_type (void); +GtkAdjustment* gtk_dial_get_adjustment (GtkDial *dial); +void gtk_dial_set_update_policy (GtkDial *dial, + GtkUpdateType policy); + +void gtk_dial_set_adjustment (GtkDial *dial, + GtkAdjustment *adjustment); +#ifdef __cplusplus +} +#endif /* __cplusplus */ + + +#endif /* __GTK_DIAL_H__ */ + +</verb></tscreen> + +Since there is quite a bit more going on in this widget, than the last +one, we have more fields in the data structure, but otherwise things +are pretty similar. + +<p> + +Next, after including header files, and declaring a few constants, +we have some functions to provide information about the widget +and initialize it: + +<tscreen><verb> +#include <math.h> +#include <stdio.h> +#include <gtk/gtkmain.h> +#include <gtk/gtksignal.h> + +#include "gtkdial.h" + +#define SCROLL_DELAY_LENGTH 300 +#define DIAL_DEFAULT_SIZE 100 + +/* Forward declararations */ + +[ omitted to save space ] + +/* Local data */ + +static GtkWidgetClass *parent_class = NULL; + +guint +gtk_dial_get_type () +{ + static guint dial_type = 0; + + if (!dial_type) + { + GtkTypeInfo dial_info = + { + "GtkDial", + sizeof (GtkDial), + sizeof (GtkDialClass), + (GtkClassInitFunc) gtk_dial_class_init, + (GtkObjectInitFunc) gtk_dial_init, + (GtkArgFunc) NULL, + }; + + dial_type = gtk_type_unique (gtk_widget_get_type (), &dial_info); + } + + return dial_type; +} + +static void +gtk_dial_class_init (GtkDialClass *class) +{ + GtkObjectClass *object_class; + GtkWidgetClass *widget_class; + + object_class = (GtkObjectClass*) class; + widget_class = (GtkWidgetClass*) class; + + parent_class = gtk_type_class (gtk_widget_get_type ()); + + object_class->destroy = gtk_dial_destroy; + + widget_class->realize = gtk_dial_realize; + widget_class->expose_event = gtk_dial_expose; + widget_class->size_request = gtk_dial_size_request; + widget_class->size_allocate = gtk_dial_size_allocate; + widget_class->button_press_event = gtk_dial_button_press; + widget_class->button_release_event = gtk_dial_button_release; + widget_class->motion_notify_event = gtk_dial_motion_notify; +} + +static void +gtk_dial_init (GtkDial *dial) +{ + dial->button = 0; + dial->policy = GTK_UPDATE_CONTINUOUS; + dial->timer = 0; + dial->radius = 0; + dial->pointer_width = 0; + dial->angle = 0.0; + dial->old_value = 0.0; + dial->old_lower = 0.0; + dial->old_upper = 0.0; + dial->adjustment = NULL; +} + +GtkWidget* +gtk_dial_new (GtkAdjustment *adjustment) +{ + GtkDial *dial; + + dial = gtk_type_new (gtk_dial_get_type ()); + + if (!adjustment) + adjustment = (GtkAdjustment*) gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0); + + gtk_dial_set_adjustment (dial, adjustment); + + return GTK_WIDGET (dial); +} + +static void +gtk_dial_destroy (GtkObject *object) +{ + GtkDial *dial; + + g_return_if_fail (object != NULL); + g_return_if_fail (GTK_IS_DIAL (object)); + + dial = GTK_DIAL (object); + + if (dial->adjustment) + gtk_object_unref (GTK_OBJECT (dial->adjustment)); + + if (GTK_OBJECT_CLASS (parent_class)->destroy) + (* GTK_OBJECT_CLASS (parent_class)->destroy) (object); +} +</verb></tscreen> + +Note that this <tt/init()/ function does less than for the Tictactoe +widget, since this is not a composite widget, and the <tt/new()/ +function does more, since it now has an argument. Also, note that when +we store a pointer to the Adjustment object, we increment its +reference count, (and correspondingly decrement when we no longer use +it) so that GTK can keep track of when it can be safely destroyed. + +<p> +Also, there are a few function to manipulate the widget's options: + +<tscreen><verb> +GtkAdjustment* +gtk_dial_get_adjustment (GtkDial *dial) +{ + g_return_val_if_fail (dial != NULL, NULL); + g_return_val_if_fail (GTK_IS_DIAL (dial), NULL); + + return dial->adjustment; +} + +void +gtk_dial_set_update_policy (GtkDial *dial, + GtkUpdateType policy) +{ + g_return_if_fail (dial != NULL); + g_return_if_fail (GTK_IS_DIAL (dial)); + + dial->policy = policy; +} + +void +gtk_dial_set_adjustment (GtkDial *dial, + GtkAdjustment *adjustment) +{ + g_return_if_fail (dial != NULL); + g_return_if_fail (GTK_IS_DIAL (dial)); + + if (dial->adjustment) + { + gtk_signal_disconnect_by_data (GTK_OBJECT (dial->adjustment), (gpointer) dial); + gtk_object_unref (GTK_OBJECT (dial->adjustment)); + } + + dial->adjustment = adjustment; + gtk_object_ref (GTK_OBJECT (dial->adjustment)); + + gtk_signal_connect (GTK_OBJECT (adjustment), "changed", + (GtkSignalFunc) gtk_dial_adjustment_changed, + (gpointer) dial); + gtk_signal_connect (GTK_OBJECT (adjustment), "value_changed", + (GtkSignalFunc) gtk_dial_adjustment_value_changed, + (gpointer) dial); + + dial->old_value = adjustment->value; + dial->old_lower = adjustment->lower; + dial->old_upper = adjustment->upper; + + gtk_dial_update (dial); +} +</verb></tscreen> + +<sect2> <tt/gtk_dial_realize()/ + +<p> +Now we come to some new types of functions. First, we have a function +that does the work of creating the X window. Notice that a mask is +passed to the function <tt/gdk_window_new()/ which specifies which fields of +the GdkWindowAttr structure actually have data in them (the remaining +fields wll be given default values). Also worth noting is the way the +event mask of the widget is created. We call +<tt/gtk_widget_get_events()/ to retrieve the event mask that the user +has specified for this widget (with <tt/gtk_widget_set_events()/, and +add the events that we are interested in ourselves. + +<p> +After creating the window, we set its style and background, and put a +pointer to the widget in the user data field of the GdkWindow. This +last step allows GTK to dispatch events for this window to the correct +widget. + +<tscreen><verb> +static void +gtk_dial_realize (GtkWidget *widget) +{ + GtkDial *dial; + GdkWindowAttr attributes; + gint attributes_mask; + + g_return_if_fail (widget != NULL); + g_return_if_fail (GTK_IS_DIAL (widget)); + + GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED); + dial = GTK_DIAL (widget); + + attributes.x = widget->allocation.x; + attributes.y = widget->allocation.y; + attributes.width = widget->allocation.width; + attributes.height = widget->allocation.height; + attributes.wclass = GDK_INPUT_OUTPUT; + attributes.window_type = GDK_WINDOW_CHILD; + attributes.event_mask = gtk_widget_get_events (widget) | + GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK | + GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK | + GDK_POINTER_MOTION_HINT_MASK; + attributes.visual = gtk_widget_get_visual (widget); + attributes.colormap = gtk_widget_get_colormap (widget); + + attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP; + widget->window = gdk_window_new (widget->parent->window, &attributes, attributes_mask); + + widget->style = gtk_style_attach (widget->style, widget->window); + + gdk_window_set_user_data (widget->window, widget); + + gtk_style_set_background (widget->style, widget->window, GTK_STATE_ACTIVE); +} +</verb></tscreen> + +<sect2> Size negotiation + +<p> +Before the first time that the window containing a widget is +displayed, and whenever the layout of the window changes, GTK asks +each child widget for its desired size. This request is handled by the +function, <tt/gtk_dial_size_request()/. Since our widget isn't a +container widget, and has no real constraints on its size, we just +return a reasonable default value. + +<tscreen><verb> +static void +gtk_dial_size_request (GtkWidget *widget, + GtkRequisition *requisition) +{ + requisition->width = DIAL_DEFAULT_SIZE; + requisition->height = DIAL_DEFAULT_SIZE; +} +</verb></tscreen> + +<p> +After all the widgets have requested an ideal size, the layout of the +window is computed and each child widget is notified of its actual +size. Usually, this will at least as large as the requested size, but +if for instance, the user has resized the window, it may occasionally +be smaller than the requested size. The size notification is handled +by the function <tt/gtk_dial_size_allocate()/. Notice that as well as +computing the sizes of some component pieces for future use, this +routine also does the grunt work of moving the widgets X window into +the new position and size. + +<tscreen><verb> +static void +gtk_dial_size_allocate (GtkWidget *widget, + GtkAllocation *allocation) +{ + GtkDial *dial; + + g_return_if_fail (widget != NULL); + g_return_if_fail (GTK_IS_DIAL (widget)); + g_return_if_fail (allocation != NULL); + + widget->allocation = *allocation; + if (GTK_WIDGET_REALIZED (widget)) + { + dial = GTK_DIAL (widget); + + gdk_window_move_resize (widget->window, + allocation->x, allocation->y, + allocation->width, allocation->height); + + dial->radius = MAX(allocation->width,allocation->height) * 0.45; + dial->pointer_width = dial->radius / 5; + } +} +</verb></tscreen>. + +<!-- ----------------------------------------------------------------- --> +<sect2> <tt/gtk_dial_expose()/ + +<p> +As mentioned above, all the drawing of this widget is done in the +handler for expose events. There's not much to remark on here except +the use of the function <tt/gtk_draw_polygon/ to draw the pointer with +three dimensional shading according to the colors stored in the +widget's style. + +<tscreen><verb> +static gint +gtk_dial_expose (GtkWidget *widget, + GdkEventExpose *event) +{ + GtkDial *dial; + GdkPoint points[3]; + gdouble s,c; + gdouble theta; + gint xc, yc; + gint tick_length; + gint i; + + g_return_val_if_fail (widget != NULL, FALSE); + g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE); + g_return_val_if_fail (event != NULL, FALSE); + + if (event->count > 0) + return FALSE; + + dial = GTK_DIAL (widget); + + gdk_window_clear_area (widget->window, + 0, 0, + widget->allocation.width, + widget->allocation.height); + + xc = widget->allocation.width/2; + yc = widget->allocation.height/2; + + /* Draw ticks */ + + for (i=0; i<25; i++) + { + theta = (i*M_PI/18. - M_PI/6.); + s = sin(theta); + c = cos(theta); + + tick_length = (i%6 == 0) ? dial->pointer_width : dial->pointer_width/2; + + gdk_draw_line (widget->window, + widget->style->fg_gc[widget->state], + xc + c*(dial->radius - tick_length), + yc - s*(dial->radius - tick_length), + xc + c*dial->radius, + yc - s*dial->radius); + } + + /* Draw pointer */ + + s = sin(dial->angle); + c = cos(dial->angle); + + + points[0].x = xc + s*dial->pointer_width/2; + points[0].y = yc + c*dial->pointer_width/2; + points[1].x = xc + c*dial->radius; + points[1].y = yc - s*dial->radius; + points[2].x = xc - s*dial->pointer_width/2; + points[2].y = yc - c*dial->pointer_width/2; + + gtk_draw_polygon (widget->style, + widget->window, + GTK_STATE_NORMAL, + GTK_SHADOW_OUT, + points, 3, + TRUE); + + return FALSE; +} +</verb></tscreen> + +<!-- ----------------------------------------------------------------- --> +<sect2> Event handling + +<p> + +The rest of the widget's code handles various types of events, and +isn't too different from what would be found in many GTK +applications. Two types of events can occur - either the user can +click on the widget with the mouse and drag to move the pointer, or +the value of the Adjustment object can change due to some external +circumstance. + +<p> +When the user clicks on the widget, we check to see if the click was +appropriately near the pointer, and if so, store then button that the +user clicked with in the <tt/button/ field of the widget +structure, and grab all mouse events with a call to +<tt/gtk_grab_add()/. Subsequent motion of the mouse causes the +value of the control to be recomputed (by the function +<tt/gtk_dial_update_mouse/). Depending on the policy that has been +set, "value_changed" events are either generated instantly +(<tt/GTK_UPDATE_CONTINUOUS/), after a delay in a timer added with +<tt/gtk_timeout_add()/ (<tt/GTK_UPDATE_DELAYED/), or only when the +button is released (<tt/GTK_UPDATE_DISCONTINUOUS/). + +<tscreen><verb> +static gint +gtk_dial_button_press (GtkWidget *widget, + GdkEventButton *event) +{ + GtkDial *dial; + gint dx, dy; + double s, c; + double d_parallel; + double d_perpendicular; + + g_return_val_if_fail (widget != NULL, FALSE); + g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE); + g_return_val_if_fail (event != NULL, FALSE); + + dial = GTK_DIAL (widget); + + /* Determine if button press was within pointer region - we + do this by computing the parallel and perpendicular distance of + the point where the mouse was pressed from the line passing through + the pointer */ + + dx = event->x - widget->allocation.width / 2; + dy = widget->allocation.height / 2 - event->y; + + s = sin(dial->angle); + c = cos(dial->angle); + + d_parallel = s*dy + c*dx; + d_perpendicular = fabs(s*dx - c*dy); + + if (!dial->button && + (d_perpendicular < dial->pointer_width/2) && + (d_parallel > - dial->pointer_width)) + { + gtk_grab_add (widget); + + dial->button = event->button; + + gtk_dial_update_mouse (dial, event->x, event->y); + } + + return FALSE; +} + +static gint +gtk_dial_button_release (GtkWidget *widget, + GdkEventButton *event) +{ + GtkDial *dial; + + g_return_val_if_fail (widget != NULL, FALSE); + g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE); + g_return_val_if_fail (event != NULL, FALSE); + + dial = GTK_DIAL (widget); + + if (dial->button == event->button) + { + gtk_grab_remove (widget); + + dial->button = 0; + + if (dial->policy == GTK_UPDATE_DELAYED) + gtk_timeout_remove (dial->timer); + + if ((dial->policy != GTK_UPDATE_CONTINUOUS) && + (dial->old_value != dial->adjustment->value)) + gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), "value_changed"); + } + + return FALSE; +} + +static gint +gtk_dial_motion_notify (GtkWidget *widget, + GdkEventMotion *event) +{ + GtkDial *dial; + GdkModifierType mods; + gint x, y, mask; + + g_return_val_if_fail (widget != NULL, FALSE); + g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE); + g_return_val_if_fail (event != NULL, FALSE); + + dial = GTK_DIAL (widget); + + if (dial->button != 0) + { + x = event->x; + y = event->y; + + if (event->is_hint || (event->window != widget->window)) + gdk_window_get_pointer (widget->window, &x, &y, &mods); + + switch (dial->button) + { + case 1: + mask = GDK_BUTTON1_MASK; + break; + case 2: + mask = GDK_BUTTON2_MASK; + break; + case 3: + mask = GDK_BUTTON3_MASK; + break; + default: + mask = 0; + break; + } + + if (mods & mask) + gtk_dial_update_mouse (dial, x,y); + } + + return FALSE; +} + +static gint +gtk_dial_timer (GtkDial *dial) +{ + g_return_val_if_fail (dial != NULL, FALSE); + g_return_val_if_fail (GTK_IS_DIAL (dial), FALSE); + + if (dial->policy == GTK_UPDATE_DELAYED) + gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), "value_changed"); + + return FALSE; +} + +static void +gtk_dial_update_mouse (GtkDial *dial, gint x, gint y) +{ + gint xc, yc; + gfloat old_value; + + g_return_if_fail (dial != NULL); + g_return_if_fail (GTK_IS_DIAL (dial)); + + xc = GTK_WIDGET(dial)->allocation.width / 2; + yc = GTK_WIDGET(dial)->allocation.height / 2; + + old_value = dial->adjustment->value; + dial->angle = atan2(yc-y, x-xc); + + if (dial->angle < -M_PI/2.) + dial->angle += 2*M_PI; + + if (dial->angle < -M_PI/6) + dial->angle = -M_PI/6; + + if (dial->angle > 7.*M_PI/6.) + dial->angle = 7.*M_PI/6.; + + dial->adjustment->value = dial->adjustment->lower + (7.*M_PI/6 - dial->angle) * + (dial->adjustment->upper - dial->adjustment->lower) / (4.*M_PI/3.); + + if (dial->adjustment->value != old_value) + { + if (dial->policy == GTK_UPDATE_CONTINUOUS) + { + gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), "value_changed"); + } + else + { + gtk_widget_draw (GTK_WIDGET(dial), NULL); + + if (dial->policy == GTK_UPDATE_DELAYED) + { + if (dial->timer) + gtk_timeout_remove (dial->timer); + + dial->timer = gtk_timeout_add (SCROLL_DELAY_LENGTH, + (GtkFunction) gtk_dial_timer, + (gpointer) dial); + } + } + } +} +</verb></tscreen> + +<p> +Changes to the Adjustment by external means are communicated to our +widget by the ``changed'' and ``value_changed'' signals. The handlers +for these functions call <tt/gtk_dial_update()/ to validate the +arguments, compute the new pointer angle, and redraw the widget (by +calling <tt/gtk_widget_draw()/). + +<tscreen><verb> +static void +gtk_dial_update (GtkDial *dial) +{ + gfloat new_value; + + g_return_if_fail (dial != NULL); + g_return_if_fail (GTK_IS_DIAL (dial)); + + new_value = dial->adjustment->value; + + if (new_value < dial->adjustment->lower) + new_value = dial->adjustment->lower; + + if (new_value > dial->adjustment->upper) + new_value = dial->adjustment->upper; + + if (new_value != dial->adjustment->value) + { + dial->adjustment->value = new_value; + gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), "value_changed"); + } + + dial->angle = 7.*M_PI/6. - (new_value - dial->adjustment->lower) * 4.*M_PI/3. / + (dial->adjustment->upper - dial->adjustment->lower); + + gtk_widget_draw (GTK_WIDGET(dial), NULL); +} + +static void +gtk_dial_adjustment_changed (GtkAdjustment *adjustment, + gpointer data) +{ + GtkDial *dial; + + g_return_if_fail (adjustment != NULL); + g_return_if_fail (data != NULL); + + dial = GTK_DIAL (data); + + if ((dial->old_value != adjustment->value) || + (dial->old_lower != adjustment->lower) || + (dial->old_upper != adjustment->upper)) + { + gtk_dial_update (dial); + + dial->old_value = adjustment->value; + dial->old_lower = adjustment->lower; + dial->old_upper = adjustment->upper; + } +} + +static void +gtk_dial_adjustment_value_changed (GtkAdjustment *adjustment, + gpointer data) +{ + GtkDial *dial; + + g_return_if_fail (adjustment != NULL); + g_return_if_fail (data != NULL); + + dial = GTK_DIAL (data); + + if (dial->old_value != adjustment->value) + { + gtk_dial_update (dial); + + dial->old_value = adjustment->value; + } +} +</verb></tscreen> + +<!-- ----------------------------------------------------------------- --> +<sect2> Possible Enhancements + +<p> + +The Dial widget as we've described it so far runs about 670 lines of +code. Although that might sound like a fair bit, we've really +accomplished quite a bit with that much code, especially since much of +that length is headers and boilerplate. However, there are quite a few +more enhancements that could be made to this widget: + +<itemize> +<item> If you try this widget out, you'll find that there is some +flashing as the pointer is dragged around. This is because the entire +widget is erased every time the pointer is moved before being +redrawn. Often, the best way to handle this problem is to draw to an +offscreen pixmap, then copy the final results onto the screen in one +step. (The ProgressBar widget draws itself in this fashion.) + +<item> The user should be able to use the up and down arrow keys to +increase and decrease the value. + +<item> It would be nice if the widget had buttons to increase and +decrease the value in small or large steps. Although it would be +possible to use embedded Button widgets for this, we would also like +the buttons to auto-repeat when held down, as the arrows on a +scrollbar do. Most of the code to implement this type of behavior can +be found in the GtkRange widget. + +<item> The Dial widget could be made into a container widget with a +single child widget positioned at the bottom between the buttons +mentioned above. The user could then add their choice of a label or +entry widget to display the current value of the dial. + +</itemize> + +<!-- ----------------------------------------------------------------- --> +<sect1> Learning More + +<p> +Only a small part of the many details involved in creating widgets +could be described above. If you want to write your own widgets, the +best source of examples is the GTK source itself. Ask yourself some +questions about the widget you want to write: is it a Container +widget? does it have its own window? is it a modification of an +existing widget? Then find a similar widget, and start making changes. +Good luck! + +<!-- ***************************************************************** --> +<sect>Scribble, A Simple Example Drawing Program +<!-- ***************************************************************** --> + +<!-- ----------------------------------------------------------------- --> +<sect1> Overview + +<p> +In this section, we will build a simple drawing program. In the +process, we will examine how to handle mouse events, how to draw in a +window, and how to do drawing better by using a backing pixmap. After +creating the simple drawing program, we will extend it by adding +support for XInput devices, such as drawing tablets. GTK provides +support routines which makes getting extended information, such as +pressure and tilt, from such devices quite easy. + +<!-- ----------------------------------------------------------------- --> +<sect1> Event Handling + +<p> +The GTK signals we have already discussed are for high-level actions, +such as a menu item being selected. However, sometimes it is useful to +learn about lower-level occurrences, such as the mouse being moved, or +a key being pressed. There are also GTK signals corresponding to these +low-level <em>events</em>. The handlers for these signals have an +extra parameter which is a pointer to a structure containing +information about the event. For instance, motion events handlers are +passed a pointer to a GdkEventMotion structure which looks (in part) +like: + +<tscreen><verb> +struct _GdkEventMotion +{ + GdkEventType type; + GdkWindow *window; + guint32 time; + gdouble x; + gdouble y; + ... + guint state; + ... +}; +</verb></tscreen> + +<tt/type/ will be set to the event type, in this case +<tt/GDK_MOTION_NOTIFY/, window is the window in which the event +occured. <tt/x/ and <tt/y/ give the coordinates of the event, +and <tt/state/ specifies the modifier state when the event +occurred (that is, it specifies which modifier keys and mouse buttons +were pressed.) It is the bitwise OR of some of the following: + +<tscreen><verb> +GDK_SHIFT_MASK +GDK_LOCK_MASK +GDK_CONTROL_MASK +GDK_MOD1_MASK +GDK_MOD2_MASK +GDK_MOD3_MASK +GDK_MOD4_MASK +GDK_MOD5_MASK +GDK_BUTTON1_MASK +GDK_BUTTON2_MASK +GDK_BUTTON3_MASK +GDK_BUTTON4_MASK +GDK_BUTTON5_MASK +</verb></tscreen> + +<p> +As for other signals, to determine what happens when an event occurs +we call <tt>gtk_signal_connect()</tt>. But we also need let GTK +know which events we want to be notified about. To do this, we call +the function: + +<tscreen><verb> +void gtk_widget_set_events (GtkWidget *widget, + gint events); +</verb></tscreen> + +The second field specifies the events we are interested in. It +is the bitwise OR of constants that specify different types +of events. For future reference the event types are: + +<tscreen><verb> +GDK_EXPOSURE_MASK +GDK_POINTER_MOTION_MASK +GDK_POINTER_MOTION_HINT_MASK +GDK_BUTTON_MOTION_MASK +GDK_BUTTON1_MOTION_MASK +GDK_BUTTON2_MOTION_MASK +GDK_BUTTON3_MOTION_MASK +GDK_BUTTON_PRESS_MASK +GDK_BUTTON_RELEASE_MASK +GDK_KEY_PRESS_MASK +GDK_KEY_RELEASE_MASK +GDK_ENTER_NOTIFY_MASK +GDK_LEAVE_NOTIFY_MASK +GDK_FOCUS_CHANGE_MASK +GDK_STRUCTURE_MASK +GDK_PROPERTY_CHANGE_MASK +GDK_PROXIMITY_IN_MASK +GDK_PROXIMITY_OUT_MASK +</verb></tscreen> + +There are a few subtle points that have to be observed when calling +<tt/gtk_widget_set_events()/. First, it must be called before the X window +for a GTK widget is created. In practical terms, this means you +should call it immediately after creating the widget. Second, the +widget must have an associated X window. For efficiency, many widget +types do not have their own window, but draw in their parent's window. +These widgets are: + +<tscreen><verb> +GtkAlignment +GtkArrow +GtkBin +GtkBox +GtkImage +GtkItem +GtkLabel +GtkPaned +GtkPixmap +GtkScrolledWindow +GtkSeparator +GtkTable +GtkViewport +GtkAspectFrame +GtkFrame +GtkVPaned +GtkHPaned +GtkVBox +GtkHBox +GtkVSeparator +GtkHSeparator +</verb></tscreen> + +To capture events for these widgets, you need to use an EventBox +widget. See the section on +<ref id="sec_The_EventBox_Widget" name="The EventBox Widget"> for +details. + +<p> +For our drawing program, we want to know when the mouse button is +pressed and when the mouse is moved, so we specify +<tt/GDK_POINTER_MOTION_MASK/ and <tt/GDK_BUTTON_PRESS_MASK/. We also +want to know when we need to redraw our window, so we specify +<tt/GDK_EXPOSURE_MASK/. Although we want to be notified via a +Configure event when our window size changes, we don't have to specify +the corresponding <tt/GDK_STRUCTURE_MASK/ flag, because it is +automatically specified for all windows. + +<p> +It turns out, however, that there is a problem with just specifying +<tt/GDK_POINTER_MOTION_MASK/. This will cause the server to add a new +motion event to the event queue every time the user moves the mouse. +Imagine that it takes us 0.1 seconds to handle a motion event, but the +X server queues a new motion event every 0.05 seconds. We will soon +get way behind the users drawing. If the user draws for 5 seconds, +it will take us another 5 seconds to catch up after they release +the mouse button! What we would like is to only get one motion +event for each event we process. The way to do this is to +specify <tt/GDK_POINTER_MOTION_HINT_MASK/. + +<p> +When we specify <tt/GDK_POINTER_MOTION_HINT_MASK/, the server sends +us a motion event the first time the pointer moves after entering +our window, or after a button press or release event. Subsequent +motion events will be suppressed until we explicitely ask for +the position of the pointer using the function: + +<tscreen><verb> +GdkWindow* gdk_window_get_pointer (GdkWindow *window, + gint *x, + gint *y, + GdkModifierType *mask); +</verb></tscreen> + +(There is another function, <tt>gtk_widget_get_pointer()</tt> which +has a simpler interface, but turns out not to be very useful, since +it only retrieves the position of the mouse, not whether the buttons +are pressed.) + +<p> +The code to set the events for our window then looks like: + +<tscreen><verb> + gtk_signal_connect (GTK_OBJECT (drawing_area), "expose_event", + (GtkSignalFunc) expose_event, NULL); + gtk_signal_connect (GTK_OBJECT(drawing_area),"configure_event", + (GtkSignalFunc) configure_event, NULL); + gtk_signal_connect (GTK_OBJECT (drawing_area), "motion_notify_event", + (GtkSignalFunc) motion_notify_event, NULL); + gtk_signal_connect (GTK_OBJECT (drawing_area), "button_press_event", + (GtkSignalFunc) button_press_event, NULL); + + gtk_widget_set_events (drawing_area, GDK_EXPOSURE_MASK + | GDK_LEAVE_NOTIFY_MASK + | GDK_BUTTON_PRESS_MASK + | GDK_POINTER_MOTION_MASK + | GDK_POINTER_MOTION_HINT_MASK); +</verb></tscreen> + +We'll save the "expose_event" and "configure_event" handlers for +later. The "motion_notify_event" and "button_press_event" handlers +pretty simple: + +<tscreen><verb> +static gint +button_press_event (GtkWidget *widget, GdkEventButton *event) +{ + if (event->button == 1 && pixmap != NULL) + draw_brush (widget, event->x, event->y); + + return TRUE; +} + +static gint +motion_notify_event (GtkWidget *widget, GdkEventMotion *event) +{ + int x, y; + GdkModifierType state; + + if (event->is_hint) + gdk_window_get_pointer (event->window, &x, &y, &state); + else + { + x = event->x; + y = event->y; + state = event->state; + } + + if (state & GDK_BUTTON1_MASK && pixmap != NULL) + draw_brush (widget, x, y); + + return TRUE; +} +</verb></tscreen> + +<!-- ----------------------------------------------------------------- --> +<sect1> The DrawingArea Widget, And Drawing + +<p> +We know turn to the process of drawing on the screen. The +widget we use for this is the DrawingArea widget. A drawing area +widget is essentially an X window and nothing more. It is a blank +canvas in which we can draw whatever we like. A drawing area +is created using the call: + +<tscreen><verb> +GtkWidget* gtk_drawing_area_new (void); +</verb></tscreen> + +A default size for the widget can be specified by calling: + +<tscreen><verb> +void gtk_drawing_area_size (GtkDrawingArea *darea, + gint width, + gint height); +</verb></tscreen> + +This default size can be overriden, as is true for all widgets, +by calling <tt>gtk_widget_set_usize()</tt>, and that, in turn, can +be overridden if the user manually resizes the the window containing +the drawing area. + +<p> +It should be noted that when we create a DrawingArea widget, we are, +<em>completely</em> responsible for drawing the contents. If our +window is obscured then uncovered, we get an exposure event and must +redraw what was previously hidden. + +<p> +Having to remember everything that was drawn on the screen so we +can properly redraw it can, to say the least, be a nuisance. In +addition, it can be visually distracting if portions of the +window are cleared, then redrawn step by step. The solution to +this problem is to use an offscreen <em>backing pixmap</em>. +Instead of drawing directly to the screen, we draw to an image +stored in server memory but not displayed, then when the image +changes or new portions of the image are displayed, we copy the +relevant portions onto the screen. + +<p> +To create an offscreen pixmap, we call the function: + +<tscreen><verb> +GdkPixmap* gdk_pixmap_new (GdkWindow *window, + gint width, + gint height, + gint depth); +</verb></tscreen> + +The <tt>window</tt> parameter specifies a GDK window that this pixmap +takes some of its properties from. <tt>width</tt> and <tt>height</tt> +specify the size of the pixmap. <tt>depth</tt> specifies the <em>color +depth</em>, that is the number of bits per pixel, for the new window. +If the depth is specified as <tt>-1</tt>, it will match the depth +of <tt>window</tt>. + +<p> +We create the pixmap in our "configure_event" handler. This event +is generated whenever the window changes size, including when it +is originally created. + +<tscreen><verb> +/* Backing pixmap for drawing area */ +static GdkPixmap *pixmap = NULL; + +/* Create a new backing pixmap of the appropriate size */ +static gint +configure_event (GtkWidget *widget, GdkEventConfigure *event) +{ + if (pixmap) + { + gdk_pixmap_destroy(pixmap); + } + pixmap = gdk_pixmap_new(widget->window, + widget->allocation.width, + widget->allocation.height, + -1); + gdk_draw_rectangle (pixmap, + widget->style->white_gc, + TRUE, + 0, 0, + widget->allocation.width, + widget->allocation.height); + + return TRUE; +} +</verb></tscreen> + +The call to <tt>gdk_draw_rectangle()</tt> clears the pixmap +initially to white. We'll say more about that in a moment. + +<p> +Our exposure event handler then simply copies the relevant portion +of the pixmap onto the screen (we determine the area we need +to redraw by using the event->area field of the exposure event): + +<tscreen><verb> +/* Refill the screen from the backing pixmap */ +static gint +expose_event (GtkWidget *widget, GdkEventExpose *event) +{ + gdk_draw_pixmap(widget->window, + widget->style->fg_gc[GTK_WIDGET_STATE (widget)], + pixmap, + event->area.x, event->area.y, + event->area.x, event->area.y, + event->area.width, event->area.height); + + return FALSE; +} +</verb></tscreen> + +We've now seen how to keep the screen up to date with our pixmap, but +how do we actually draw interesting stuff on our pixmap? There are a +large number of calls in GTK's GDK library for drawing on +<em>drawables</em>. A drawable is simply something that can be drawn +upon. It can be a window, a pixmap, or a bitmap (a black and white +image). We've already seen two such calls above, +<tt>gdk_draw_rectangle()</tt> and <tt>gdk_draw_pixmap()</tt>. The +complete list is: + +<tscreen><verb> +gdk_draw_line () +gdk_draw_rectangle () +gdk_draw_arc () +gdk_draw_polygon () +gdk_draw_string () +gdk_draw_text () +gdk_draw_pixmap () +gdk_draw_bitmap () +gdk_draw_image () +gdk_draw_points () +gdk_draw_segments () +</verb></tscreen> + +See the reference documentation or the header file +<tt><gdk/gdk.h></tt> for further details on these functions. +These functions all share the same first two arguments. The first +argument is the drawable to draw upon, the second argument is a +<em>graphics context</em> (GC). + +<p> +A graphics context encapsulates information about things such as +foreground and background color and line width. GDK has a full set of +functions for creating and modifying graphics contexts, but to keep +things simple we'll just use predefined graphics contexts. Each widget +has an associated style. (Which can be modified in a gtkrc file, see +the section GTK's rc file.) This, among other things, stores a number +of graphics contexts. Some examples of accessing these graphics +contexts are: + +<tscreen><verb> +widget->style->white_gc +widget->style->black_gc +widget->style->fg_gc[GTK_STATE_NORMAL] +widget->style->bg_gc[GTK_WIDGET_STATE(widget)] +</verb></tscreen> + +The fields <tt>fg_gc</tt>, <tt>bg_gc</tt>, <tt>dark_gc</tt>, and +<tt>light_gc</tt> are indexed by a parameter of type +<tt>GtkStateType</tt> which can take on the values: + +<tscreen><verb> +GTK_STATE_NORMAL, +GTK_STATE_ACTIVE, +GTK_STATE_PRELIGHT, +GTK_STATE_SELECTED, +GTK_STATE_INSENSITIVE +</verb></tscreen> + +For instance, the for <tt/GTK_STATE_SELECTED/ the default foreground +color is white and the default background color, dark blue. + +<p> +Our function <tt>draw_brush()</tt>, which does the actual drawing +on the screen, is then: + +<tscreen><verb> +/* Draw a rectangle on the screen */ +static void +draw_brush (GtkWidget *widget, gdouble x, gdouble y) +{ + GdkRectangle update_rect; + + update_rect.x = x - 5; + update_rect.y = y - 5; + update_rect.width = 10; + update_rect.height = 10; + gdk_draw_rectangle (pixmap, + widget->style->black_gc, + TRUE, + update_rect.x, update_rect.y, + update_rect.width, update_rect.height); + gtk_widget_draw (widget, &update_rect); +} +</verb></tscreen> + +After we draw the rectangle representing the brush onto the pixmap, +we call the function: + +<tscreen><verb> +void gtk_widget_draw (GtkWidget *widget, + GdkRectangle *area); +</verb></tscreen> + +which notifies X that the area given by the <tt>area</tt> parameter +needs to be updated. X will eventually generate an expose event +(possibly combining the areas passed in several calls to +<tt>gtk_widget_draw()</tt>) which will cause our expose event handler +to copy the relevant portions to the screen. + +<p> +We have now covered the entire drawing program except for a few +mundane details like creating the main window. The complete +source code is available from the location from which you got +this tutorial, or from: + +<htmlurl url="http://www.msc.cornell.edu/~otaylor/gtk-gimp/tutorial" +name="http://www.msc.cornell.edu/~otaylor/gtk-gimp/tutorial"> + + +<!-- ----------------------------------------------------------------- --> +<sect1> Adding XInput support + +<p> + +It is now possible to buy quite inexpensive input devices such +as drawing tablets, which allow drawing with a much greater +ease of artistic expression than does a mouse. The simplest way +to use such devices is simply as a replacement for the mouse, +but that misses out many of the advantages of these devices, +such as: + +<itemize> +<item> Pressure sensitivity +<item> Tilt reporting +<item> Sub-pixel positioning +<item> Multiple inputs (for example, a stylus with a point and eraser) +</itemize> + +For information about the XInput extension, see the <htmlurl +url="http://www.msc.cornell.edu/~otaylor/xinput/XInput-HOWTO.html" +name="XInput-HOWTO">. + +<p> +If we examine the full definition of, for example, the GdkEventMotion +structure, we see that it has fields to support extended device +information. + +<tscreen><verb> +struct _GdkEventMotion +{ + GdkEventType type; + GdkWindow *window; + guint32 time; + gdouble x; + gdouble y; + gdouble pressure; + gdouble xtilt; + gdouble ytilt; + guint state; + gint16 is_hint; + GdkInputSource source; + guint32 deviceid; +}; +</verb></tscreen> + +<tt/pressure/ gives the pressure as a floating point number between +0 and 1. <tt/xtilt/ and <tt/ytilt/ can take on values between +-1 and 1, corresponding to the degree of tilt in each direction. +<tt/source/ and <tt/deviceid/ specify the device for which the +event occurred in two different ways. <tt/source/ gives some simple +information about the type of device. It can take the enumeration +values. + +<tscreen><verb> +GDK_SOURCE_MOUSE +GDK_SOURCE_PEN +GDK_SOURCE_ERASER +GDK_SOURCE_CURSOR +</verb></tscreen> + +<tt/deviceid/ specifies a unique numeric ID for the device. This can +be used to find out further information about the device using the +<tt/gdk_input_list_devices()/ call (see below). The special value +<tt/GDK_CORE_POINTER/ is used for the core pointer device. (Usually +the mouse.) + +<sect2> Enabling extended device information + +<p> +To let GTK know about our interest in the extended device information, +we merely have to add a single line to our program: + +<tscreen><verb> +gtk_widget_set_extension_events (drawing_area, GDK_EXTENSION_EVENTS_CURSOR); +</verb></tscreen> + +By giving the value <tt/GDK_EXTENSION_EVENTS_CURSOR/ we say that +we are interested in extension events, but only if we don't have +to draw our own cursor. See the section <ref +id="sec_Further_Sophistications" name="Further Sophistications"> below +for more information about drawing the cursor. We could also +give the values <tt/GDK_EXTENSION_EVENTS_ALL/ if we were willing +to draw our own cursor, or <tt/GDK_EXTENSION_EVENTS_NONE/ to revert +back to the default condition. + +<p> +This is not completely the end of the story however. By default, +no extension devices are enabled. We need a mechanism to allow +users to enable and configure their extension devices. GTK provides +the InputDialog widget to automate this process. The following +procedure manages an InputDialog widget. It creates the dialog if +it isn't present, and raises it to the top otherwise. + +<tscreen><verb> +void +input_dialog_destroy (GtkWidget *w, gpointer data) +{ + *((GtkWidget **)data) = NULL; +} + +void +create_input_dialog () +{ + static GtkWidget *inputd = NULL; + + if (!inputd) + { + inputd = gtk_input_dialog_new(); + + gtk_signal_connect (GTK_OBJECT(inputd), "destroy", + (GtkSignalFunc)input_dialog_destroy, &inputd); + gtk_signal_connect_object (GTK_OBJECT(GTK_INPUT_DIALOG(inputd)->close_button), + "clicked", + (GtkSignalFunc)gtk_widget_hide, + GTK_OBJECT(inputd)); + gtk_widget_hide ( GTK_INPUT_DIALOG(inputd)->save_button); + + gtk_widget_show (inputd); + } + else + { + if (!GTK_WIDGET_MAPPED(inputd)) + gtk_widget_show(inputd); + else + gdk_window_raise(inputd->window); + } +} +</verb></tscreen> + +(You might want to take note of the way we handle this dialog. By +connecting to the "destroy" signal, we make sure that we don't keep a +pointer to dialog around after it is destroyed - that could lead to a +segfault.) + +<p> +The InputDialog has two buttons "Close" and "Save", which by default +have no actions assigned to them. In the above function we make +"Close" hide the dialog, hide the "Save" button, since we don't +implement saving of XInput options in this program. + +<sect2> Using extended device information + +<p> +Once we've enabled the device, we can just use the extended +device information in the extra fields of the event structures. +In fact, it is always safe to use this information since these +fields will have reasonable default values even when extended +events are not enabled. + +<p> +Once change we do have to make is to call +<tt/gdk_input_window_get_pointer()/ instead of +<tt/gdk_window_get_pointer/. This is necessary because +<tt/gdk_window_get_pointer/ doesn't return the extended device +information. + +<tscreen><verb> +void gdk_input_window_get_pointer (GdkWindow *window, + guint32 deviceid, + gdouble *x, + gdouble *y, + gdouble *pressure, + gdouble *xtilt, + gdouble *ytilt, + GdkModifierType *mask); +</verb></tscreen> + +When calling this function, we need to specify the device ID as +well as the window. Usually, we'll get the device ID from the +<tt/deviceid/ field of an event structure. Again, this function +will return reasonable values when extension events are not +enabled. (In this case, <tt/event->deviceid/ will have the value +<tt/GDK_CORE_POINTER/). + +So the basic structure of our button-press and motion event handlers, +doesn't change much - we just need to add code to deal with the +extended information. + +<tscreen><verb> +static gint +button_press_event (GtkWidget *widget, GdkEventButton *event) +{ + print_button_press (event->deviceid); + + if (event->button == 1 && pixmap != NULL) + draw_brush (widget, event->source, event->x, event->y, event->pressure); + + return TRUE; +} + +static gint +motion_notify_event (GtkWidget *widget, GdkEventMotion *event) +{ + gdouble x, y; + gdouble pressure; + GdkModifierType state; + + if (event->is_hint) + gdk_input_window_get_pointer (event->window, event->deviceid, + &x, &y, &pressure, NULL, NULL, &state); + else + { + x = event->x; + y = event->y; + pressure = event->pressure; + state = event->state; + } + + if (state & GDK_BUTTON1_MASK && pixmap != NULL) + draw_brush (widget, event->source, x, y, pressure); + + return TRUE; +} +</verb></tscreen> + +We also need to do something with the new information. Our new +<tt/draw_brush()/ function draws with a different color for +each <tt/event->source/ and changes the brush size depending +on the pressure. + +<tscreen><verb> +/* Draw a rectangle on the screen, size depending on pressure, + and color on the type of device */ +static void +draw_brush (GtkWidget *widget, GdkInputSource source, + gdouble x, gdouble y, gdouble pressure) +{ + GdkGC *gc; + GdkRectangle update_rect; + + switch (source) + { + case GDK_SOURCE_MOUSE: + gc = widget->style->dark_gc[GTK_WIDGET_STATE (widget)]; + break; + case GDK_SOURCE_PEN: + gc = widget->style->black_gc; + break; + case GDK_SOURCE_ERASER: + gc = widget->style->white_gc; + break; + default: + gc = widget->style->light_gc[GTK_WIDGET_STATE (widget)]; + } + + update_rect.x = x - 10 * pressure; + update_rect.y = y - 10 * pressure; + update_rect.width = 20 * pressure; + update_rect.height = 20 * pressure; + gdk_draw_rectangle (pixmap, gc, TRUE, + update_rect.x, update_rect.y, + update_rect.width, update_rect.height); + gtk_widget_draw (widget, &update_rect); +} +</verb></tscreen> + +<sect2> Finding out more about a device + +<p> +As an example of how to find out more about a device, our program +will print the name of the device that generates each button +press. To find out the name of a device, we call the function: + +<tscreen><verb> +GList *gdk_input_list_devices (void); +</verb></tscreen> + +which returns a GList (a linked list type from the glib library) +of GdkDeviceInfo structures. The GdkDeviceInfo strucure is defined +as: + +<tscreen><verb> +struct _GdkDeviceInfo +{ + guint32 deviceid; + gchar *name; + GdkInputSource source; + GdkInputMode mode; + gint has_cursor; + gint num_axes; + GdkAxisUse *axes; + gint num_keys; + GdkDeviceKey *keys; +}; +</verb></tscreen> + +Most of these fields are configuration information that you +can ignore unless you are implemented XInput configuration +saving. The we are interested in here is <tt/name/ which is +simply the name that X assigns to the device. The other field +that isn't configuration information is <tt/has_cursor/. If +<tt/has_cursor/ is false, then we we need to draw our own +cursor. But since we've specified <tt/GDK_EXTENSION_EVENTS_CURSOR/, +we don't have to worry about this. + +<p> +Our <tt/print_button_press()/ function simply iterates through +the returned list until it finds a match, then prints out +the name of the device. + +<tscreen><verb> +static void +print_button_press (guint32 deviceid) +{ + GList *tmp_list; + + /* gdk_input_list_devices returns an internal list, so we shouldn't + free it afterwards */ + tmp_list = gdk_input_list_devices(); + + while (tmp_list) + { + GdkDeviceInfo *info = (GdkDeviceInfo *)tmp_list->data; + + if (info->deviceid == deviceid) + { + printf("Button press on device '%s'\n", info->name); + return; + } + + tmp_list = tmp_list->next; + } +} +</verb></tscreen> + +That completes the changes to ``XInputize'' our program. As with +the first version, the complete source is available at the location +from which you got this tutorial, or from: + +<htmlurl url="http://www.msc.cornell.edu/~otaylor/gtk-gimp/tutorial" +name="http://www.msc.cornell.edu/~otaylor/gtk-gimp/tutorial"> + + +<sect2> Further sophistications <label id="sec_Further_Sophistications"> + +<p> +Although our program now supports XInput quite well, it lacks some +features we would want in a full-featured application. First, the user +probably doesn't want to have to configure their device each time they +run the program, so we should allow them to save the device +configuration. This is done by iterating through the return of +<tt/gdk_input_list_devices()/ and writing out the configuration to a +file. + +<p> +To restore the state next time the program is run, GDK provides +functions to change device configuration: + +<tscreen><verb> +gdk_input_set_extension_events() +gdk_input_set_source() +gdk_input_set_mode() +gdk_input_set_axes() +gdk_input_set_key() +</verb></tscreen> + +(The list returned from <tt/gdk_input_list_devices()/ should not be +modified directly.) An example of doing this can be found in the +drawing program gsumi. (Available from <htmlurl +url="http://www.msc.cornell.edu/~otaylor/gsumi/" +name="http://www.msc.cornell.edu/~otaylor/gsumi/">) Eventually, it +would be nice to have a standard way of doing this for all +applications. This probably belongs at a slightly higher level than +GTK, perhaps in the GNOME library. + +<p> +Another major ommission that we have mentioned above is the lack of +cursor drawing. Platforms other than XFree86 currently do not allow +simultaneously using a device as both the core pointer and directly by +an application. See the <url +url="http://www.msc.cornell.edu/~otaylor/xinput/XInput-HOWTO.html" +name="XInput-HOWTO"> for more information about this. This means that +applications that want to support the widest audience need to draw +their own cursor. + +<p> +An application that draws it's own cursor needs to do two things: +determine if the current device needs a cursor drawn or not, and +determine if the current device is in proximity. (If the current +device is a drawing tablet, it's a nice touch to make the cursor +disappear when the stylus is lifted from the tablet. When the +device is touching the stylus, that is called "in proximity.") +The first is done by searching the device list, as we did +to find out the device name. The second is achieved by selecting +"proximity_out" events. An example of drawing one's own cursor is +found in the 'testinput' program found in the GTK distribution. + +<!-- ***************************************************************** --> +<sect>Tips For Writing GTK Applications +<!-- ***************************************************************** --> + +<p> +This section is simply a gathering of wisdom, general style guidelines and hints to +creating good GTK applications. It is totally useless right now cause it's +only a topic sentence :) + +Use GNU autoconf and automake! They are your friends :) I am planning to +make a quick intro on them here. + +<!-- ***************************************************************** --> +<sect>Contributing +<!-- ***************************************************************** --> + +<p> +This document, like so much other great software out there, was created for +free by volunteers. If you are at all knowledgeable about any aspect of GTK +that does not already have documentation, please consider contributing to +this document. +<p> +If you do decide to contribute, please mail your text to Tony Gale, +<tt><htmlurl url="mailto:gale@gimp.org" +name="gale@gimp.org"></tt>. Also, be aware that the entirety of this +document is free, and any addition by yourself must also be free. That is, +people may use any portion of your examples in their programs, and copies +of this document may be distributed at will etc. +<p> +Thank you. + +<!-- ***************************************************************** --> +<sect>Credits +<!-- ***************************************************************** --> +<p> +I would like to thank the following for their contributions to this text. + +<itemize> +<item>Bawer Dagdeviren, <tt><htmlurl url="mailto:chamele0n@geocities.com" +name="chamele0n@geocities.com"></tt> for the menus tutorial. + +<item>Raph Levien, <tt><htmlurl url="mailto:raph@acm.org" +name="raph@acm.org"></tt> +for hello world ala GTK, widget packing, and general all around wisdom. +He's also generously donated a home for this tutorial. + +<item>Peter Mattis, <tt><htmlurl url="mailto:petm@xcf.berkeley.edu" +name="petm@xcf.berkeley.edu"></tt> for the simplest GTK program.. +and the ability to make it :) + +<item>Werner Koch <tt><htmlurl url="mailto:werner.koch@guug.de" +name="werner.koch@guug.de"></tt> for converting the original plain text to +SGML, and the widget class hierarchy. + +<item>Mark Crichton <tt><htmlurl url="mailto:crichton@expert.cc.purdue.edu" +name="crichton@expert.cc.purdue.edu"></tt> for the menu factory code, and +the table packing tutorial. + +<item>Owen Taylor <tt><htmlurl url="mailto:owt1@cornell.edu" +name="owt1@cornell.edu"></tt> for the EventBox widget section (and +the patch to the distro). He's also responsible for the selections code and +tutorial, as well as the sections on writing your own GTK widgets, and the +example application. Thanks a lot Owen for all you help! + +<item>Mark VanderBoom <tt><htmlurl url="mailto:mvboom42@calvin.edu" +name="mvboom42@calvin.edu"></tt> for his wonderful work on the Notebook, +Progress Bar, Dialogs, and File selection widgets. Thanks a lot Mark! +You've been a great help. + +<item>Tim Janik <tt><htmlurl url="mailto:timj@psynet.net" +name="timj@psynet.net"></tt> for his great job on the Lists Widget. +Thanks Tim :) + +<item>Rajat Datta <tt><htmlurl url="mailto:rajat@ix.netcom.com" +name="rajat@ix.netcom.com"</tt> for the excellent job on the Pixmap tutorial. + +<item>Michael K. Johnson <tt><htmlurl url="mailto:johnsonm@redhat.com" +name="johnsonm@redhat.com"></tt> for info and code for popup menus. + +</itemize> +<p> +And to all of you who commented and helped refine this document. +<p> +Thanks. + +<!-- ***************************************************************** --> +<sect> Tutorial Copyright and Permissions Notice +<!-- ***************************************************************** --> + +<p> +The GTK Tutorial is Copyright (C) 1997 Ian Main. + +Copyright (C) 1998 Tony Gale. +<p> +Permission is granted to make and distribute verbatim copies of this +manual provided the copyright notice and this permission notice are +preserved on all copies. +<P>Permission is granted to copy and distribute modified versions of +this document under the conditions for verbatim copying, provided that +this copyright notice is included exactly as in the original, +and that the entire resulting derived work is distributed under +the terms of a permission notice identical to this one. +<P>Permission is granted to copy and distribute translations of this +document into another language, under the above conditions for modified +versions. +<P>If you are intending to incorporate this document into a published +work, please contact the maintainer, and we will make an effort +to ensure that you have the most up to date information available. +<P>There is no guarentee that this document lives up to its intended +purpose. This is simply provided as a free resource. As such, +the authors and maintainers of the information provided within can +not make any guarentee that the information is even accurate. +</article> diff --git a/docs/tutorial/gtk_tut_it.sgml b/docs/tutorial/gtk_tut_it.sgml new file mode 100644 index 000000000..9ed24eb86 --- /dev/null +++ b/docs/tutorial/gtk_tut_it.sgml @@ -0,0 +1,8340 @@ + +<!doctype linuxdoc system> +<article> +<title>GTK Tutorial +<author>Ian Main, <tt><htmlurl url="mailto:slow@intergate.bc.ca" + name="slow@intergate.bc.ca"></tt> + +<date>December 1, 1997 - Traduzione Aggiornata al 19 Gennaio 1998 + +<abstract>Tradotto da Michel Morelli, <tt><htmlurl url="mailto:ziobudda@chiara.dei.unipd.it" name="ziobudda@chiara.dei.unipd.it"></tt>, Daniele Canazza, <tt><htmlurl url="mailto:dcanazz@tin.it" name="dcanazz@tin.it"></tt> e Antonio Schifano, <tt><htmlurl url="mailto:schifano@cli.di.unipi.it" name="schifano@cli.di.unipi.it"></tt> +</abstract> + +<sect>Introduzione +<p> +GTK (GIMP Toolkit) era orginariamente sviluppato come toolkit per il programma +GIMP (General Image Manipulation Program). GTK è costruito sulla base del +kit di disegno di GIMP, il GDK (GIMP Drawing Kit) il quale è costruito a sua +volta attorno alle funzioni della Xlib. E' chiamato ``toolkit di GIMP'' perché +era inizialmente scritto per sviluppare GIMP, ma ora viene utilizzato nello +sviluppo di molti progetti software liberi. Gli autori sono +<itemize> +<item> Peter Mattis <tt><htmlurl url="mailto:petm@xcf.berkeley.edu" + name="petm@xcf.berkeley.edu"></tt> +<item> Spencer Kimball <tt><htmlurl url="mailto:spencer@xcf.berkeley.edu" + name="spencer@xcf.berkeley.edu"></tt> +<item> Josh MacDonald <tt><htmlurl url="mailto:jmacd@xcf.berkeley.edu" + name="jmacd@xcf.berkeley.edu"></tt> +</itemize> + +<p> +GTK è essenzialmente una API (application programmers interface) +orientata agli oggetti. +Anche se scritto completamente in C, è implementato usando l'idea delle +classi e delle funzioni di callback (puntatori a funzioni). + +<p> +C'è anche una terza componente chiamata glib che contiene una serie di +implementazioni differenti di alcune chiamate di funzioni standard e anche +alcune funzioni aggiuntive, per esempio per la manipolazione delle liste +collegate, eccetera. Le funzioni sostitutive sono usate per migliorare la +portabilità di GTK. Alcune delle funzioni implementate qui non sono +disponibili o non sono standard, altre sono uniche come g_strerror(). +Altre contengono miglioramenti alle stesse della libc come g_malloc che ha +delle utility di debugging migliorate. + +<p> +Questo tutorial è un tentativo di documentare il meglio possibile la libreria gtk +e non pretende di essere completo. Questo tutorial suppone una buona conoscenza del +linugaggio C e di come creare programmi in C. Saranno facilitati i lettori che hanno una +precedente esperienza nella programmazione in X. Se il GTK è il primo insieme di widget +che studiate, siete pregati di dirmi come avete trovato questo tutorial e che tipo di problemi +avete avuto. +Notate che c'è anche una versione per il C++ della libreria GTK (chiamata GTK--), quindi +se preferite utilizzare questo linguaggio al posto del C potreste cercare questa versione +e non la GTK normale. +Ci sono poi un ``wrapper'' Objective C e un collegamento a Guile, ma non ne seguo +l'evoluzione. + +<p> +Mi farebbe molto piacere conoscere qualsiasi problema che abbiate avuto nell'imparare il GTK +da questo documento e apprezzerei anche critiche sul come migliorarlo. + +<sect>Iniziamo +<p> +La prima cosa da fare è certamente quella di scaricare il GTK e installarlo. Potete prendere +l'ultima versione dal sito ftp.gimp.org nella directory /pub/gimp. Un'altra possibile sorgente +di informazioni è il sito http://www.gimp.org/gtk. GTK usa il comando GNU autoconf per +autoconfigurarsi. +Una volta estratti i file dall'archivio tar, eseguite configure --help per vedere una lista delle +opzioni del comando configure. + +<p> +Per iniziare la nostra introduzione a GTK, cominceremo con il più semplice programma +possibile . Questo programma crea una finestra con dimensioni (in pixel) di 200x200 e +l'unica possibilità di uscita è di ucciderlo ucciso usando la shell o il Window Manager. + +<tscreen><verb> +#include <gtk/gtk.h> + +int main (int argc, char *argv[]) +{ + GtkWidget *window; + + gtk_init (&argc, &argv); + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_widget_show (window); + + gtk_main (); + + return 0; +} +</verb></tscreen> + +Tutti i programmi certamente includeranno <gtk/gtk.h> che dichiara le variabili, le funzioni, +le strutture, etc. che saranno usate nella tua applicazione GTK. + +<p> +La linea seguente: + +<tscreen><verb> +gtk_init (&argc, &argv); +</verb></tscreen> + +invoca la funzione gtk_init(gint *argc, gchar ***argv) che sarà usata in tutte le +applicazioni GTK. Questa funzione sistema alcune cose al posto nostro, come la visuale +predefinita e la mappa dei colori, e procede poi chiamando gdk_init(gint *argc, gchar ***argv). +Questa funzione inizializza la libreria per l'uso, setta il gestore predefinito dei segnali +e guarda negli argomenti, passati via linea di comando alla tua applicazione, alla ricerca +di uno di questi argomenti: +<itemize> +<item> <tt/--display/ +<item> <tt/--debug-level/ +<item> <tt/--no-xshm/ +<item> <tt/--sync/ +<item> <tt/--show-events/ +<item> <tt/--no-show-events/ +</itemize> +<p> +Rimuove questi argomenti dalla lista degli argomenti passati, lasciando quelli non +riconosciuti a disposizione della tua applicazione che potrà tenerne conto o ignorarli. +In questo modo si crea un set di argomenti standard accettato da tutte le applicazione GTK. + +<p> +Le seguenti 2 linee di codice creano e mostrano la finestra. + +<tscreen><verb> + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_widget_show (window); +</verb></tscreen> + +L'argomento GTK_WINDOW_TOPLEVEL specifica che noi vogliamo che la nostra finestra si +sottometta alle decorazioni del windows manager e alla posizione che quest'ultimo indicherà. +Invece di creare una finestra avente dimensioni 0x0, la dimensione di una finestra senza +figli (altri widget, come i bottoni, etc) è predefinita a 200x200 così che si possa manipolarla. +La funzione gtk_widget_show() fa sì che GTK sappia che abbiamo finito di settare gli +attributi di questo widget e che quindi quest'ultimo può essere visualizzato. + +<p> +L'ultima linea ci fa entrare nel ciclo principale del GTK. + +<tscreen><verb> +gtk_main (); +</verb></tscreen> + +gtk_main() è un'altra chiamata che tu vedrete in tutte le applicazioni GTK. Quando il controllo +raggiunge questo punto, l'applicazione si metterà a dormire aspettando che si verifichino eventi +di X (come la pressione di un bottone o di un tasto), timeout o notifiche di Input/Output dei file +Nel nostro esempio, comunque, tutti gli eventi sono ignorati. + +<sect1>Hello World in GTK +<p> +Ok, ora un programma con un widget (un bottone). E' il classico ``Hello World'' alla GTK. + +<tscreen><verb> + +#include <gtk/gtk.h> + + +/* E' una funzione di ritorno (callback). Gli argomenti passati sono ignorati in questo +* esempio. +* Piu' informazioni sulle callback in seguito. */ + +void hello (GtkWidget *widget, gpointer data) +{ + g_print ("Hello World\n"); +} + +gint delete_event(GtkWidget *widget, gpointer data) + { + g_print ("delete event occured\n"); + /* Se si dà TRUE al manipolatore del segnale ``delete_event'', GTK emettera' il segnale + ``destroy''. Fornire FALSE significa non volere che la finestra sia distrutta. + Cambia FALSE con TRUE e la finestra principale sara' distrutta con un "delete_event" + */ + +/* Un'altra callback */ +void destroy (GtkWidget *widget, gpointer data) +{ + gtk_main_quit (); +} + +int main (int argc, char *argv[]) +{ + /* GtkWidget e' il tipo di dato per i Widget */ + GtkWidget *window; + GtkWidget *button; + + /* Questa e' una chiamata presente in tutte le applicazioni GTK. Gli argomenti della + linea di comando vengono scorsi e restituiti alla applicazione */ + gtk_init (&argc, &argv); + + /* Crea una nuova finestra */ + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + + /* Quando alla finestra viene passato il segnale ``delete_event'' (questo + * segnale viene passato Windows Manager di solito con l'opzione 'close' + * o con la barra del titolo (title bar)) noi chiediamo che la funzione + * delete_event() (definita sopra) venga invocata. + * Il dato passato come argomento alla funzione di ritorno é NULL + * ed é ignorato dalla funzione stessa. */ + gtk_signal_connect (GTK_OBJECT (window), "delete_event", + GTK_SIGNAL_FUNC (destroy), NULL); + + /* Qui connettiamo l'evento ``destroy'' al gestore del segnale. + * Questo evento accade quando noi chiamimo la funzione gtk_widget_destroy() + * sulla finestra o se ritorniamo TRUE dalla callback ``delete_event''. */ + gtk_signal_connect (GTK_OBJECT (window), "destroy", + GTK_SIGNAL_FUNC (destroy), NULL); + + /* Setta il bordo interno della finestra */ + gtk_container_border_width (GTK_CONTAINER (window), 10); + + /* Crea un nuovo bottone avente etichetta (label) uguale a ``Hello World'' */ + button = gtk_button_new_with_label ("Hello World"); + + /* Quando il bottone riceve il segnale ``clicked'', invochera' la funzione + * hello() passando NULL come argomento della funzione. La funzione + * hello() é definita sopra. */ + gtk_signal_connect (GTK_OBJECT (button), "clicked", + GTK_SIGNAL_FUNC (hello), NULL); + + /* Questo farà sì che la finestra venga distrutta dalla chiamata + * gtk_widget_destroy(window) quando il bottone verrà premuto. Ancora, + * questo segnale (``destroy'') puo' arrivare da qui o dal windows + * manager */ + gtk_signal_connect_object (GTK_OBJECT (button), "clicked", + GTK_SIGNAL_FUNC (gtk_widget_destroy), + GTK_OBJECT (window)); + + /* Questo inserisce il bottone nella finestra + * (un contenitore GTK) */ + gtk_container_add (GTK_CONTAINER (window), button); + + /* Il passo finale é il mostrare questo nuovo widget appena creato */ + gtk_widget_show (button); + + /* e la finestra */ + gtk_widget_show (window); + + /* Tutte le applicazioni GTK devono avere la funzione gtk_main(). + * Il controllo finisce qui e attende un evento (come la pressione + * di un tasto o l'evento di un mouse). + gtk_main (); + + return 0; +} +</verb></tscreen> + +<sect1>Compilare hello World +<p> +Per compilare si utilizza : + +<tscreen><verb> +gcc -Wall -g helloworld.c -o hello_world -L/usr/X11R6/lib \ + -lglib -lgdk -lgtk -lX11 -lXext -lm +</verb></tscreen> +<p> +Le librerie sopra (glib, gtk,...) devono essere tutte nel percorso predefinito +delle librerie. Se cosi' non fosse aggiungi ``-L<directory>'' e il gcc +guarderà in questa directory per cercare le librerie di cui necessita. +Per esempio sul mio sistema debian-linux io ho dovuto aggiungere +<tt>-L/usr/X11R6/lib</> per riuscire a far trovare le librerie di X11. + +<p> +L'odine della dichiarazione delle librerie é significativo. Il linker +sa quali funzioni di una libreria ha bisogno prima di processarla. + +<p> +le librerie che noi linkiamo sono: +<itemize> +<item> la libreria glib (-lglib), contiene varie funzioni, ma solo +g_print() é usato in questo esempio. GTK si appoggia a questa +libreria cosi' devi sempre, comunque, linkarla. Vedi comunque la <ref +id="sec_glib" name="glib"> sezione sulla glib per altri dettagli. +<item>La libreria GDK (-lgdk), la copertura della X11. +<item>La libreria GTK (-lgtk), la libreria dei widget, basata sulla GDK. +<item>La libreria xlib(-lX11) la quale è usata dalla GDK. +<item>La libreria Xext(-lXext). Questa contiene il codice per le pixmap a +memoria condivisa e altre estensioni di X. +<item>La libreria matematica (-lm). Questa é usata dalla GTK per vari scopi. +</itemize> + +<sect1>Teoria dei segnali e delle funzioni di ritorno (callback) +<p> +Prima di guardare in dettaglio ``Hello World'', discuteremo gli eventi e le +funzioni di ritorno. GTK è un toolkit guidato dagli eventi, il che significa +che se ne starà a dorimire in gtk_main finché non succederà un evento ed il +controllo passerà alla funzione appropriata. + +<p> +Questo passaggio di controllo è fatto usando l'idea dei segnali. Quando succede un +evento, come la pressione di un bottone del mouse, verrà emesso il segnale appropriato +dal widget che é stato premuto. +Questo è il modo in cui GTK fa molto del suo utile lavoro. Per fare sì che un +bottone esegua una azione, noi prepareremo un gestore del segnale che catturi +questi segnali e chiami la funzione corretta. Questo è fatto usando una +funzione del tipo: + +<tscreen><verb> +gint gtk_signal_connect (GtkObject *object, + gchar *name, + GtkSignalFunc func, + gpointer func_data); +</verb></tscreen> + +<p> +Dove, il primo argomento è il widget che emetterà il segnale, il secondo è il nome +del segnale che si vuole catturare,il terzo è la funzione che verrà invocata +quando il segnale sarà catturato e il quarto è il dato che potr essere passato a +questa funzione. + +<p> +La funzione specificata come terzo argomento è chiamata ``funzione di ritorno (callback)'', +e dovrebbe essere della forma: + +<tscreen><verb> +void callback_func(GtkWidget *widget, gpointer *callback_data); +</verb></tscreen> +<p> +Dove il primo argomento sarà un puntatore al widget che emette il segnale e il +secondo un puntatore al dato passato come ultimo argomento della funzione +gtk_signal_connect() come descritto sopra. + +<p> +Un'altra chiamata usata nell'esempio Hello World è: + +<tscreen><verb> +gint gtk_signal_connect_object (GtkObject *object, + gchar *name, + GtkSignalFunc func, + GtkObject *slot_object); +</verb></tscreen> +<p> +gtk_signal_connect_object() è uguale a gtk_signal_connect() eccetto che la +funzione di callback usa solo un argomento, un puntatore ad un'oggetto GTK. +Cosi' quando usa questa funzione per connettere i segnali, la callback +potrebbe essere della forma : + +<tscreen><verb> + void callback_func (GtkObject *object); +</verb></tscreen> +<p> +Dove object è di solito un widget. Noi, generalmente, non assegnamo una callback per +gtk_signal_connect_object. Queste sono invocate ,usualmente, per chiamare +una funzione GTK che accetta un widget singolo o un oggetto come argomento, +come nel caso dell'esempio Hello World. + +Lo scopo di avere due funzioni per connettere i segnali è semplicemente quello di +permettere alla funzione di callback di avere un numero di argomenti diverso. +Molte funzioni della libreria GTK accettano solo un singolo puntatore ad un widget +GTK come argomento, così per queste si può usare la funzione gtk_signal_connect_object(), +mentre per le vostre funzioni potreste aver bisogno di passare dati supplementari alle +funzioni di ritorno. + +<sect1>Attraverso Hello World passo per passo +<p> +Ora che conosciamo la teoria che vi è dietro, iniziamo ad essere più chiari +camminando attraverso il programma di Hello World. + +<p> +Questa è la funzione di callback che sarà invocata quando il bottone è clickato. +Noi, in questo esempio, ignoriamo sia il widget che i dati passati, ma non è +difficile farci invece qualcosa. Il prossimo esempio userà l'argomento passato +per dire quale bottone è stato premuto. + +<tscreen><verb> +void hello (GtkWidget *widget, gpointer *data) +{ + g_print ("Hello World\n"); +} +</verb></tscreen> + +<p> +Questa callback è un po' speciale. L'evento ``delete'' avviene quanto il Window Manager +manda questo evento all'applicazione. Qui abbiamo una scelta da fare: cosa fare di questo evento. +Possiamo ignorarlo, creare qualche tipo di risposta, o semplicemente terminare +l'applicazione. + +Il valore che si restituisce in questa callback fa sì che la GTK sappia cosa fare. +Restituire FALSE significa che noi non vogliamo che il segnale ``destroy'' sia emesso, +quindi far sì che la nostra applicazione continui a procedere. Ritornare TRUE vuole dire +far emettere il segnale ``destroy'' il quale chiamerà il gestore del segnale ``destroy'' +(o meglio : la nostra funzione di callback). + +<tscreen><verb> + gint delete_event(GtkWidget *widget, gpointer data) + { + g_print ("delete event occured\n"); + + return (FALSE); + } +</verb></tscreen> + +<p> +Questa è un'altra funzione di callback la quale fa uscire dal programma chiamando +gtk_main_quit(). Non c'è molto da dire al riguardo, è abbastanza auto-esplicativa. + +<tscreen><verb> +void destroy (GtkWidget *widget, gpointer *data) +{ + gtk_main_quit (); +} +</verb></tscreen> +<p> +Ritengo che conosciate la funzione main()... si, come tutte le altre applicazioni +anche le applicazioni GTK hanno questa funzione. + +<tscreen><verb> +int main (int argc, char *argv[]) +{ +</verb></tscreen> + +<p> +Questa parte dichiara un puntatore ad una struttura di tipo GtkWidget. Queste sono +usate sotto per creare una finestra ed un bottone. + +<tscreen><verb> + GtkWidget *window; + GtkWidget *button; +</verb></tscreen> +<p> +Qui vi è ancora la nostra gtk_init. Come prima questa inizializza il toolkit e +analizza gli argomenti trovati nella linea di comando_ Tutti gli argomenti riconosciuti +nella linea di comando sono rimossi dalla lista degli argomenti e vengono così modificati +argc e argv per far sì che sembri che questi non siano mai esisitie permettere alla +tua applicazione di analizzare gli argomenti rimasti. + +<tscreen><verb> + gtk_init (&argc, &argv); +</verb></tscreen> +<p> +Crea una nuova finestra. Questo viene spiegato abbastanza approfonditamente più avanti. +Viene allocata la memoria per la struttura GtkWidget *window così che si punti ad una struttura +valida. In questo modo si predispone la nuova finestra, ma non la si visualizza fino a sotto dove, quasi +alla fine del nostro programma, invochiamo gtk_widget_show(window). +<tscreen><verb> + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); +</verb></tscreen> +<p> +Questo è un esempio di come connettere un gestore dei segnali con un oggetto, in questo +caso la finestra. Qui viene catturato il segnale ``destroy''. Questo è emesso quando usiamo +il Window Manager per uccidere la finestra (e noi restituiamo TRUE dal gestore di ``delete_event'') +o quando emettiamo la chiamata gtk_widget_destroy() passando l'oggetto finestra +come oggetto da distruggere. Sistemando le cose così, trattiamo entrambi i casi con una singola +chiamata. Qui è giusto invocare la funzione destroy() definita sopra con NULL come argomento, +la quale termina l'applicazione GTK per noi. +Questo ci permetterà di utilizzare il Window Manager per uccidere il programma. +<!-- fino a qui --> +<p> +GTK_OBJECT e GTK_SIGNAL_FUNC sono macro che interpretano il casting e il controllo di tipo per noi, +così da rendere piu' leggibile il codice. + +<tscreen><verb> + gtk_signal_connect (GTK_OBJECT (window), "destroy", + GTK_SIGNAL_FUNC (destroy), NULL); +</verb></tscreen> +<p> +La prossima funzione è usata per settare un attributo di un oggetto contenitore. Questo +sistema la finestra così da avere un'area vuota all'interno della finestrra larga 10 pixel dove +non potrà andare nessun widget. Ci sono altre funzioni simili che vedremo nella +sezione <ref id="sec_setting_widget_attributes" name="Settare gli attributi del Widget."> + +<p> +E ancora, GTK_CONTAINER è una macro per interpretare il casting di tipo. + +<tscreen><verb> + gtk_container_border_width (GTK_CONTAINER (window), 10); +</verb></tscreen> +<p> +Questa chiamata crea un nuovo bottone. Alloca spazio in memoria per un nuovo GtkWidget, +inizializzandolo e facendo sì che il puntatore a bottone punti ad esso. +Quando sarà visualizzato, avrà etichetta ``Hello World''. + +<tscreen><verb> + button = gtk_button_new_with_label ("Hello World"); +</verb></tscreen> +<p> +Qui prendiamo il bottone e gli facciamo fare qualcosa di utile. +Gli colleghiamo un un gestore di segnale in modo che quando emetterà il +segnale ``clicked'', verrà invocata la nostra funzione hello(). Il dato passato +alla funzione è ignorato, cosicché alla funzione di callback hello() passiamo +semplicemente NULL. Evidentemente il segnale ``clicked'' viene emesso quando +premiamo il bottone con il mouse. + +<tscreen><verb> + gtk_signal_connect (GTK_OBJECT (button), "clicked", + GTK_SIGNAL_FUNC (hello), NULL); +</verb></tscreen> +<p> +Usiamo questo bottone anche per uscire dal programma. Questo illustrera' +come il segnale ``destroy'' può arrivare sia dal Window Manager che dal nostro programma. +Quando il bottone è ``clicked'', come sopra, chiamera' la funzione di callback +hello() e poi questa nell'ordine in cui sono definite. Si possono avere +tante funzioni di callback, quante sono necessarie, e saranno eseguite nell'ordine in cui +sono connesse. Visto che la funzione gtk_widget_destroy() accetta come argomento solo un +GtkWidget *widget, usiamo la funzione gtk_signal_connect_object() +al posto della semplice gtk_signal_connect(). + +<tscreen><verb> + gtk_signal_connect_object (GTK_OBJECT (button), "clicked", + GTK_SIGNAL_FUNC (gtk_widget_destroy), + GTK_OBJECT (window)); +</verb></tscreen> +<p> +Questa é una chiamata di ``impacchettamento'' che sarà spiegata più avanti. +Ma è molto facile da capire. Semplicemente dice alla libreria GTK che il +bottone è da mettere nella finestra dove sarà visualizzato. + +<tscreen><verb> + gtk_container_add (GTK_CONTAINER (window), button); +</verb></tscreen> +<p> +A questo punto abbiamo predisposto tutto quello che ci eravamo prefissati. +Con tutti i gestori di segnale a posto e il bottone messo nella finestra in cui +dovrebbe essere, possiamo dire a GTK di mostrare gli oggetti sullo schermo. +L'oggetto finestra viene mostrato per ultimo così che la finestra completa di tutti +i suoi oggetti sarà mostrata in una volta sola, invece di vedere +prima la finestra spoglia e poi la comparsa del bottone all'interno di essa. +Per quanto, con questi semplici esempi, questo l'avrai già notato. +<tscreen><verb> + gtk_widget_show (button); + + gtk_widget_show (window); +</verb></tscreen> +<p> +E naturalmente chiamiamo gtk_main(), la quale aspetta l'arrivo degli eventi +dal server X e chiamerà l'oggetto interessato per fargli emettere il segnale +adeguato. +<tscreen><verb> + gtk_main (); +</verb></tscreen> +E il return finale. Il controllo ritorna qui dopo che viene invocata gtk_quit(). + +<tscreen><verb> + return 0; +</verb></tscreen> +<p> +Ora, quando premiamo il bottone del mouse su un bottone GTK, questo oggetto +emette il segnale ``clicked''. Per poter utilizzare queste informazioni, il nostro +programma predispone un gestore di segnale per catturare quel segnale, il quale +avvia la funzione da noi scelta. Nel nostro esempio, quando il bottone creato viene +clickato , la funzione hello() è invocata con un argomento NULL, dopoodiché +viene invocato il successivo gestore di questo segnale. Questo chiama la funziona +gtk_widget_destroy(), passandole l'oggetto-finestra (window) come argomento, che +distruggerà la finestra. Questo fa sì che la finestra emetta il segnale +``destroy'' che viene catturato e che fa invocare la funzione di ritorno +destroy(), che semplicemente esce dal programma GTK. + +<p> +Un'altro modo in cui possono andare le cose è l'uso del window manager per uccidere +la finestra. Questo causera' l'emissione del segnale ``delete_event'' che +automaticamente chiamerà il gestore del segnale ``delete_event''. Se qui noi +restituiamo il valore FALSE, la finestra non verrà toccata e tutto procederà come +se nulla fosse successo. Dare invece il valore TRUE causerà l'emissione da parte +di GTK del segnale ``destroy'' il quale, a sua volta, invocherà la callback ``destroy'', +uscendo dall'applicazione. + +<p> +Nota che questi segnali non sono gli stessi del sistema Unix e che non sono +implementati usando quei segnali, anche se la terminologia è praticamente identica. + +<sect>Proseguiamo +<p> +<sect1>Tipi di Dato +<p> +Ci sono alcune cose che avrete probabilmente notato nei precedenti esempi che +hanno bisogno di una spiegazione. I gint, gchar ecc. che vedete sono tipi di dato +riferiti rispettivamente a int e char. Questo viene fatto per rimediare alla brutta +dipendenza dalle dimensioni di semplici tipi di dato quando si fanno dei calcoli. +Un buon esempio è ``gint32'' il quale sarà un tipo di dato riferito ad un intero a +32 bit per tutte le piattaforme x86 e ad un 64 bit per gli alpha. +I tipi di dato sono ben spiegati più avanti ed intuitivi. Sono definiti in +glib/glib.h (il quale viene incluso da gtk.h). + +<p> +Noterete anche la possibilità di utilizzare un GtkWidget quando la funzione richiede +un GtkObject. GTK è una libreria orienta agli oggetti ed un widget è un oggetto. + +<sect1>Altri Dettagli sui Segnali +<p> +Diamo un'altra occhiata alla dichiarazione della funzione gtk_signal_connect. + +<tscreen><verb> +gint gtk_signal_connect (GtkObject *object, gchar *name, + GtkSignalFunc func, gpointer func_data); +</verb></tscreen> +Notate il valore di ritorno definito come gint? questo è un identificatore per +la tua funzione di callback. Come detto sopra, si possono avere più funzioni di +ritorno per ogni segnale e per ogni ogetto a seconda delle necessità. ed ognuna sarà +eseguita in sequenza, nell'ordine in cui sono state collegate. Questo identificatore +ti permette di rimuovere una funzione dalla lista delle funzioni di ritorno tramite +la seguente chiamata +<tscreen><verb> +void gtk_signal_disconnect (GtkObject *object, + gint id); +</verb></tscreen> +Così passando il widget da cui vuoi rimuovere il gestore di segnale, e +l'identificativo restituito da una delle funzioni signal_connect, puoi rimuovere +il gestore di segnale che desideri da quella del widget. + +<p> +Un'altra funzione per rimuovere tutti i segnali di un widget in una volta sola è: + +<tscreen><verb> +gtk_signal_handlers_destroy (GtkObject *object); +</verb></tscreen> +<p> +Questa chiamata è abbastanza auto esplicativa. Semplicemente rimuove tutti i segnali +collegati al widget che passi alla funzione come argomento. + +<sect1>Miglioriamo Hello World + +<p> +Diamo un'occhiata ad una migliorata versione di Hello World con altri esempi sulle +callback. Questo anche ci introdurrà al nostro prossimo argomento, +l'impacchettamento dei widget. + +<tscreen><verb> +#include <gtk/gtk.h> + +/* La nostra funzione di callback migliorata. I dati passati a questa + * vengono stampati su stdout. */ +void callback (GtkWidget *widget, gpointer *data) +{ + g_print ("Hello again - %s was pressed\n", (char *) data); +} + +/* Un'altra callback */ +void delete_event (GtkWidget *widget, gpointer *data) +{ + gtk_main_quit (); +} + +int main (int argc, char *argv[]) +{ + /* GtkWidget e' il tipo di dato per i widget */ + GtkWidget *window; + GtkWidget *button; + GtkWidget *box1; + + /* Questa funzione e' invocata in tutte le applicazioni GTK, gli + argomenti sono analizzati e restituiti all'applicazione. */ + gtk_init (&argc, &argv); + + /* Crea una nuova finestra */ + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + + /* Questa e' una nuova chiamata. Assegna "Hello Buttons" come titolo + della nostra finestra */ + gtk_window_set_title (GTK_WINDOW (window), "Hello Buttons!"); + + /* Qui settiamo il gestore per il segnale "delete_event" che + immediatamente esce dalla applicazione. + gtk_signal_connect (GTK_OBJECT (window), "delete_event", + GTK_SIGNAL_FUNC (delete_event), NULL); + + + /* predispone il bordo della finestra */ + gtk_container_border_width (GTK_CONTAINER (window), 10); + + /* creiamo una scatola dove mettere tutti i widget. Questa è descritta + dettagliatamente nella sezione "packing". La scatola non è realmente + visibile, è solamente usata per sistemare i widget. */ + box1 = gtk_hbox_new(FALSE, 0); + + /* Inseriamo la scatola nella finestra */ + gtk_container_add (GTK_CONTAINER (window), box1); + + /* Creiamo un nuovo bottone con etichetta "Button 1" */ + button = gtk_button_new_with_label ("Button 1"); + + /* Quando il bottone e' premuto, noi invocheremo la funzione di callback, + con un puntatore alla stringa "button 1" come proprio argomento) */ + gtk_signal_connect (GTK_OBJECT (button), "clicked", + GTK_SIGNAL_FUNC (callback), (gpointer) "button 1"); + + /* invece di aggiungerlo alla finestra, lo inseriamo nella scatola invisibile, + la quale e' stata inserita nella finstra. */ + gtk_box_pack_start(GTK_BOX(box1), button, TRUE, TRUE, 0); + + /* Ricordati sempre questo passo. Dice a GTK che la preparazione di questo + bottone e' finita e che quindi puo' essere mostrato. */ + gtk_widget_show(button); + + /* Facciamo la stessa cosa per il secondo bottone. */ + button = gtk_button_new_with_label ("Button 2"); + + /* Chiamiamo la stessa funzione ma passandogli un argomento differente, + gli passiamo un puntatore alla stringa "button 2" */ + gtk_signal_connect (GTK_OBJECT (button), "clicked", + GTK_SIGNAL_FUNC (callback), (gpointer) "button 2"); + + gtk_box_pack_start(GTK_BOX(box1), button, TRUE, TRUE, 0); + + /* L'ordine nel quale i bottoni sono visualizzati non e' realmente importante, + ma io ti raccomando di mostrare per ultima la finestra cosi' che tutto + sia visualizzato in una volta sola */ + gtk_widget_show(button); + + gtk_widget_show(box1); + + gtk_widget_show (window); + + /* e ora ci mettiamo in gtk_main e aspettiamo che il diverimento inizi. + gtk_main (); + + return 0; +} +</verb></tscreen> +<p> +Compilate questo programma usando gli stessi argomenti di link del nostro primo +esempio. Noterete che questa volta non c'è un modo semplice per uscire dal programma, +si deve usare il nostro window manager o la linea di comando per uccidere +l'applicazione. +Un buon esercizio per il lettore è quello di inserire un tezo bottone ``quit'' che +faccia uscire dal programma. Potete anche divertirvi con le opzioni di +gtk_box_pack_start() mentre leggete il prossimo capitolo. Provate a ridimensionare +la finestra ed a osservare cosa succede. + +<p> +Solo una piccola nota, c'è un'altra definizione di gtk_window_new() - +GTK_WINDOW_DIALOG. Questa interagisce con il window manager in un modo un po' +diverso, e dovrebbe essere usata per finestre temporanee. + +<sect>Come ``Impacchettare'' i Widget +<p> +Nel momento in cui si crea un'applicazione, normalmente si avrà la necessità di mettere più +di un unico bottone all'interno di una finestra. Il nostro primo esempio ``Hello World'' +usava un solo oggetto, cosicché abbiamo potuto usare semplicemente una chiamata +a gtk_container_add per impacchettare il widget nella finestra. Quando invece si vuole +inserire più di un unico widget in una finestra, come si fa a controllare dove vengono +posizionati i propri oggetti? E' qui che entra in gioco il meccanismo dell'``impacchettamento''. +<sect1>Teoria delle Scatole per Impacchettamento +<p> +La maggior parte dell'impacchettamento viene effettuata creando delle scatole +come nell'esempio più sopra. Le scatole sono dei contenitori invisibili di +widget che possiamo usare per imballarci i nostri oggetti e che esistono in +due varietà: in particolare si possono avere scatole orizzontali (hbox) e +verticali (vbox). +Quando si impacchentano degli oggetti in una scatola orizzontale, gli oggetti vengono inseriti +orizzontalmente da sinistra a destra oppure da destra a sinistra a seconda della +chiamata di funzione che si usa. In una scatola verticale, gli oggetti vengono inseriti +dall'alto in basso o viceversa. Si può usare qualsiasi combinazione di scatole +all'interno o a fianco di altre scatole, fino ad ottenere l'effetto desiderato. +<p> +Per creare una nuova scatola orizzontale, si usa una chiamata a gtk_hbox_new(), mentre +per le scatole verticali si usa gtk_vbox_new(). Per inserire i widget +all'interno di questi contenitori si usano le funzioni gtk_box_pack_start() e +gtk_box_pack_end(). La funzione gtk_box_pack_start() comincerà dall'alto verso il +basso in una vbox e da sinistra a destra in una hbox. gtk_box_pack_end() fa l'opposto, +impacchettando dal basso verso l'alto in una vbox e da destra a sinistra in una hbox. +Queste funzioni ci permettono di giustificare a destra o a sinistra i nostri +widget, e possono essere mescolate in qualsiasi modo per ottenere l'effetto desiderato. +Useremo gtk_box_pack_start() nella maggior parte dei nostri esempi. Un oggetto può +essere costituito da un altro contenitore o da un oggetto grafico. Infatti, molti +oggetti grafici sono a loro volta dei contenitori, compreso il bottone, anche se +tipicamente all'interno del bottone mettiamo solo una etichetta. +<p> + +Usando queste chiamate, GTK riesce a capire dove si vogliono piazzare i propri +widget, in modo di essere poi in grado di effettuare il ridimensionamento +automatico e altre cose interessanti. Esiste poi un insieme di opzioni che riguardano +il modo in cui i propri oggetti grafici dovrebbero essere impacchettati. Come +si può immaginare, questo metodo dà una buona flessibilità nella creazione e +nella disposizione dei propri widget. +<sect1>Dettagli sulle Scatole +<p> +A causa di questa flessibilità, le scatole per impacchettamento del GTK +possono, di primo acchito, creare un po' di disorientamento. Sono infatti disponibili +molte opzioni, e non è immediato il modo in cui si combinano l'una con l'altra. +Alla fine però, si possono ottenere essenzialmente cinque diversi stili. + +<p> +<? +<IMG ALIGN="center" SRC="packbox1.gif" +VSPACE="15" HSPACE="10" ALT="Box Packing Example Image" WIDTH="528" +HEIGHT="235"> +> + + +Ogni linea contiene una scatola orizzontale (hbox) con diversi bottoni. +La chiamata a gtk_box_pack è una scorciatoia per la chiamata di impacchettamento +di ognuno dei bottoni nella hbox. Ognuno dei bottoni viene impacchettato nella +hbox nello stesso modo (cioè, con gli stessi argomenti per la funzione gtk_box_pack_start ()). +<p> +Questa è la dichiarazione della funzione gtk_box_pack_start. + +<tscreen><verb> +void gtk_box_pack_start (GtkBox *box, + GtkWidget *child, + gint expand, + gint fill, + gint padding); +</verb></tscreen> +Il primo argomento è la scatola nella quale si stanno inscatolando i +widget, il secondo è il widget stesso. Gli oggetti per ora saranno +bottoni, quindi quello che faremo sarà impacchettare bottoni in scatole. +<p> +L'argomento ``expand'' in gtk_box_pack_start() o gtk_box_pack_end() controlla +se gli oggetti devono essere sistemati nella scatola in modo da riempire tutto +lo spazio in diponibile presente nella scatola, in modo che la scatola si espanda fino +ad occupare tutta l'area assegnatale (valore TRUE). +La scatola può anche essere rimpiciolita in modo da contenere esattamente i +widget (valore FALSE). Assegnare a expand il valore FALSE permette di giustificare +a destra o sinistra i propri oggetti. In caso contrario, tutti gli ogetti si espandono +fino ad adattarsi alla scatola, e il medesimo effetto si può ottenere usando solo una +delle funzioni gtk_box_pack_start o pack_end. +<p> +L'argomento ``fill'' delle funzioni gtk_box_pack stabilisce se lo spazio disponibile +nella scatola deve essere allocato agli oggetti (TRUE) o se deve essere mantenuto +come riempimento attorno a questi oggetti (FALSE). Questo argomento ha effetto +solo se a expand è assegnato il valore TRUE. +<p> +Quando si crea una nuova scatola, la funzione ha questo aspetto: + +<tscreen><verb> +GtkWidget * gtk_hbox_new (gint homogeneous, + gint spacing); +</verb></tscreen> + +L'argomento homogeneous di gtk_hbox_new (la stesso per gtk_vbox_new) +determina se ogni oggetto nella scatola deve avere la stessa dimensione (cioè +la stessa ampiezza in una hbox o la stessa altezza in una vbox). Se è settato, +l'argomento expand delle routine gtk_box_pack è sempre attivato. +<p> +Qual è la differenza fra la spaziatura (che è stabilita quando la scatola +viene creata) e il riempimento (che viene stabilito quando gli elementi vengono +impacchettati)? La spaziatura viene inserita fra gli oggetti, mentre il +riempimento viene aggiuno a ciascuno dei lati dell'oggetti. La seguente figura +dovrebbe chiarire meglio questo punto: + +<? +<IMG ALIGN="center" SRC="packbox2.gif" +VSPACE="15" HSPACE="10" ALT="Box Packing Example Image" WIDTH="509" +HEIGHT="213"> +> + + +Di seguito è riportato il codice usato per creare le immagini precedenti. +L'ho commentato in modo piuttosto pesante, in modo che non dovreste avere +problemi nel seguirlo. Compilatelo voi stessi e preovate a giocarci un po'. + +<sect1>Programma Dimostrativo di Impacchettamento +<p> + +<tscreen><verb> +#include "gtk/gtk.h" + +void +delete_event (GtkWidget *widget, gpointer *data) +{ + gtk_main_quit (); +} + +/* Costruisco una nuova hbox riempita con bottoni-etichette. Gli + * argomenti per le varabili che ci interessano sono passati + * in questa funzione. Non mostriamo la scatola, ma mostriamo + * tutto quello che c'è dentro. */ +GtkWidget *make_box (gint homogeneous, gint spacing, + gint expand, gint fill, gint padding) +{ + GtkWidget *box; + GtkWidget *button; + char padstr[80]; + + /* costruisco una nuova hbox con i valori appropriati di + * homogeneous e spacing */ + box = gtk_hbox_new (homogeneous, spacing); + + /* costruisco una serie di bottoni con i valori appropriati */ + button = gtk_button_new_with_label ("gtk_box_pack"); + gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding); + gtk_widget_show (button); + + button = gtk_button_new_with_label ("(box,"); + gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding); + gtk_widget_show (button); + + button = gtk_button_new_with_label ("button,"); + gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding); + gtk_widget_show (button); + + /* costruisco un bottone con l'etichetta che dipende dal valore di + * expand. */ + if (expand == TRUE) + button = gtk_button_new_with_label ("TRUE,"); + else + button = gtk_button_new_with_label ("FALSE,"); + + gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding); + gtk_widget_show (button); + + /* Questo è la stessa cosa della creazione del bottone per "expand" + * più sopra, ma usa la forma breve. */ + button = gtk_button_new_with_label (fill ? "TRUE," : "FALSE,"); + gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding); + gtk_widget_show (button); + + sprintf (padstr, "%d);", padding); + + button = gtk_button_new_with_label (padstr); + gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding); + gtk_widget_show (button); + + return box; +} + +int +main (int argc, char *argv[]) +{ + GtkWidget *window; + GtkWidget *button; + GtkWidget *box1; + GtkWidget *box2; + GtkWidget *separator; + GtkWidget *label; + GtkWidget *quitbox; + int which; + + /* La nostra inizializzazione, non dimenticatela! :) */ + gtk_init (&argc, &argv); + + if (argc != 2) { + fprintf (stderr, "uso: packbox num, dove num è 1, 2, o 3.\n"); + /* questo fa solo un po' di pulizia in GTK, ed esce con un valore 1. */ + gtk_exit (1); + } + + which = atoi (argv[1]); + + /* Creiamo la nostra finestra */ + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + + /* Ci si dovrebbe sempre ricordare di connettere il segnale di destroy + * alla finestra principale. Ciò è molto importante per avere un funzionamento + * corretto dal punto di vista intuitivo */ + gtk_signal_connect (GTK_OBJECT (window), "delete_event", + GTK_SIGNAL_FUNC (delete_event), NULL); + gtk_container_border_width (GTK_CONTAINER (window), 10); + + /* Creiamo una scatola verticale (vbox) in cui impacchettare quelle + * orizzontali. Questo ci permette di impilare le scatole orizzontali + * piene di bottoni una sull'altra in questa vbox. */ + + box1 = gtk_vbox_new (FALSE, 0); + + /* Decide quale esempio si deve mostrare. Corrispondono alle figure precedenti */ + switch (which) { + case 1: + /* creare una nuova etichetta. */ + label = gtk_label_new ("gtk_hbox_new (FALSE, 0);"); + + /* allineare l'etichetta al lato sinistro. Discuteremo questa e altre + * funzioni nella sezione dedicata agli attributi degli oggetti grafici. */ + gtk_misc_set_alignment (GTK_MISC (label), 0, 0); + + /* Impacchettare l'etichetta nella scatola verticale (vbox box1). + * Ricordare che gli oggetti che vengono aggiunti in una vbox vengono + * impacchettati uno sopra all'altro in ordine. */ + gtk_box_pack_start (GTK_BOX (box1), label, FALSE, FALSE, 0); + + /* mostrare l'etichetta */ + gtk_widget_show (label); + + /* chiamare la nostra funzione make_box - homogeneous = FALSE, + * spacing = 0, expand = FALSE, fill = FALSE, padding = 0 */ + box2 = make_box (FALSE, 0, FALSE, FALSE, 0); + gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0); + gtk_widget_show (box2); + + /* chiamare la nostra funzione make_box - homogeneous = FALSE, spacing = 0, + * expand = FALSE, fill = FALSE, padding = 0 */ + box2 = make_box (FALSE, 0, TRUE, FALSE, 0); + gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0); + gtk_widget_show (box2); + + /* Gli argomenti sono: homogeneous, spacing, expand, fill, padding */ + box2 = make_box (FALSE, 0, TRUE, TRUE, 0); + gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0); + gtk_widget_show (box2); + + /* Questo crea un separatore. Li conosceremo meglio in seguito, + * comunque sono piuttosto semplici. */ + separator = gtk_hseparator_new (); + + /* Impacchetta il separatore nella vbox. Ricordare che stiamo impacchettando + * ognuno di questi oggetti in una vbox, cosicché essi verranno + * impacchettati verticalmente. */ + gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 5); + gtk_widget_show (separator); + + /* crea un'altra nuova etichetta e mostrala. */ + label = gtk_label_new ("gtk_hbox_new (TRUE, 0);"); + gtk_misc_set_alignment (GTK_MISC (label), 0, 0); + gtk_box_pack_start (GTK_BOX (box1), label, FALSE, FALSE, 0); + gtk_widget_show (label); + + /* Gli argomenti sono: homogeneous, spacing, expand, fill, padding */ + box2 = make_box (TRUE, 0, TRUE, FALSE, 0); + gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0); + gtk_widget_show (box2); + + /* Gli argomenti sono: homogeneous, spacing, expand, fill, padding */ + box2 = make_box (TRUE, 0, TRUE, TRUE, 0); + gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0); + gtk_widget_show (box2); + + /* ancora un nuovo separatore. */ + separator = gtk_hseparator_new (); + /* Gli ultimi 3 argumenti per gtk_box_pack_start sono: expand, fill, padding. */ + gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 5); + gtk_widget_show (separator); + + break; + + case 2: + + /* creare una nuova etichetta, ricordare che box1 è la vbox creata + * vicino all'inizio di main() */ + label = gtk_label_new ("gtk_hbox_new (FALSE, 10);"); + gtk_misc_set_alignment (GTK_MISC (label), 0, 0); + gtk_box_pack_start (GTK_BOX (box1), label, FALSE, FALSE, 0); + gtk_widget_show (label); + + /* Gli argomenti sono: homogeneous, spacing, expand, fill, padding */ + box2 = make_box (FALSE, 10, TRUE, FALSE, 0); + gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0); + gtk_widget_show (box2); + + /* Gli argomenti sono: homogeneous, spacing, expand, fill, padding */ + box2 = make_box (FALSE, 10, TRUE, TRUE, 0); + gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0); + gtk_widget_show (box2); + + separator = gtk_hseparator_new (); + /* Gli ultimi tre arcomenti di gtk_box_pack_start sono: expand, fill, padding. */ + gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 5); + gtk_widget_show (separator); + + label = gtk_label_new ("gtk_hbox_new (FALSE, 0);"); + gtk_misc_set_alignment (GTK_MISC (label), 0, 0); + gtk_box_pack_start (GTK_BOX (box1), label, FALSE, FALSE, 0); + gtk_widget_show (label); + + /* Gli argomenti sono: homogeneous, spacing, expand, fill, padding */ + box2 = make_box (FALSE, 0, TRUE, FALSE, 10); + gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0); + gtk_widget_show (box2); + + /* Gli argomenti sono: homogeneous, spacing, expand, fill, padding */ + box2 = make_box (FALSE, 0, TRUE, TRUE, 10); + gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0); + gtk_widget_show (box2); + + separator = gtk_hseparator_new (); + /* Gli ultimi tre argomenti di gtk_box_pack_start sono: expand, fill, padding. */ + gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 5); + gtk_widget_show (separator); + break; + + case 3: + + /* Questo dimostra la possibilità di usare use gtk_box_pack_end() per + * giustificare gli oggetti a destra. Per prima cosa creiamo una + + * nuova scatola come prima. */ + box2 = make_box (FALSE, 0, FALSE, FALSE, 0); + /* creiamo l'etichetta che sarà aggiunta alla fine. */ + label = gtk_label_new ("end"); + /* impacchettiamola usando gtk_box_pack_end(), così che viene inserita + * sul lato destro della hbox creata nella chiamata a the make_box(). */ + gtk_box_pack_end (GTK_BOX (box2), label, FALSE, FALSE, 0); + /* mostriamo l'etichetta. */ + gtk_widget_show (label); + + /* impacchettiamo box2 in box1 (the vbox, ricordate? :) */ + gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0); + gtk_widget_show (box2); + + /* un separatore per il fondo */ + separator = gtk_hseparator_new (); + /* Questo assegna esplicitamente al separatore l'ampiezza di 400 pixel + * e l'altezza di 5 pixel. Ciò fa sì che la hbox che abbiamo creato sia + * anche essa larga 400 pixel, e che l'etichetta finale sia separata dalle + * altre etichette nella hbox. In caso contrario, tutti gli oggetti nella + * hbox sarebbero impacchettati il più vicino possibile. */ + gtk_widget_set_usize (separator, 400, 5); + /* impacchetta il separatore nella vbox (box1) creata vicino all'inizio + * di main() */ + gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 5); + gtk_widget_show (separator); + } + + /* Creare un'altra nuova hbox.. ricordate che ne possiamo usare quante ne vogliamo! */ + quitbox = gtk_hbox_new (FALSE, 0); + + /* Il nostro bottone di uscita. */ + button = gtk_button_new_with_label ("Quit"); + + + /* Configuriamo il segnale per distruggere la finestra. Ricordate che + * ciò manderà alla finestra il segnale "destroy", che verrà catturato + * dal nostro gestore di segnali che abbiamo definito in precedenza. */ + gtk_signal_connect_object (GTK_OBJECT (button), "clicked", + GTK_SIGNAL_FUNC (gtk_widget_destroy), + GTK_OBJECT (window)); + /* impacchetta il bottone in quitbox. + * Gli ultimi tre argomenti di gtk_box_pack_start sono: expand, fill, padding. */ + gtk_box_pack_start (GTK_BOX (quitbox), button, TRUE, FALSE, 0); + /* impacchetta quitbox nella vbox (box1) */ + gtk_box_pack_start (GTK_BOX (box1), quitbox, FALSE, FALSE, 0); + + /* impacchetta la vbox (box1), che ora contiene tutti i nostri oggetti, + * nella finestra principale. */ + gtk_container_add (GTK_CONTAINER (window), box1); + + /* e mostra tutto quel che rimane */ + gtk_widget_show (button); + gtk_widget_show (quitbox); + + gtk_widget_show (box1); + /* Mostriamo la finestra alla fine in modo che tutto spunti fuori assieme. */ + gtk_widget_show (window); + + /* E, naturalmente, la nostra funzione main. */ + gtk_main (); + + /* Il controllo ritorna a questo punto quando viene chiamata gtk_main_quit(), + * ma non quando si usa gtk_exit. */ + + return 0; +} +</verb></tscreen> + +<p> +<sect1>Impacchettamento con uso di Tabelle +<p> +Diamo ora un'occhiata ad un altro modo di impacchettare - le Tabelle. +In certe situazioni, possono risultare estremamente utili. + +Usando le tabelle, creiamo una griglia in cui possiamo piazzare gli oggetti. +Gli oggetti possono occupare tanti spazi quanti ne specifichiamo. + +Naturalmente, la prima cosa da vedere è la funzione gtk_table_new: + +<tscreen><verb> +GtkWidget* gtk_table_new (gint rows, + gint columns, + gint homogeneous); +</verb></tscreen> +<p> +Il primo argomento rappresenta il numero di righe da mettere nella tabella, +mentre il secondo è ovviamente il numero di colonne. + +L'argomento homogeneous ha a che fare con il modo in cui le caselle della tabella +sono dimensionate. Se homogeneous ha il valore TRUE, le caselle sono ridimensionate +fino alla dimensione del più grande oggetto contenuto nella tabelle. Se è FALSE, la +dimensione delle caselleè decisa dal più alto oggetto in una certa riga e dal più +largo oggetto in una stessa colonna. + +Le righe e le colonne sono disposte a partire da 0 fino a n, dove n è il numero +che era stato specificato nella chiamata a gtk_table_new. Così, se specificate +rows = 2 e columns = 2, lo schema avrà questo aspetto: + +<tscreen><verb> + 0 1 2 +0+----------+----------+ + | | | +1+----------+----------+ + | | | +2+----------+----------+ +</verb></tscreen> +<p> +Notate che il sistema di coordinate ha origine nel vertice in alto a sinistra. Per +mettere un oggetto in una tabella, usate la seguente funzione: + +<tscreen><verb> +void gtk_table_attach (GtkTable *table, + GtkWidget *child, + gint left_attach, + gint right_attach, + gint top_attach, + gint bottom_attach, + gint xoptions, + gint yoptions, + gint xpadding, + gint ypadding); +</verb></tscreen> +<p> +In cui il primo argomento (``table'') è la tabella che avete creato e il secondo +(``child'') è l'oggetto che volete piazzare nella tabella. + +Gli argomenti ``attach'' (right, left, top, bottom) specificano dove mettere l'oggetto +e quante caselle adoperare. Se volete mettere un bottone nella casella in basso a destra +nella nostra tabella 2x2, e volete che esso riempia SOLO quella casella, dovete porre +left_attach = 1, right_attach = 2, top_attach = 1, bottom_attach = 2. + +Se invece volete che un oggetto si prenda tutta la riga più in alto nella nostra tabella +2x2, dovreste usare left_attach = 0, right_attach =2, top_attach = 0, +bottom_attach = 1. + +Gli argomenti ``xoptions'' e ``yoptions'' sono usati per specificare le opzioni di impacchettamento; +di essi si può fare l'OR in modo di ottenere opzioni multiple. + +Le opzioni sono: +<itemize> +<item>GTK_FILL - Se la parte di tabella in cui si vuole inserire il widget è più +grande dell'oggetto, e se si specifica GTK_FILL, l'oggetto viene espanso fino ad +occupare tutto lo spazio disponibile. + +<item>GTK_SHRINK - Se si alloca all'oggetto nella tabella meno spazio del necessario +(di solito succede quando l'utente ridimensiona la finestra), allora normalmente +l'oggetto verrebbe spinto fuori dal fondo della finestra fino a sparire. +Se invece si specifica GTK_SHRINK is specified, gli oggetti si rimpiccioliscono +assieme alla tabella. + +<item>GTK_EXPAND - Questo fa sì che la tabella si espanda fino ad occupare tutto lo +spazio che rimane nella finestra. +</itemize> + +Il riempimento funziona come nelle scatole, con la creazione di un'area vuota +attorno all'oggetto la cui dimensione viene specificata in pixel. + +La funzione gtk_table_attach() ha UN MUCCHIO di opzioni. Quindi, ecco una scorciatoia: + +<tscreen><verb> +void gtk_table_attach_defaults (GtkTable *table, + GtkWidget *widget, + gint left_attach, + gint right_attach, + gint top_attach, + gint bottom_attach); +</verb></tscreen> + +Le xoptions e yoptions vengono posti per difetto a GTK_FILL | GTK_EXPAND, e sia xpadding +che ypadding vengono posti a 0. Il resto degli argomenti sono identici a quelli della funzione +precedente. + +Ci sono poi le funzioni gtk_table_set_row_spacing() and gtk_table_set_col_spacing(). +Queste mettono dello spazio fra le righe (o colonne)in corrispondenza di una specifica +riga (o colonna). + +<tscreen><verb> +void gtk_table_set_row_spacing (GtkTable *table, + gint row, + gint spacing); +</verb></tscreen> +e +<tscreen><verb> +void gtk_table_set_col_spacing (GtkTable *table, + gint column, + gint spacing); +</verb></tscreen> + +Notate che per le colonne lo spazio viene posto alla destra della colonna, mentre +per le righe lo spazio viene posto al di sotto della riga. + +Si può poi inserire una spaziatura identica fra tutte le righe e/o colonne usando: + +<tscreen><verb> +void gtk_table_set_row_spacings (GtkTable *table, + gint spacing); +</verb></tscreen> +<p> +e +<tscreen><verb> +void gtk_table_set_col_spacings (GtkTable *table, + gint spacing); +</verb></tscreen> +<p> +Notate che con queste chiamate, all'ultima riga e all'ultima colonna +non viene assegnata alcuna spaziatura. + +<sect1>Esempio di Impacchettamento con Tabelle +<p> +Per il momento, si prega di fare riferimento all'esempio di tabella in +testgtk.c distribuito con i sorgenti di gtk. + + +<sect>Panoramica sui Widget +<p> +<p> +La procedura generale di creazione di un widget in GTK prevede i seguenti passi: +<enum> +<item> gtk_*_new - una delle varie funzioni che servono per greare un nuovo widget. +In questa sezione le vedremo tutte in dettaglio. + +<item> Connettere tutti i segnali che si vogliono usare alle funzione gestione appropriate. + +<item> Assegnare gli attributi all'oggetto. + +<item> Impacchettare l'oggetto in un contenitore usando la chiamate appropriata, +per esempio gtk_container_add() o gtk_box_pack_start(). + +<item> Mostrare l'oggetto con gtk_widget_show(). +</enum> +<p> +gtk_widget_show() fa sì che GTK sappia che abbiamo terminato di assegnare gli +attributi dell'oggetto grafico, e che è pronto per essere visualizzato. +Si può anche usare la funzione gtk_widget_hide per farlo sparire di nuovo. +L'ordine in cui mostrate gli oggetti grafici non è importante, ma io suggerisco +di mostrare per ultima la finestra, in modo che questa spunti fuori già completa, +invece di vedere i singoli oggetti che arrivano sullo schermo a mano a mano che si +formano. I figli di un oggetto grafico (anche una finestra è un oggetto grafico) non +vengono infatti mostrati finché la finestra stessa non viene mostrata usando la +funzione gtk_widget_show(). + + +<sect1> Casting +<p> +Noterete andando avanti che GTK usa un sistema di casting di tipo. Questa operazione +viene sempre effettuata usando delle macro che allo stesso tempo controllano la +possibilità di effettuare il cast sull'elemento dato e lo effettuano realmente. +Alcune macro che avrete modo di incontrare sono: + +<itemize> +<item> GTK_WIDGET(widget) +<item> GTK_OBJECT(object) +<item> GTK_SIGNAL_FUNC(function) +<item> GTK_CONTAINER(container) +<item> GTK_WINDOW(window) +<item> GTK_BOX(box) +</itemize> + +Tutte queste funzioni sono usate per fare il cast di argomenti di funzione. Le vedrete +negli esempi, e capirete se è il caso di usarle semplicemente guardando alle +dichiarazioni delle funzioni. + +Come potrete vedere più sotto nella gerarchia delle classi, tutti i GtkWidgets +sono derivati dalla classe base GtkObject. Ciò significa che potete usare un +widget in ogni posto in cui una funzione richiede un oggetto - semplicemente +usate la macro GTK_OBJECT(). + +Per esempio: + +<tscreen><verb> +gtk_signal_connect(GTK_OBJECT(button), "clicked", + GTK_SIGNAL_FUNC(callback_function), callback_data); +</verb></tscreen> + +Questo fa il cast del bottone in un oggetto e fornisce alla chiamata di ritorno +un cast al puntatore a funzione. + +Molti oggetti grafici sono anche contenitori. Se guardate alla gerarchia delle +classi più sotto, vedrete che molti oggetti grafici sono derivati dalla classe +GtkContainer. Ognuna di queste classi può essere usata, con la macro GTK_CONTAINER, +come argomento per funzioni che richiedono un contenitore. + +Sfortunatamente, in questo tutorial non si parlerà in modo estensivo di queste macro, +ma raccomando di dare un'occhiata ai file header di GTK. Può essere una cosa molto +educativa. Infatti, non è difficile imparare come funziona un oggetto solo guardando +le dichiarazioni delle funzioni. + +<p> +<sect1>Gerarchia degli Oggetti Grafici +<p> +Ecco, per vostro riferimento, la gerarchia delle classi usata per implementare gli +oggetti grafici. + +<tscreen><verb> + GtkObject + +-- GtkData + | \-- GtkAdjustment + | + \-- GtkWidget + +-- GtkContainer + | +-- GtkBin + | | +-- GtkAlignment + | | +-- GtkFrame + | | | *-- GtkAspectFrame + | | | + | | +-- GtkItem + | | | +-- GtkListItem + | | | +-- GtkMenuItem + | | | | +-- GtkCheckMenuItem + | | | | *-- GtkRadioMenuItem + | | | | + | | | *-- GtkTreeItem + | | | + | | +-- GtkViewport + | | \-- GtkWindow + | | +-- GtkDialog + | | \-- GtkFileSelection + | | + | +-- GtkBox + | | +-- GtkHBox + | | \-- GtkVBox + | | +-- GtkColorSelection + | | \-- GtkCurve + | | + | +-- GtkButton + | | +-- GtkOptionMenu + | | \-- GtkToggleButton + | | \-- GtkCheckButton + | | \-- GtkRadioButton + | | + | +-- GtkList + | +-- GtkMenuShell + | | +-- GtkMenu + | | \-- GtkMenuBar + | | + | +-- GtkNotebook + | +-- GtkScrolledWindow + | +-- GtkTable + | \-- GtkTree + | + +-- GtkDrawingArea + +-- GtkEntry + +-- GtkMisc + | +-- GtkArrow + | +-- GtkImage + | +-- GtkLabel + | \-- GtkPixmap + | + +-- GtkPreview + +-- GtkProgressBar + +-- GtkRange + | +-- GtkScale + | | +-- GtkHScale + | | \-- GtkVScale + | | + | \-- GtkScrollbar + | +-- GtkHScrollbar + | \-- GtkVScrollbar + | + +-- GtkRuler + | +-- GtkHRuler + | \-- GtkVRuler + | + \-- GtkSeparator + +-- GtkHSeparator + \-- GtkVSeparator + +</verb></tscreen> +<p> + +<sect1>Oggetti senza Finestre +<p> +Gli oggetti seguenti non hanno una finestra associata. Se volete catturare +degli eventi, dovrete usare l'oggetto GtkEventBox. Vedete anche la sezione su +<ref id="sec_The_EventBox_Widget" name="Il Widget EventBox"> + +<tscreen><verb> +GtkAlignment +GtkArrow +GtkBin +GtkBox +GtkImage +GtkItem +GtkLabel +GtkPaned +GtkPixmap +GtkScrolledWindow +GtkSeparator +GtkTable +GtkViewport +GtkAspectFrame +GtkFrame +GtkVPaned +GtkHPaned +GtkVBox +GtkHBox +GtkVSeparator +GtkHSeparator +</verb></tscreen> +<p> +Proseguiremo la nostra esplorazione di GTK esaminando uno alla volta tutti +gli oggetti, creando qualche semplice funzione per mostrarli. Un'altra +buona sorgente è il programma testgtk.c che viene fornito con GTK. Potete +trovarlo in gtk/testgtk.c. + +<sect>Il Widget Bottone (Button) +<p> +<sect1>Bottoni Normali +<p> +Ormai abbiamo visto tutto quello che c'è da vedere riguardo all'oggetto +``bottone''. E' piuttosto semplice, ma ci sono due modi per crare un bottone. +Potete usare gtk_button_new_with_label() per creare un bottone con una +etichetta, o usare gtk_button_new() per creare un bottone vuoto. In tal caso è poi +vostro compito impacchettare un'etichetta o una pixmap sul bottone creato. +Per fare ciò, create una nuova scatola, e poi impacchettateci i vostri +oggetti usando la solita gtk_box_pack_start, e infine usate la funzione +gtk_container_add per impacchettare la scatola nel bottone. +<p> +Ecco un esempio di utilizzo di gtk_button_new per creare un bottone con +un'immagine ed un'etichetta su di sè. Ho separato il codice usato per +creare la scatola in modo che lo possiate usare nei vostri programmi. + +<tscreen><verb> +#include <gtk/gtk.h> + + +/* crea una nuova hbox contenente un'immagine ed un'etichetta + * e ritorna la scatola creata. */ + +GtkWidget *xpm_label_box (GtkWidget *parent, gchar *xpm_filename, gchar *label_text) +{ + GtkWidget *box1; + GtkWidget *label; + GtkWidget *pixmapwid; + GdkPixmap *pixmap; + GdkBitmap *mask; + GtkStyle *style; + + /* creare una scatola per una xpm ed una etichetta */ + box1 = gtk_hbox_new (FALSE, 0); + gtk_container_border_width (GTK_CONTAINER (box1), 2); + + /* ottengo lo stile del bottone. Penso che sia per avere il colore + * dello sfondo. Se qualcuno sa il vero motivo, è pregato di dirmelo. */ + style = gtk_widget_get_style(parent); + + /* e ora via con le faccende dell'xpm stuff. Carichiamo l'xpm*/ + pixmap = gdk_pixmap_create_from_xpm (parent->window, &mask, + &style->bg[GTK_STATE_NORMAL], + xpm_filename); + pixmapwid = gtk_pixmap_new (pixmap, mask); + + /* creiamo l'etichetta per il bottone */ + label = gtk_label_new (label_text); + + /* impacchettiamo la pixmap e l'etichetta nella scatola */ + gtk_box_pack_start (GTK_BOX (box1), + pixmapwid, FALSE, FALSE, 3); + + gtk_box_pack_start (GTK_BOX (box1), label, FALSE, FALSE, 3); + + gtk_widget_show(pixmapwid); + gtk_widget_show(label); + + return (box1); +} + +/* la nostra solita funzione di callback */ +void callback (GtkWidget *widget, gpointer *data) +{ + g_print ("Hello again - %s was pressed\n", (char *) data); +} + + +int main (int argc, char *argv[]) +{ + /* GtkWidget è il tipo per contenere gli oggetti */ + GtkWidget *window; + GtkWidget *button; + GtkWidget *box1; + + gtk_init (&argc, &argv); + + /* creiamo una nuova finestra */ + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + + gtk_window_set_title (GTK_WINDOW (window), "Pixmap'd Buttons!"); + + /* E' una buona idea fare questo per tutte le finestre. */ + gtk_signal_connect (GTK_OBJECT (window), "destroy", + GTK_SIGNAL_FUNC (gtk_exit), NULL); + + + /* assegnamo lo spessore del bordo della finestra */ + gtk_container_border_width (GTK_CONTAINER (window), 10); + + /* creiamo un nuovo bottone */ + button = gtk_button_new (); + + /* Ormai dovreste esservi abituati a vedere la maggior parte di + * queste funzioni */ + gtk_signal_connect (GTK_OBJECT (button), "clicked", + GTK_SIGNAL_FUNC (callback), (gpointer) "cool button"); + + /* questa chiama la nostra funzione di creazione di scatole */ + box1 = xpm_label_box(window, "info.xpm", "cool button"); + + /* impacchetta e mostra tutti i nostri oggetti */ + gtk_widget_show(box1); + + gtk_container_add (GTK_CONTAINER (button), box1); + + gtk_widget_show(button); + + gtk_container_add (GTK_CONTAINER (window), button); + + gtk_widget_show (window); + + /* mettiti in gtk_main e aspetta che cominci il divertimento! */ + gtk_main (); + + return 0; +} +</verb></tscreen> +La funzione xpm_label_box può essere usata per impacchettare delle xpm +e delle etichette su qualsiasi oggetto che può essere un contenitore. + +<sect1> Bottoni a Commutazione (Toggle Buttons) +<p> +I bottoni a commutazione sono molto simili ai bottoni normali, tranne che per il +fatto che essi si trovano sempre in uno di due stati, che si alternano ad ogni +click. Possono trovarsi nello stato ``premuto'', e quando li si ripreme, tornano +ad essere sollevati. Ri-clickandoli, torneranno giù. + +I bottoni a commutazione sono la base per i bottoni di controllo (check button) e +per i radio-bottoni, e quindi molte delle chiamate disponibili per i bottoni +a commutazione vengono ereditati dai radio-bottoni e dai bottoni di controllo. +Ma vedremo questi aspetti nel momento in cui li incontreremo. + +Creare un nuovo bottone a commutazione: + +<tscreen><verb> +GtkWidget* gtk_toggle_button_new (void); + +GtkWidget* gtk_toggle_button_new_with_label (gchar *label); +</verb></tscreen> +<p> +Come potete immaginare, queste funzioni lavorano in modo identico che per +i bottoni normali. La prima crea un bottone a commutazione vuoto e la seconda un +bottone con un'etichetta. +<p> +Per ottenere lo stato dei widget a commutazione, compresi i radio-bottoni e i +bottoni di controllo, si può usare una macro come mostrato nell'esempio +più sotto. In questo modo lo stato dell'oggetto commutabile viene valutato in +una funzione di ritorno. Il segnale emesso dai bottoni a commutazione +(toggle button, il radio button o il check button) che ci interessa è il segnale +``toggled''. Per controllare lo stato di questi bottoni, create un gestore di +segnali che catturi il ``toggled'', e usate la macro per determinare +il suo stato. La funzione di callback avrà un aspetto più o meno così: + +<tscreen><verb> +void toggle_button_callback (GtkWidget *widget, gpointer data) + { + if (GTK_TOGGLE_BUTTON (widget)->active) + { + /* Se il programma si è arrivato a questo punto, il bottone + * a commutazione è sollevato */ + + } else { + + /* il bottone è abbassato */ + } + } + </verb></tscreen> + +<!-- + +COMMENTED! + +<tscreen><verb> +guint gtk_toggle_button_get_type (void); +</verb></tscreen> +<p> +No idea... they all have this, but I dunno what it is :) + + +<tscreen><verb> +void gtk_toggle_button_set_mode (GtkToggleButton *toggle_button, + gint draw_indicator); +</verb></tscreen> +<p> +No idea. +--> + +<tscreen><verb> +void gtk_toggle_button_set_state (GtkToggleButton *toggle_button, + gint state); +</verb></tscreen> +<p> +La chiamata qui sopra può essere usata per fare l'assegnazione dello stato +del bottone a commutazione e dei suoi figli, il radio-bottone e il bottone di +controllo. Passando come primo argomento a questa funzione il vostro bottone e +come secondo argomento il valore TRUE o FALSE, si può specificare se il +bottone deve essere sollevato (rilasciato) o abbassato (premuto). Il valore +di difetto è sollevato, cioè FALSE. + +Notate che quando usate la funzione gtk_toggle_button_set_state(), e lo +stato viene cambiato, si ha il risultato che il bottone emette il segnale +``clicked''. + +<tscreen><verb> +void gtk_toggle_button_toggled (GtkToggleButton *toggle_button); +</verb></tscreen> +<p> +Questa funzione semplicemente commuta il bottone, ed emette il segnale ``toggled''. + +<sect1> Bottoni di Controllo (Check Buttons) +<p> +I bottoni di controllo ereditano molte proprietà e funzioni dal bottone a commutazione, +ma hanno un aspetto un po' diverso. Invece di essere bottoni contenenti del testo, +si tratta di quadratini con del testo alla propria destra. Questi bottoni sono +spesso usati nelle applicazioni per commutare fra lo stato attivato e disattivato delle +opzioni. + +Le due funzioni di creazione sono analoghe a quelle del bottone normale.. + +<tscreen><verb> +GtkWidget* gtk_check_button_new (void); + +GtkWidget* gtk_check_button_new_with_label (gchar *label); +</verb></tscreen> + +La funzione new_with_label crea un bottone di controllo con una etichetta +a fianco di esso. + +Per controllare lo stato del check button si opera in modo identico al bottone +a commutazione. + +<sect1> Radio-Bottoni (Radio Buttons) +<p> +I radio-bottoni sono simili ai bottoni di controllo, tranne che per il +fatto che sono sempre raggruppati in modo che solo uno alla volta di essi +può essere selezionato (premuto). Tornano utili quando nella propria applicazione +si ha bisogno di selezionare una opzione da una breve lista. + +La creazione di un nuovo radio-bottone si fa con una di queste chiamate: + +<tscreen><verb> +GtkWidget* gtk_radio_button_new (GSList *group); + +GtkWidget* gtk_radio_button_new_with_label (GSList *group, + gchar *label); +</verb></tscreen> +<p> +Avrete notato l'argomento in più che c'è in queste chiamate. Queste hanno +infatti bisogno dela specificazione di un ``gruppo'' per svolgere il loro compito. +Per il primo bottone di un gruppo si deve passare come primo argomento il valore +NULL. Dopodiché potete creare un gruppo usando la funzione: + +<tscreen><verb> +GSList* gtk_radio_button_group (GtkRadioButton *radio_button); +</verb></tscreen> + +<p> +A questo punto potete passare questo gruppo ad ogni chiamata successiva a +gtk_radio_button_new o new_with_label. E' anche una buona idea specificare +esplicitamente quale dei bottoni dovrà essere quello premuto per difetto, +usando: + +<tscreen><verb> +void gtk_toggle_button_set_state (GtkToggleButton *toggle_button, + gint state); +</verb></tscreen> +<p> +Questa funzione è descritta nella sezione sui bottoni a commutazione, e funziona +nello stesso identico modo. + +<p> +[Inserirò un esempio di come usare questi oggetti, penso che sarebbe molto +utile] + + +<sect> Alcuni Widget +<p> +<sect1> L'Etichetta (Label) +<p> +Le etichette sono molto usate in GTK, e sono relativamente semplici. Le +etichette non emettono segnali, dal momento che non hanno una finestra +X a loro assegnata. Se avete la necessità di avere dei segnali o di fare +delle operazioni di clipping, potete usare il widget EventBox. + +Per creare una nuova etichetta, si usa: + +<tscreen><verb> +GtkWidget* gtk_label_new (char *str); +</verb></tscreen> + +In cui l'unico argomento è la stringa che si vuole sia mostrata. + +Per cambiare il testo dell'etichetta dopo che è stata creata, si usa +la funzione: + +<tscreen><verb> +void gtk_label_set (GtkLabel *label, + char *str); +</verb></tscreen> +<p> +in cui il primo argomento è l'etichetta creata in precedenza (di cui si +fa il cast usando la macro GTK_LABEL()), mentre il secondo è la nuova +stringa. + +Nel caso, lo spazio necessario per la nuova stringa verrà regolato automaticamente. + +Per ottenere la stringa corrente si usa: + +<tscreen><verb> +void gtk_label_get (GtkLabel *label, + char **str); +</verb></tscreen> + +in cui il primo argomento è l'etichetta che avete creato, e il secondo +è il valore di ritorno per la stringa. + + +<sect1>Il Widget Suggerimenti (Tooltips) +<p> +I suggerimenti sono piccole stringhe di testo che spuntano quando lasciate il +puntatore su un bottone o un altro widget per qualche secondo. Sono piuttosto +semplici da usare, per cui ne darò la spiegazione senza corredarla di esempi. +Se volede vedere un po' di codice, date un'occhiata al programma testgtk.c +distribuito con GTK. +<p> +Con alcuni widget (per esempio con l'etichetta) i suggerimenti non funzionano. +<p> +La prima chiamata che si usa per creare un nuovo tooltip è la seguente. +In una data funzione, è necessario chiamarla una sola volta: il GtkTooltip +che viene ritornato da questa funzione può essere usato per creare suggerimenti +multipli. + +<tscreen><verb> +GtkTooltips *gtk_tooltips_new (void); +</verb></tscreen> + +Una volta creato un nuovo suggerimento e il widget su cui lo volete usare, +basta usare la seguente chiamata per fare l'assegnazione: + +<tscreen><verb> +void gtk_tooltips_set_tips (GtkTooltips *tooltips, + GtkWidget *widget, + gchar *tips_text); +</verb></tscreen> + +Il primo argomento è il suggerimento che era già stato creato, che è seguito +dal widget da cui volete che spunti il suggerimento e dal testo che volete +venga mostrato. +<p> +Ecco un piccolo esempio: + +<tscreen><verb> +GtkTooltips *tooltips; +GtkWidget *button; +... +tooltips = gtk_tooltips_new (); +button = gtk_button_new_with_label ("button 1"); +... +gtk_tooltips_set_tips (tooltips, button, "This is button 1"); +</verb></tscreen> + +Ci sono anche altre funzioni che si usano con i suggerimenti. Eccone una lista +con una breve descrizione di quello che fanno. + +<tscreen><verb> +void gtk_tooltips_destroy (GtkTooltips *tooltips); +</verb></tscreen> + +Distrugge un suggerimento esistente. + +<tscreen><verb> +void gtk_tooltips_enable (GtkTooltips *tooltips); +</verb></tscreen> + +Abilita un gruppo di suggerimenti disbilitato. + +<tscreen><verb> +void gtk_tooltips_disable (GtkTooltips *tooltips); +</verb></tscreen> + +Disabilita un gruppo di suggerimenti abilitato. + +<tscreen><verb> +void gtk_tooltips_set_delay (GtkTooltips *tooltips, + gint delay); + +</verb></tscreen> +Stabilisce quanti millisecondi si deve mantenere il puntatore sopra al +widget prima che venga mostrato il suggerimento. Il valore di difetto +è di 1000 millisecondi. + +<tscreen><verb> +void gtk_tooltips_set_tips (GtkTooltips *tooltips, + GtkWidget *widget, + gchar *tips_text); +</verb></tscreen> + +Cambia il testo di un suggerimento già esistente. + +<tscreen><verb> +void gtk_tooltips_set_colors (GtkTooltips *tooltips, + GdkColor *background, + GdkColor *foreground); +</verb></tscreen> + +Assegna i colori di primo piano e di sfondo dei suggerimenti. (Non ho idea +di come si specifichino i colori). +<p> +E questo è tutto riguardo alle funzioni relative ai suggerimenti. Più +di quanto avreste mai voluto sapere :) + +<sect1> La Barra di Avanzamento (Progress Bar) +<p> +Le barre di avanzamento sono usate per mostrare lo stato di una operazione. Come potete +vedere nel frammento di codice qui sotto, sono piuttosto semplici da usare. +Ma prima vediamo come cominciare con la chiamata per creare una nuova progrss +bar. + +<tscreen><verb> +GtkWidget *gtk_progress_bar_new (void); +</verb></tscreen> + +Ora che la barra di avanzamento è stata creata, possiamo usarla.. + +<tscreen><verb> +void gtk_progress_bar_update (GtkProgressBar *pbar, gfloat percentage); +</verb></tscreen> + +Il primo argomento è la barra di avanzamento su cui volete lavorare, e il secondo +è la quantità 'completato', cioè la quantità di riempimento della progress +bar fra 0 e 100% (un numero reale fra 0 e 1). + +Le barre di avanzamento sono usate di solito con funzioni di timeout o altre di +questo tipo (vedi alla sezione <ref id="sec_timeouts" name="Timeouts, +I/O and Idle Functions">) per dare l'illusione del multitasking. Tutte +usano la funzione gtk_progress_bar_update nello stesso modo. + +Ecco un esempio di barra di avanzamento, in cui l'aggiornamento avviene usando +dei timeout. Questo codice vi mostra anche come riinizializzare le +barre di avanzamento. + +<tscreen><verb> +#include <gtk/gtk.h> + +static int ptimer = 0; +int pstat = TRUE; + +/* Questa funzione incrementa e aggiorna la barra di avanzamento, e la rimette + a zero se pstat è FALSE */ +gint progress (gpointer data) +{ + gfloat pvalue; + + /* ottiene il valore corrente della status bar */ + pvalue = GTK_PROGRESS_BAR (data)->percentage; + + if ((pvalue >= 1.0) || (pstat == FALSE)) { + pvalue = 0.0; + pstat = TRUE; + } + pvalue += 0.01; + + gtk_progress_bar_update (GTK_PROGRESS_BAR (data), pvalue); + + return TRUE; +} + +/* Questa funzione segnala la riinizializzazione della + barra di avanzamento */ +void progress_r (void) +{ + pstat = FALSE; +} + +void destroy (GtkWidget *widget, gpointer *data) +{ + gtk_main_quit (); +} + +int main (int argc, char *argv[]) +{ + GtkWidget *window; + GtkWidget *button; + GtkWidget *label; + GtkWidget *table; + GtkWidget *pbar; + + gtk_init (&argc, &argv); + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + + gtk_signal_connect (GTK_OBJECT (window), "delete_event", + GTK_SIGNAL_FUNC (destroy), NULL); + + gtk_container_border_width (GTK_CONTAINER (window), 10); + + table = gtk_table_new(3,2,TRUE); + gtk_container_add (GTK_CONTAINER (window), table); + + label = gtk_label_new ("Progress Bar Example"); + gtk_table_attach_defaults(GTK_TABLE(table), label, 0,2,0,1); + gtk_widget_show(label); + /* Crea una nuova barra di avanzamento, impacchettala nella tabella + e mostrala */ + pbar = gtk_progress_bar_new (); + gtk_table_attach_defaults(GTK_TABLE(table), pbar, 0,2,1,2); + gtk_widget_show (pbar); + + /* Attiva un timeout che gestisca l'aggiornamento automatico della barra */ + ptimer = gtk_timeout_add (100, progress, pbar); + + /* Questo bottone segnala alla barra che deve essere resettata */ + button = gtk_button_new_with_label ("Reset"); + gtk_signal_connect (GTK_OBJECT (button), "clicked", + GTK_SIGNAL_FUNC (progress_r), NULL); + gtk_table_attach_defaults(GTK_TABLE(table), button, 0,1,2,3); + gtk_widget_show(button); + + button = gtk_button_new_with_label ("Cancel"); + gtk_signal_connect (GTK_OBJECT (button), "clicked", + GTK_SIGNAL_FUNC (destroy), NULL); + + gtk_table_attach_defaults(GTK_TABLE(table), button, 1,2,2,3); + gtk_widget_show (button); + + gtk_widget_show(table); + gtk_widget_show(window); + + gtk_main (); + + return 0; +} +</verb></tscreen> + +In questo programmino ci sono quattro aree che riguardano il modo di +uso generale delle Barre di Avanzamento; le vediamo ora nell'ordine. + +<tscreen><verb> +pbar = gtk_progress_bar_new (); +</verb></tscreen> + +Questo codice crea una nuova barra ciamata pbar. + +<tscreen><verb> +ptimer = gtk_timeout_add (100, progress, pbar); +</verb></tscreen> + +Questo codice usa dei timeout per abilitare degli intervalli di tempo uguali. +Per usare le barre di avanzamento non è però necessario servirsi di timeout. + +<tscreen><verb> +pvalue = GTK_PROGRESS_BAR (data)->percentage; +</verb></tscreen> + +Qui si assegna a pvalue il valore corrente della percentuale di avanzamento. + +<tscreen><verb> +gtk_progress_bar_update (GTK_PROGRESS_BAR (data), pvalue); +</verb></tscreen> + +Infine, questo codice aggiorna la barra di avanzamento con il valore di pvalue. + +Questo è tutto quanto c'è da sapere sulle barre di avanzamento, divertitevi. + +<sect1> Dialoghi +<p> + +Il widget ``Dialogo'' è molto semplice: si tratta in realtà di una finestra +con alcuni elementi pre-impacchettati. La struttura di un dialogo è la +seguente: + +<tscreen><verb> +struct GtkDialog +{ + GtkWindow window; + + GtkWidget *vbox; + GtkWidget *action_area; +}; +</verb></tscreen> + +Come potete vedere, crea semplicemente una finestra vi inserisce una vbox +in cima, poi un separatore e infine una hbox come ``area di azione''. + +Un Dialogo può essere utilizzato per messaggi per l'utente e +altri scopi simili. E' un widget molto essenziale, che ha una sola funzione, +e precisamente: + +<tscreen><verb> +GtkWidget* gtk_dialog_new (void); +</verb></tscreen> + +Per cui, per creare una nuova finestra di dialogo, uate: + +<tscreen><verb> +GtkWidget window; +window = gtk_dialog_new (); +</verb></tscreen> + +Questa funzione crea una finestra di dialogo, dopodiché sta a voi +utilizzarla. Potete mettere un bottone nella action_area facendo +qualcosa del tipo: + +<tscreen><verb> +button = ... +gtk_box_pack_start (GTK_BOX (GTK_DIALOG (window)->action_area), button, + TRUE, TRUE, 0); +gtk_widget_show (button); +</verb></tscreen> + +Potreste anche aggiungere, ad esempio, un'etichetta all'area della vbox, +con qualcosa di questo genere: + +<tscreen><verb> +label = gtk_label_new ("Dialogs are groovy"); +gtk_box_pack_start (GTK_BOX (GTK_DIALOG (window)->vbox), label, TRUE, + TRUE, 0); +gtk_widget_show (label); +</verb></tscreen> + +Per provare a usare una finestra di dialogo, potreste provare a mettere +due bottoni nella action_area, per esempio un bottone ``Cancella'' ed un +bottone ``OK'' e un'etichetta nella vbox che chieda qualcosa all'utente o +segnali un errore. Poi potreste collegare un diverso segnale a ciascun +bottone ed eseguire l'operazione che l'utente che viene scelta dall'utente. + + +<sect1> Pixmaps +<p> + +Le Pixmap sono strutture dati che contengono immagini. Queste immagini +possono poi essere utilizzate in varie occasioni, per esempio come +icone sul desktop X-Window o come cusori. Una bitmap è una pixmap a due +colori. + +Per usare una pixmap in GTK, dobbiamo in primo luogo creare una struttura +GdkPixmap utilizzando le routine disponibili nello strato GDK. Una Pixmap +può essere creata a partire da dati presenti in memoria o letti da un file. +Vedremo ora una ad una le chiamate utilizzate per creare una pixmap. + +<tscreen><verb> +GdkPixmap *gdk_bitmap_create_from_data( GdkWindow *window, + gchar *data, + gint width, + gint height ); +</verb></tscreen> +<p> +Si usa questa routine per creare una pixmap ad un solo piano (2 colori) da +dati disponibili in memoria. Ogni bit nei dati indica lo stato acceso o +spento di un pixel. L'altezza (height) e la larghezza (width) sono espresse + in pixel. GdkWindow è un puntatore alla finestra corrente, dal momento che +le risorse di una pixmap hanno significato solo nel contesto dello schermo +in cui deve essere mostrata. + +<tscreen><verb> +GdkPixmap* gdk_pixmap_create_from_data( GdkWindow *window, + gchar *data, + gint width, + gint height, + gint depth, + GdkColor *fg, + GdkColor *bg ); +</verb></tscreen> + +Questa è usata per creare una pixmap con la profondità data (depth, ossia +numero di colori) usando i dati specificati. fg e bg indicano i colori da +usare per il primo piano e per lo sfondo. + +<tscreen><verb> +GdkPixmap* gdk_pixmap_create_from_xpm( GdkWindow *window, + GdkBitmap **mask, + GdkColor *transparent_color, + const gchar *filename ); +</verb></tscreen> + +Il formato XPM è una rappresentazione di pixmap leggibile per X Window. E' una +rappresentazione molto diffusa, e sono disponibili parecchi programmi per creare +immagini in questo formato. Il file specificato da ``filename'' deve contenere +un'immagine in questo formato, che viene caricato nella struttura pixmap. +La maschera (mask) specifica quali pixel della pixmap devono essere opachi. +Tutti gli altri pixel sono colorati usando il colore specificato da +transparent_color. Più sotto mostreremo un esempio di uso di questa funzione. + +<tscreen><verb> +GdkPixmap* gdk_pixmap_create_from_xpm_d (GdkWindow *window, + GdkBitmap **mask, + GdkColor *transparent_color, + gchar **data); +</verb></tscreen> + +Si possono incorporare piccole immagini all'interno di un programma sotto +forma di dati in formato XPM. In questo modo, invece di leggerli da un file, +si possono usare questi dati per creare una pixmap. Un esempio di questo tipo +di dati è + +<tscreen><verb> +/* XPM */ +static const char * xpm_data[] = { +"16 16 3 1", +" c None", +". c #000000000000", +"X c #FFFFFFFFFFFF", +" ", +" ...... ", +" .XXX.X. ", +" .XXX.XX. ", +" .XXX.XXX. ", +" .XXX..... ", +" .XXXXXXX. ", +" .XXXXXXX. ", +" .XXXXXXX. ", +" .XXXXXXX. ", +" .XXXXXXX. ", +" .XXXXXXX. ", +" .XXXXXXX. ", +" ......... ", +" ", +" "}; +</verb></tscreen> + +<tscreen><verb> +void gdk_pixmap_destroy( GdkPixmap *pixmap ); +</verb></tscreen> +<p> +Quando abbiamo finito di usare una pixmap e pensiamo di non doverla riutilizzare +presto, è una buona idea liberare queste risorse usando la funzione +dk_pixmap_destroy. Le pixmap devono essere considerate una risorsa preziosa. + +Quando abbiamo creato una pixmap, possiamo mostrarla come un widget GTK. +E' necessario creare un widget pixmap che contenga una pixmap GDK. Questa +operazione viene compiuta usando + +<tscreen><verb> +GtkWidget* gtk_pixmap_new( GdkPixmap *pixmap, + GdkBitmap *mask ); +</verb></tscreen> +<p> +Le altre chiamate per i widget pixmap sono + +<tscreen><verb> +guint gtk_pixmap_get_type( void ); +void gtk_pixmap_set( GtkPixmap *pixmap, + GdkPixmap *val, + GdkBitmap *mask); +void gtk_pixmap_get( GtkPixmap *pixmap, + GdkPixmap **val, + GdkBitmap **mask); +</verb></tscreen> +<p> +La funzione gtk_pixmap_set viene usata per cambiare la pixmap che viene +gestita correntemente dal widget. +gtk_pixmap_set is used to change the pixmap that the widget is currently +managing. ``val'' è la pixmap che è stata creata usando il GDK. +Segue un esempio di uso di una pixmap in un bottone. + +<tscreen><verb> + +#include <gtk/gtk.h> + + +/* dat XPM dell'icona Apri File */ +static const char * xpm_data[] = { +"16 16 3 1", +" c None", +". c #000000000000", +"X c #FFFFFFFFFFFF", +" ", +" ...... ", +" .XXX.X. ", +" .XXX.XX. ", +" .XXX.XXX. ", +" .XXX..... ", +" .XXXXXXX. ", +" .XXXXXXX. ", +" .XXXXXXX. ", +" .XXXXXXX. ", +" .XXXXXXX. ", +" .XXXXXXX. ", +" .XXXXXXX. ", +" ......... ", +" ", +" "}; + + +/* quando invocata (con il segnale delete_event), termina l'applicazione. */ +void close_application( GtkWidget *widget, gpointer *data ) { + gtk_main_quit(); +} + + +/* invocata se il bottone è clickato. Stampa semplicemente un messaggio */ +void button_clicked( GtkWidget *widget, gpointer *data ) { + printf( "button clicked\n" ); +} + + + + +int main( int argc, char *argv[] ) +{ + /* i widget sono memorizzati nel tipo GtkWidget */ + GtkWidget *window, *pixmapwid, *button; + GdkPixmap *pixmap; + GdkBitmap *mask; + GtkStyle *style; + + /* crea la finestra principale, e collega il segnale delete_event + alla terminazione dell'applicazione */ + gtk_init( &argc, &argv ); + window = gtk_window_new( GTK_WINDOW_TOPLEVEL ); + gtk_signal_connect( GTK_OBJECT (window), "delete_event", + GTK_SIGNAL_FUNC (close_application), NULL ); + gtk_container_border_width( GTK_CONTAINER (window), 10 ); + gtk_widget_show( window ); + + /* la pixmap proviene da gdk */ + style = gtk_widget_get_style( window ); + pixmap = gdk_pixmap_create_from_xpm_d( window->window, &mask, + &style->bg[GTK_STATE_NORMAL], + (gchar **)xpm_data ); + + /* un widget pixmap per contenere la pixmap */ + pixmapwid = gtk_pixmap_new( pixmap, mask ); + gtk_widget_show( pixmapwid ); + + /* un bottone per contenere il widget pixmap */ + button = gtk_button_new(); + gtk_container_add( GTK_CONTAINER(button), pixmapwid ); + gtk_container_add( GTK_CONTAINER(window), button ); + gtk_widget_show( button ); + + gtk_signal_connect( GTK_OBJECT(button), "clicked", + GTK_SIGNAL_FUNC(button_clicked), NULL ); + + /* mostra la finestra */ + gtk_main (); + + return 0; +} +</verb></tscreen> + + +Per caricare una pixmap da un file XPM chiamato icon0.xpm che si trova +nella direttorio corrente, avremmo creato la pixmap in questo modo: + +<tscreen><verb> + /* carica una pixmap da un file */ + pixmap = gdk_pixmap_create_from_xpm( window->window, &mask, + &style->bg[GTK_STATE_NORMAL], + "./icon0.xpm" ); + pixmapwid = gtk_pixmap_new( pixmap, mask ); + gtk_widget_show( pixmapwid ); + gtk_container_add( GTK_CONTAINER(window), pixmapwid ); +</verb></tscreen> + + +Usare le Sagome +<p> +Uno degli svantaggi di usare le pixmap è costituito dal fatto che l'oggetto +mostrato è sempre rettangolare, a prescindere dall'immagine. Ci piacerebbe +invece poter crare dei desktop e delle immagini con forme più naturali. Per +esempio, per l'interfaccia di un gioco, potremmo volere avere dei pulsanti +circolari. Il modo per ottenere questo effetto è di usare delle finestre +sagomate. + +Una finestra sagomata è semplicemente una pixmap in cui i pixel dello +sfondo sono trasparenti. In questo modo, se l'immagine di sfondo è +multicolore, possiamo evitare di sovrascriverla con un bordo rettangolare +attorno all'icona. Il prossimo esempio mostra una carriola sul desktop. + +<tscreen><verb> + +#include <gtk/gtk.h> + + + +/* XPM */ +static char * WheelbarrowFull_xpm[] = { +"48 48 64 1", +" c None", +". c #DF7DCF3CC71B", +"X c #965875D669A6", +"o c #71C671C671C6", +"O c #A699A289A699", +"+ c #965892489658", +"@ c #8E38410330C2", +"# c #D75C7DF769A6", +"$ c #F7DECF3CC71B", +"% c #96588A288E38", +"& c #A69992489E79", +"* c #8E3886178E38", +"= c #104008200820", +"- c #596510401040", +"; c #C71B30C230C2", +": c #C71B9A699658", +"> c #618561856185", +", c #20811C712081", +"< c #104000000000", +"1 c #861720812081", +"2 c #DF7D4D344103", +"3 c #79E769A671C6", +"4 c #861782078617", +"5 c #41033CF34103", +"6 c #000000000000", +"7 c #49241C711040", +"8 c #492445144924", +"9 c #082008200820", +"0 c #69A618611861", +"q c #B6DA71C65144", +"w c #410330C238E3", +"e c #CF3CBAEAB6DA", +"r c #71C6451430C2", +"t c #EFBEDB6CD75C", +"y c #28A208200820", +"u c #186110401040", +"i c #596528A21861", +"p c #71C661855965", +"a c #A69996589658", +"s c #30C228A230C2", +"d c #BEFBA289AEBA", +"f c #596545145144", +"g c #30C230C230C2", +"h c #8E3882078617", +"j c #208118612081", +"k c #38E30C300820", +"l c #30C2208128A2", +"z c #38E328A238E3", +"x c #514438E34924", +"c c #618555555965", +"v c #30C2208130C2", +"b c #38E328A230C2", +"n c #28A228A228A2", +"m c #41032CB228A2", +"M c #104010401040", +"N c #492438E34103", +"B c #28A2208128A2", +"V c #A699596538E3", +"C c #30C21C711040", +"Z c #30C218611040", +"A c #965865955965", +"S c #618534D32081", +"D c #38E31C711040", +"F c #082000000820", +" ", +" .XoO ", +" +@#$%o& ", +" *=-;#::o+ ", +" >,<12#:34 ", +" 45671#:X3 ", +" +89<02qwo ", +"e* >,67;ro ", +"ty> 459@>+&& ", +"$2u+ ><ipas8* ", +"%$;=* *3:.Xa.dfg> ", +"Oh$;ya *3d.a8j,Xe.d3g8+ ", +" Oh$;ka *3d$a8lz,,xxc:.e3g54 ", +" Oh$;kO *pd$%svbzz,sxxxxfX..&wn> ", +" Oh$@mO *3dthwlsslszjzxxxxxxx3:td8M4 ", +" Oh$@g& *3d$XNlvvvlllm,mNwxxxxxxxfa.:,B* ", +" Oh$@,Od.czlllllzlmmqV@V#V@fxxxxxxxf:%j5& ", +" Oh$1hd5lllslllCCZrV#r#:#2AxxxxxxxxxcdwM* ", +" OXq6c.%8vvvllZZiqqApA:mq:Xxcpcxxxxxfdc9* ", +" 2r<6gde3bllZZrVi7S@SV77A::qApxxxxxxfdcM ", +" :,q-6MN.dfmZZrrSS:#riirDSAX@Af5xxxxxfevo", +" +A26jguXtAZZZC7iDiCCrVVii7Cmmmxxxxxx%3g", +" *#16jszN..3DZZZZrCVSA2rZrV7Dmmwxxxx&en", +" p2yFvzssXe:fCZZCiiD7iiZDiDSSZwwxx8e*>", +" OA1<jzxwwc:$d%NDZZZZCCCZCCZZCmxxfd.B ", +" 3206Bwxxszx%et.eaAp77m77mmmf3&eeeg* ", +" @26MvzxNzvlbwfpdettttttttttt.c,n& ", +" *;16=lsNwwNwgsvslbwwvccc3pcfu<o ", +" p;<69BvwwsszslllbBlllllllu<5+ ", +" OS0y6FBlvvvzvzss,u=Blllj=54 ", +" c1-699Blvlllllu7k96MMMg4 ", +" *10y8n6FjvllllB<166668 ", +" S-kg+>666<M<996-y6n<8* ", +" p71=4 m69996kD8Z-66698&& ", +" &i0ycm6n4 ogk17,0<6666g ", +" N-k-<> >=01-kuu666> ", +" ,6ky& &46-10ul,66, ", +" Ou0<> o66y<ulw<66& ", +" *kk5 >66By7=xu664 ", +" <<M4 466lj<Mxu66o ", +" *>> +66uv,zN666* ", +" 566,xxj669 ", +" 4666FF666> ", +" >966666M ", +" oM6668+ ", +" *4 ", +" ", +" "}; + + +/* quando invocata (con il segnale delete_event), termina l'applicazione. */ +void close_application( GtkWidget *widget, gpointer *data ) { + gtk_main_quit(); +} + + +int main (int argc, char *argv[]) +{ + /* il tipo di dato per i widget è GtkWidget */ + GtkWidget *window, *pixmap, *fixed; + GdkPixmap *gdk_pixmap; + GdkBitmap *mask; + GtkStyle *style; + GdkGC *gc; + + /* crea la finestra principale e collega il segnale delete_event per + terminare l'applicazione. Notare che non mettiamo un titolo + alla finestra. */ + gtk_init (&argc, &argv); + window = gtk_window_new( GTK_WINDOW_POPUP ); + gtk_signal_connect (GTK_OBJECT (window), "delete_event", + GTK_SIGNAL_FUNC (close_application), NULL); + gtk_widget_show (window); + + /* ora occupiamoci della pixmap e del widget pixmap */ + style = gtk_widget_get_default_style(); + gc = style->black_gc; + gdk_pixmap = gdk_pixmap_create_from_xpm_d( window->window, &mask, + &style->bg[GTK_STATE_NORMAL], + WheelbarrowFull_xpm ); + pixmap = gtk_pixmap_new( gdk_pixmap, mask ); + gtk_widget_show( pixmap ); + + /* Per mostrare la pixmap, usiamo un widget "fixed" in cui metterla */ + fixed = gtk_fixed_new(); + gtk_widget_set_usize( fixed, 200, 200 ); + gtk_fixed_put( GTK_FIXED(fixed), pixmap, 0, 0 ); + gtk_container_add( GTK_CONTAINER(window), fixed ); + gtk_widget_show( fixed ); + + /* Questa maschera tutto tranne l'immagine stessa */ + gtk_widget_shape_combine_mask( window, mask, 0, 0 ); + + /* mostra la finestra */ + gtk_widget_set_uposition( window, 20, 400 ); + gtk_widget_show( window ); + gtk_main (); + + return 0; +} +</verb></tscreen> +<p> +Per rendere sensibile l'immagine della carriola, potremmo collegare +il segnale di pressione del bottone in modo che venga compiuta una certa +azione. Le prossime linee renderebbero l'immagine sensibile alla pressione +di un bottone del mouse che fa sì che l'applicazione termini. + +<tscreen><verb> +gtk_widget_set_events( window, + gtk_widget_get_events( window ) | + GDK_BUTTON_PRESS_MASK ); + +gtk_signal_connect( GTK_OBJECT(window), "button_press_event", + GTK_SIGNAL_FUNC(close_application), NULL ); +</verb></tscreen> + + +<sect> Widget Contenitore + +<sect1> Il widget Blocco Note (Notebook) +<p> +Il widget Blocco note è un insieme di pagine sovrapposte l'una con l'altra, +ognuna contente cose diverse. Questo widget è diventato molto comune nella +programmazione delle interfacce utente ed è un buon metodo per mostrare informazioni +tra loro correlate ma che debbano essere mostrate separatamente. + +<p> +La prima funzione da invocare che si deve conoscere, come si può intuire, è usata +per creare un nuovo Blocco Note. + +<tscreen><verb> +GtkWidget* gtk_notebook_new (void); +</verb></tscreen> + +Una volta che il notebook è sato creato, ci sono 12 funzioni che possono +operare sul widget notebook. Guardiamole individualmente. + +La prima che vediamo riguarda come posizionare l'indicatore di pagina. +Questi inidicatori di pagina o ``linguette'' (come possono anche essere chiamati) +possono essere posizionati in quattro posti: alto, basso, sinistra.destra. + +<tscreen><verb> +void gtk_notebook_set_tab_pos (GtkNotebook *notebook, GtkPositionType pos); +</verb></tscreen> + +GtkPositionType sarà uno dei seguenti valori (molto autoesplicativi) +<itemize> +<item> GTK_POS_LEFT +<item> GTK_POS_RIGHT +<item> GTK_POS_TOP +<item> GTK_POS_BOTTOM +</itemize> + +GTK_POS_TOP e' il valore predefinito. + +Ora vediamo come aggiugere le pagine al Blocco Note. Ci sono 3 modi per farlo. Diamo +un'occhiata ai primi due insieme, viste che sono molto simili. + +<tscreen><verb> +void gtk_notebook_append_page (GtkNotebook *notebook, GtkWidget *child, GtkWidget *tab_label); + +void gtk_notebook_prepend_page (GtkNotebook *notebook, GtkWidget *child, GtkWidget *tab_label); +</verb></tscreen> + +Queste funzioni aggiungono pagine al notebook inserendole rispettivamente alla fine +(append) o all'inizio (prepend). *child è il widget che è posto nella pagina del +notebook e *tab_label e la intestazione della pagina stessa. + +L'ultima funzione per aggiungere una pagina al notebook contiene tutte le proprietà +delle precedenti due, ma permette di specificare dove posizionare la pagina che +si vuole inserire. + +<tscreen><verb> +void gtk_notebook_insert_page (GtkNotebook *notebook, GtkWidget *child, GtkWidget *tab_label, gint position); +</verb></tscreen> + +I parametri sono gli stessi di _append_ e _prepend_ tranne che per il parametro in +più: ``position''. +Questo parametro viene usato per specificare in che posizione ineserire la pagina. + +Ora che conosciamo come aggiungere le pagine, vediamo come poter toglierne una. + +<tscreen><verb> +void gtk_notebook_remove_page (GtkNotebook *notebook, gint page_num); +</verb></tscreen> + +Questa funzione prende il numero della pagina specificata dal campo page_num e +rimuove la pagina corrispondente dal Blocco Note. + +Per trovare qual'è la pagina corrente nel notebook bisogna usare la funzione: + +<tscreen><verb> +gint gtk_notebook_current_page (GtkNotebook *notebook); +</verb></tscreen> + +Le prossime due funzioni sono semplicemente delle chiamate che muovono la pagina del +notebook avanti o indietro. Semplicemente forniscono le chiamate alle rispettive +funzioni del widget notebook su si può operare. NB: quando un notebook è +correntemente sull'ultima pagina e viene invocata la funzione gtk_notebook_next_page, +il notebook ritornerà automaticamente alla prima pagina. Logicamente succede anche +il contrario quando invochi gtk_notebook_prev_page e ti trovi sulla prima pagina. + +<tscreen><verb> +void gtk_notebook_next_page (GtkNoteBook *notebook); +void gtk_notebook_prev_page (GtkNoteBook *notebook); +</verb></tscreen> + +La prossima funzione stabilisce la pagina ``attiva''. Se si vuole che la pagina +principale del notebook sia per esempio la 5 (ad esempio) si può usare questa +funzione. +Se non si usa questa funzione la pagina principale sarà la 1. + +<tscreen><verb> +void gtk_notebook_set_page (GtkNotebook *notebook, gint page_num); +</verb></tscreen> + +Le prossime due funzioni aggiungono o rimuovono, rispettivamente, le intestazioni e +i bordi delle pagine. + +<tscreen><verb> +void gtk_notebook_set_show_tabs (GtkNotebook *notebook, gint show_tabs); +void gtk_notebook_set_show_border (GtkNotebook *notebook, gint show_border); +</verb></tscreen> + +show_tabs e show_border posso avere come valore TRUE o FALSE (0 or 1). + +Diamo ora una occhiata ad un esempio. Si tratta di una espansione del codice preso +dal file testgtk.c che è compreso in tutte le distribuzioni, e mostra +tutte le 13 funzioni. Questo piccolo programma crea una finestra con un notebook +e 6 bottoni. Il notebook contiene 11 pagine, aggiunte nei 3 modi differenti (alla +fine, all'inizio o in qualsiasi posizione). I bottoni permettono di girare le +intestazioni, aggiungere/rimuovere le intestazioni e i bordi, rimuovere una +pagina, cambiare la pagina avanti e indietro e uscire dal programma. + +<tscreen><verb> + +#include <gtk/gtk.h> + +/* Queta funzione ruota le posizione delle linguette delle pagine */ +void rotate_book (GtkButton *button, GtkNotebook *notebook) +{ + gtk_notebook_set_tab_pos (notebook, (notebook->tab_pos +1) %4); +} + +/* Aggiunge e rimuove le linguette e i bordi */ +void tabsborder_book (GtkButton *button, GtkNotebook *notebook) +{ + gint tval = FALSE; + gint bval = FALSE; + if (notebook->show_tabs == 0) + tval = TRUE; + if (notebook->show_border == 0) + bval = TRUE; + + gtk_notebook_set_show_tabs (notebook, tval); + gtk_notebook_set_show_border (notebook, bval); +} + +/* Rimuove una pagina */ +void remove_book (GtkButton *button, GtkNotebook *notebook) +{ + gint page; + + page = gtk_notebook_current_page(notebook); + gtk_notebook_remove_page (notebook, page); + /* E' necessario fare un refresh del widget -- + Questo forza il widget a ridisegnarsi. */ + gtk_widget_draw(GTK_WIDGET(notebook), NULL); +} + +void delete (GtkWidget *widget, gpointer *data) +{ + gtk_main_quit (); +} + +int main (int argc, char *argv[]) +{ + GtkWidget *window; + GtkWidget *button; + GtkWidget *table; + GtkWidget *notebook; + GtkWidget *frame; + GtkWidget *label; + GtkWidget *checkbutton; + int i; + char bufferf[32]; + char bufferl[32]; + + gtk_init (&argc, &argv); + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + + gtk_signal_connect (GTK_OBJECT (window), "destroy", + GTK_SIGNAL_FUNC (destroy), NULL); + + gtk_container_border_width (GTK_CONTAINER (window), 10); + + table = gtk_table_new(2,6,TRUE); + gtk_container_add (GTK_CONTAINER (window), table); + + /* Crea un nuovo notebook, e tabilisce la posizione delle linguette */ + notebook = gtk_notebook_new (); + gtk_notebook_set_tab_pos (GTK_NOTEBOOK (notebook), GTK_POS_TOP); + gtk_table_attach_defaults(GTK_TABLE(table), notebook, 0,6,0,1); + gtk_widget_show(notebook); + + /* appende una parte delle pagine */ + for (i=0; i < 5; i++) { + sprintf(bufferf, "Append Frame %d", i+1); + sprintf(bufferl, "Page %d", i+1); + + frame = gtk_frame_new (bufferf); + gtk_container_border_width (GTK_CONTAINER (frame), 10); + gtk_widget_set_usize (frame, 100, 75); + gtk_widget_show (frame); + + label = gtk_label_new (bufferf); + gtk_container_add (GTK_CONTAINER (frame), label); + gtk_widget_show (label); + + label = gtk_label_new (bufferl); + gtk_notebook_append_page (GTK_NOTEBOOK (notebook), frame, label); + } + + + /* Ora aggiungiamo una pagina in una certa posizione */ + checkbutton = gtk_check_button_new_with_label ("Check me please!"); + gtk_widget_set_usize(checkbutton, 100, 75); + gtk_widget_show (checkbutton); + + label = gtk_label_new ("Add spot"); + gtk_container_add (GTK_CONTAINER (checkbutton), label); + gtk_widget_show (label); + label = gtk_label_new ("Add page"); + gtk_notebook_insert_page (GTK_NOTEBOOK (notebook), checkbutton, label, 2); + + /* Ora finalmente aggiungiamo le pagine all'inizio */ + for (i=0; i < 5; i++) { + sprintf(bufferf, "Prepend Frame %d", i+1); + sprintf(bufferl, "PPage %d", i+1); + + frame = gtk_frame_new (bufferf); + gtk_container_border_width (GTK_CONTAINER (frame), 10); + gtk_widget_set_usize (frame, 100, 75); + gtk_widget_show (frame); + + label = gtk_label_new (bufferf); + gtk_container_add (GTK_CONTAINER (frame), label); + gtk_widget_show (label); + + label = gtk_label_new (bufferl); + gtk_notebook_prepend_page (GTK_NOTEBOOK(notebook), frame, label); + } + + /* Stabilisce quale sarà la prima pagina che sarà visualizzata. */ + gtk_notebook_set_page (GTK_NOTEBOOK(notebook), 3); + + + /* Crea un set di bottoni */ + button = gtk_button_new_with_label ("close"); + gtk_signal_connect_object (GTK_OBJECT (button), "clicked", + GTK_SIGNAL_FUNC (destroy), NULL); + gtk_table_attach_defaults(GTK_TABLE(table), button, 0,1,1,2); + gtk_widget_show(button); + + button = gtk_button_new_with_label ("next page"); + gtk_signal_connect_object (GTK_OBJECT (button), "clicked", + (GtkSignalFunc) gtk_notebook_next_page, + GTK_OBJECT (notebook)); + gtk_table_attach_defaults(GTK_TABLE(table), button, 1,2,1,2); + gtk_widget_show(button); + + button = gtk_button_new_with_label ("prev page"); + gtk_signal_connect_object (GTK_OBJECT (button), "clicked", + (GtkSignalFunc) gtk_notebook_prev_page, + GTK_OBJECT (notebook)); + gtk_table_attach_defaults(GTK_TABLE(table), button, 2,3,1,2); + gtk_widget_show(button); + + button = gtk_button_new_with_label ("tab position"); + gtk_signal_connect_object (GTK_OBJECT (button), "clicked", + (GtkSignalFunc) rotate_book, GTK_OBJECT(notebook)); + gtk_table_attach_defaults(GTK_TABLE(table), button, 3,4,1,2); + gtk_widget_show(button); + + button = gtk_button_new_with_label ("tabs/border on/off"); + gtk_signal_connect_object (GTK_OBJECT (button), "clicked", + (GtkSignalFunc) tabsborder_book, + GTK_OBJECT (notebook)); + gtk_table_attach_defaults(GTK_TABLE(table), button, 4,5,1,2); + gtk_widget_show(button); + + button = gtk_button_new_with_label ("remove page"); + gtk_signal_connect_object (GTK_OBJECT (button), "clicked", + (GtkSignalFunc) remove_book, + GTK_OBJECT(notebook)); + gtk_table_attach_defaults(GTK_TABLE(table), button, 5,6,1,2); + gtk_widget_show(button); + + gtk_widget_show(table); + gtk_widget_show(window); + + gtk_main (); + + return 0; +} +</verb></tscreen> +<p> +E speriamo che questo vi aiuti a creare i Blocco Note per le vostre applicazioni GTK! + +<sect1> Finestre Scorribili (Scrolled Windows) +<p> +Le Finestre Scorribili sono usate per creare areee scorribili in una vera finestra. +Si può inserire qualsiasi tipo di widget in questo tipo di finestra, e possono poi +essere accessibili a prescindere dalle dimensioni usando le barre di scorrimento. + +La funzione seguente è usata per creare una nuova scrolled window. + +<tscreen><verb> +GtkWidget* gtk_scrolled_window_new (GtkAdjustment *hadjustment, + GtkAdjustment *vadjustment); +</verb></tscreen> +<p> +Il primo argomento è l'aggiustamento (di quanto scendere ogni +volta) orizzontale e il secondo è quello verticale. A questi si assegna +quasi sempre il valore NULL. + +<tscreen><verb> +void gtk_scrolled_window_set_policy (GtkScrolledWindow *scrolled_window, + GtkPolicyType hscrollbar_policy, + GtkPolicyType vscrollbar_policy); +</verb></tscreen> + +Questa funzione stabilisce la politica da usare nella barra di scorrimento. Il primo +argomento è la finestra scorribile interessata. Il secondo stabilisce la politica +per la barra di scorrimento orizzontale e il terzo è quello per la politca verticale. + +La politica può essere GTK_POLICY AUTOMATIC o GTK_POLICY_ALWAYS. +GTK_POLICY_AUTOMATIC decide automaticamente se la barra di scorrimento deve essere +visualizzata, mentre con GTK_POLICY_ALWAYS la barra verrà sempre mostrata. + +<tscreen><verb> +#include <gtk/gtk.h> + +void destroy(GtkWidget *widget, gpointer *data) +{ + gtk_main_quit(); +} + +int main (int argc, char *argv[]) +{ + static GtkWidget *window; + GtkWidget *scrolled_window; + GtkWidget *table; + GtkWidget *button; + char buffer[32]; + int i, j; + + gtk_init (&argc, &argv); + + /* Crea una nuove finestra di dialogo in cui la scrolled window sarà + inserita. Una finestra di dialogo è semplicemente come una + finestra normale, ma ha anche un vbox e un separatore orizzontale + già inseriti per difetto. E'un modo semplice per + creare finestre di dialogo. */ + window = gtk_dialog_new (); + gtk_signal_connect (GTK_OBJECT (window), "destroy", + (GtkSignalFunc) destroy, NULL); + gtk_window_set_title (GTK_WINDOW (window), "dialog"); + gtk_container_border_width (GTK_CONTAINER (window), 0); + + /* crea una nuova finestra scorribile. */ + scrolled_window = gtk_scrolled_window_new (NULL, NULL); + + gtk_container_border_width (GTK_CONTAINER (scrolled_window), 10); + + /* la politica è GTK_POLICY AUTOMATIC per lo scorrimento orizzontale e + GTK_POLICY_ALWAYS per quello verticale. */ + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window), + GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS); + + /* La finestra di dialogo è creata con un vbox già inserito.*/ + gtk_box_pack_start (GTK_BOX (GTK_DIALOG(window)->vbox), scrolled_window, + TRUE, TRUE, 0); + gtk_widget_show (scrolled_window); + + /* crea una tablella di10 x 10. */ + table = gtk_table_new (10, 10, FALSE); + + /* setta lo spazio tra ogni cella di 10 pixel sia verticale sia orizzontale*/ + gtk_table_set_row_spacings (GTK_TABLE (table), 10); + gtk_table_set_col_spacings (GTK_TABLE (table), 10); + + /* inserisce la tabella nella finestra scorribile*/ + gtk_container_add (GTK_CONTAINER (scrolled_window), table); + gtk_widget_show (table); + + /* questo semplicemente crea una griglia di bottoni nella tabelle per + dimostrare il comportamento della finestra scorribile */ + for (i = 0; i < 10; i++) + for (j = 0; j < 10; j++) { + sprintf (buffer, "button (%d,%d)\n", i, j); + button = gtk_toggle_button_new_with_label (buffer); + gtk_table_attach_defaults (GTK_TABLE (table), button, + i, i+1, j, j+1); + gtk_widget_show (button); + } + + /* Aggiunge un bottone "close" alla fine della finestra */ + button = gtk_button_new_with_label ("close"); + gtk_signal_connect_object (GTK_OBJECT (button), "clicked", + (GtkSignalFunc) gtk_widget_destroy, + GTK_OBJECT (window)); + + /* questo fa sì che questo bottone sia quello predefinito */ + + GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT); + gtk_box_pack_start (GTK_BOX (GTK_DIALOG (window)->action_area), button, TRUE, TRUE, 0); + + /* Questo ottiene il bottone predefinito. Premendo semplicemente l'"enter" il + bottone si avvierà */ + gtk_widget_grab_default (button); + gtk_widget_show (button); + + gtk_widget_show (window); + + gtk_main(); + + return(0); +} +</verb></tscreen> +<p> +Prova a giocare con il ridemensionamento della finestra. Noterete la reazione della +barra di scorrimento. Potete anche usare la funzione gtk_widget_set_usize() per +assegnare la dimensione predefinita della finestra o di un widget. +<!-- (ndMichel: questa chiamata non funziona per i bottoni!) --> + + +<sect> Il Widgets Lista +<p> +Il widget GtkList serve come contenitore verticale per altri widget che +devono essere di tipo GtkListItem. + +Un widget GtkList possiede una sua propria finestra per ricevere eventi +e un suo proprio colore di sfondo che di solito è bianco. Dal momento +che è direttamente derivato dal widget GtkContainer, può essere trattato +come tale usando la macro GTK_CONTAINER(List); si veda il widget GtkContainer +per ulteriori dettagli. +Per usare il widget GtkList in tutte le sue potenzialità, si dovrebbe essere +già familiari con l'uso della GList e delle relative funzioni g_list_*(). + +All'interno della definizione della struttura del widget GtkList c'è un +campo che sarà per noi di grande interesse, cioè: + +<tscreen><verb> +struct _GtkList +{ + ... + GList *selection; + guint selection_mode; + ... +}; +</verb></tscreen> + +Il campo ``selection'' in un GtkList punta a una lista collegata di tutti +gli elementi che sono selezionati correntemente, oppure a NULL se la +selezione è vuota. Quindi, per avere informazioni sulla selezione corrente, +leggiamo il campo GTK_LIST()->selection, senza però modificarlo dal momento +che i campi interni debbono essere gestiti dalle funzioni gtk_list_*(). + +Le modalità di selezione in una GtkList, e quindi il contenuto di +GTK_LIST()->selection, sono determinate dal campo selection_mode: + +selection_mode può assumere uno dei seguenti valori: +<itemize> +<item> GTK_SELECTION_SINGLE - La selezione può essere o NULL oppure + un puntatore GList* per un singolo elemento + selezionato. + +<item> GTK_SELECTION_BROWSE - La selezione è null se la lista non contiene + alcun widget o se ha solo widget non sensibili, + oppure può contenere un puntatore a una struttura + GList, e quindi esattamente un elemento di lista. + +<item> GTK_SELECTION_MULTIPLE - La selezione è ``NULL'' se non è selezionato + alcun elemento di lista, oppure un puntatore GList al + primo elemento selezionato. Quello, a sua volta, punta + a una struttura GList per il secondo elemento selezionato + e così via. + +<item> GTK_SELECTION_EXTENDED - La selezione è sempre NULL. +</itemize> +<p> +Il valore per difetto è GTK_SELECTION_MULTIPLE. + +<sect1> Segnali +<p> +<tscreen><verb> +void GtkList::selection_changed (GtkList *LIST) +</verb></tscreen> + +Questo segnale verrà invocato ogni volta che il campo di +selezione di una GtkList è cambiato. Questo accade quando +un figlio della GtkList viene selezionato o deselezionato. + +<tscreen><verb> +void GtkList::select_child (GtkList *LIST, GtkWidget *CHILD) +</verb></tscreen> + +Questo segnale viene invocato quando un fuglio di una GtkList +sta per essere selezionato. Questo accade principalmente in +occasione di chiamate a gtk_list_select_item() e gtk_list_select_child(), +di pressioni di bottoni e a volte può venir fatto scattare indirettamente +in altre occasioni, in cui vengono aggiunti o rimossi dei figli +dalla GtkList. + +<tscreen><verb> +void GtkList::unselect_child (GtkList *LIST, GtkWidget *CHILD) +</verb></tscreen> + +Questo segnale viene invocato quando un figlio della GtkList sta +per essere deselezionato. Ciò accade principalmente in occasione +di chiamate a gtk_list_unselect_item() e gtk_list_unselect_child(), +di pressioni di bottoni, e a volte può venir fatto scattare indirettamente +in altre occasioni, in cui vengono aggiunti o rimossi dei figli +dalla GtkList. + +<sect1> Funzioni +<p> +<tscreen><verb> +guint gtk_list_get_type (void) +</verb></tscreen> + +Restituisce l'identificatore di tipo `GtkList'. + +<tscreen><verb> +GtkWidget* gtk_list_new (void) +</verb></tscreen> + +Crea un nuovo oggetto `GtkList'. Il nuovo widget viene +restituito sotto forma di un puntoatore ad un oggetto +`GtkWidgetì'. In caso di fallimento, viene ritornato NULL. + +<tscreen><verb> +void gtk_list_insert_items (GtkList *LIST, GList *ITEMS, gint POSITION) +</verb></tscreen> + +Inserisce degli elementi di lista nella LIST, a partire da +POSITION. ITEMS ITEMS è una lista doppiamente collegata, in +cui ci si aspetta che i puntatori di ogni nodo puntino a +un GtkListItem appena creato. I nodi GList di ITEMS vengono +assunti dalla LIST. + +<tscreen><verb> +void gtk_list_append_items (GtkList *LIST, GList *ITEMS) +</verb></tscreen> + +Inserisce elementi di lista proprio come gtk_list_insert_items(), +ma alla fine della LIST. I nodi GList di ITEMS vengono +assunti dalla LIST. + +<tscreen><verb> +void gtk_list_prepend_items (GtkList *LIST, GList *ITEMS) +</verb></tscreen> + +Inserisce elementi di lista proprio come gtk_list_insert_items(), +ma al principio della LIST. I nodi GList di ITEMS vengono +assunti dalla LIST. + +<tscreen><verb> +void gtk_list_remove_items (GtkList *LIST, GList *ITEMS) +</verb></tscreen> + +Rimuove degli elementi di lista dalla LIST. ITEMS è una lista +doppiamente collegata in cui ci si aspetta che i puntatori di +ogni nodo puntino a un figlio diretto di LIST. E' poi responsabilità +del chiamante di fare una chiamata a g_list_free(ITEMS). E' anche +necessario che il chiamante distrugga lui stesso gli elementi della +lista. + +<tscreen><verb> +void gtk_list_clear_items (GtkList *LIST, gint START, gint END) +</verb></tscreen> + +Rimuove e distrugge elementi di lista da LIST. Un widget ne è +interessato se la sua posizione corrente all'interno di LIST è compreso +fra START ed END. + +<tscreen><verb> +void gtk_list_select_item (GtkList *LIST, gint ITEM) +</verb></tscreen> + +Invoca il segnale GtkList::select_child per un elemento di lista +specificato dalla sua posizione corrente all'interno di LIST. + +<tscreen><verb> +void gtk_list_unselect_item (GtkList *LIST, gint ITEM) +</verb></tscreen> + +Invoca il segnale GtkList::unselect_child per un elemento di lista +specificato dalla sua posizione corrente all'interno di LIST. + +<tscreen><verb> +void gtk_list_select_child (GtkList *LIST, GtkWidget *CHILD) +</verb></tscreen> + +Invoca il segnale GtkList::select_child per uno specifico CHILD. + +<tscreen><verb> +void gtk_list_unselect_child (GtkList *LIST, GtkWidget *CHILD) +</verb></tscreen> + +Invoca il segnale GtkList::unselect_child per uno specifico CHILD. + +<tscreen><verb> +gint gtk_list_child_position (GtkList *LIST, GtkWidget *CHILD) +</verb></tscreen> + +Restituisce la posizione di CHILD all'interno di LIST. In caso di fallimento, +viene restituito `-1'. + +<tscreen><verb> +void gtk_list_set_selection_mode (GtkList *LIST, GtkSelectionMode MODE) +</verb></tscreen> + +Assegna a LIST il modo di selezione MODE, che può essere uno fra +GTK_SELECTION_SINGLE, GTK_SELECTION_BROWSE, GTK_SELECTION_MULTIPLE o +GTK_SELECTION_EXTENDED. + +<tscreen><verb> +GtkList* GTK_LIST (gpointer OBJ) +</verb></tscreen> + +Fa il cast di un generico puntatore a `GtkList*'. Per maggiori +informazioni vedere Standard Macros::. + +<tscreen><verb> +GtkListClass* GTK_LIST_CLASS (gpointer CLASS) +</verb></tscreen> + +Fa il cast di un generico puntatore a `GtkListClass*'. Per maggiori +informazioni vedere Standard Macros::. + +<tscreen><verb> +gint GTK_IS_LIST (gpointer OBJ) +</verb></tscreen> + +Determina se un generico puntatore si riferisce ad un oggetto `GtkList'. +Per maggiori informazioni vedere Standard Macros::. + + +<sect1> Esempio +<p> +Diamo di seguito un programma di esempio che stamperà i campbiamenti +della selezione di una GtkList, e vi lascia ``imprigionare'' gli elementi +di una lista selezionandoli con il pulsante destro del mouse: + +<tscreen><verb> +/* compilate questo programma con: + * $ gcc -I/usr/local/include/ -lgtk -lgdk -lglib -lX11 -lm -Wall main.c + */ + +/* includiamo i file header di gtk+ + * includiamo stdio.h, ne abbiamo bisogno per printf() + */ +#include <gtk/gtk.h> +#include <stdio.h> + +/* Questa e' la nostra stringa di identificazione dei dati per assegnarli + * ad elementi di lista + */ +const gchar *list_item_data_key="list_item_data"; + + +/* prototipi per i gestori di segnale che connetteremo + * al widget GtkList + */ +static void sigh_print_selection (GtkWidget *gtklist, + gpointer func_data); +static void sigh_button_event (GtkWidget *gtklist, + GdkEventButton *event, + GtkWidget *frame); + + +/* funzione main per predisporre l'interfaccia utente */ + +gint main (int argc, gchar *argv[]) +{ + GtkWidget *separator; + GtkWidget *window; + GtkWidget *vbox; + GtkWidget *scrolled_window; + GtkWidget *frame; + GtkWidget *gtklist; + GtkWidget *button; + GtkWidget *list_item; + GList *dlist; + guint i; + gchar buffer[64]; + + + /* inizializza gtk+ (e di conseguenza gdk) */ + + gtk_init(&argc, &argv); + + + /* crea una finestra in cui mettere tutti i widget + * connette gtk_main_quit() al segnale "destroy" della finestra + * per gestire le richieste di chiusura finestra del window manager + */ + window=gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_window_set_title(GTK_WINDOW(window), "GtkList Example"); + gtk_signal_connect(GTK_OBJECT(window), + "destroy", + GTK_SIGNAL_FUNC(gtk_main_quit), + NULL); + + + /* all'interno della finestra abbiamo bisogno di una scatola + * in cui mettere i widget verticalmente */ + vbox=gtk_vbox_new(FALSE, 5); + gtk_container_border_width(GTK_CONTAINER(vbox), 5); + gtk_container_add(GTK_CONTAINER(window), vbox); + gtk_widget_show(vbox); + + /* questa è la finestra scorribile in cui mettere il widget GtkList */ + scrolled_window=gtk_scrolled_window_new(NULL, NULL); + gtk_widget_set_usize(scrolled_window, 250, 150); + gtk_container_add(GTK_CONTAINER(vbox), scrolled_window); + gtk_widget_show(scrolled_window); + + /* crea il widget GtkList + * connette il gestore di segnale sigh_print_selection() + * al segnale "selection_changed" della GtkList, per stampare + * gli elementi selezionati ogni volta che la selezione cambia + */ + gtklist=gtk_list_new(); + gtk_container_add(GTK_CONTAINER(scrolled_window), gtklist); + gtk_widget_show(gtklist); + gtk_signal_connect(GTK_OBJECT(gtklist), + "selection_changed", + GTK_SIGNAL_FUNC(sigh_print_selection), + NULL); + + /* creiamo una "Prigione" (Prison) in cui mettere gli elementi di lista ;) + */ + frame=gtk_frame_new("Prison"); + gtk_widget_set_usize(frame, 200, 50); + gtk_container_border_width(GTK_CONTAINER(frame), 5); + gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_OUT); + gtk_container_add(GTK_CONTAINER(vbox), frame); + gtk_widget_show(frame); + + /* connette il gestore di segnale sigh_button_event() alla GtkList + * il quale gestira' l'"imprigionamento" degli elementi di lista + */ + gtk_signal_connect(GTK_OBJECT(gtklist), + "button_release_event", + GTK_SIGNAL_FUNC(sigh_button_event), + frame); + + /* crea un separatore + */ + separator=gtk_hseparator_new(); + gtk_container_add(GTK_CONTAINER(vbox), separator); + gtk_widget_show(separator); + + /* infine creiamo un bottone e connettiamone il segnale "clicked" + * alla distruzione della finestra + */ + button=gtk_button_new_with_label("Close"); + gtk_container_add(GTK_CONTAINER(vbox), button); + gtk_widget_show(button); + gtk_signal_connect_object(GTK_OBJECT(button), + "clicked", + GTK_SIGNAL_FUNC(gtk_widget_destroy), + GTK_OBJECT(window)); + + + /* a questo punto creiamo 5 elementi di lista, ognuno con la + * propria etichetta, e li aggiungiamo alla GtkList usando + * gtk_container_add(). Inoltre, recuperiamo la stringa di testo + * dall'etichetta e la associamo, per ogni elemento, a + * list_item_data_key + */ + for (i=0; i<5; i++) { + GtkWidget *label; + gchar *string; + + sprintf(buffer, "ListItemContainer with Label #%d", i); + label=gtk_label_new(buffer); + list_item=gtk_list_item_new(); + gtk_container_add(GTK_CONTAINER(list_item), label); + gtk_widget_show(label); + gtk_container_add(GTK_CONTAINER(gtklist), list_item); + gtk_widget_show(list_item); + gtk_label_get(GTK_LABEL(label), &string); + gtk_object_set_data(GTK_OBJECT(list_item), + list_item_data_key, + string); + } + + /* qui creiamo altre 5 etichette, questa volta usando + * per la creazione gtk_list_item_new_with_label(). + * Non possiamo recuperare la stringa di testo dall'etichetta + * dal momento che non disponiamo di puntatori alle etichette, + * quindi associamo semplicemente il list_item_data_key di ogni + * elemento di lista con la medesima stringa di testo. + * Per aggiungere elementi di lista, li mettiamo tutti in una lista + * doppiamente collegata (GList), e quindi li aggiungiamo con una + * unica chiamata a gtk_list_append_items(). + * Dal momento che usiamo g_list_prepend() per mettere gli elementi + * nella lista doppiamente collegata, il loro ordine sara' discendente + * (invece che ascendente come sarebbe se usassimo g_list_append()) + */ + dlist=NULL; + for (; i<10; i++) { + sprintf(buffer, "List Item with Label %d", i); + list_item=gtk_list_item_new_with_label(buffer); + dlist=g_list_prepend(dlist, list_item); + gtk_widget_show(list_item); + gtk_object_set_data(GTK_OBJECT(list_item), + list_item_data_key, + "ListItem with integrated Label"); + } + gtk_list_append_items(GTK_LIST(gtklist), dlist); + + /* e finalmente vogliamo vedere la finestra, non e' vero? ;) + */ + gtk_widget_show(window); + + /* lancia il ciclo principale di gtk + */ + gtk_main(); + + /* si arriva a questo punto dopo la chiamata di gtk_main_quit(), + * il che accade quando viene distrutta la finestra principale + */ + return 0; +} + +/* questo e' il gestore di segnale che e' stato connesso all'evento di + * pressione/rilascio del bottone della GtkList + */ +void +sigh_button_event (GtkWidget *gtklist, + GdkEventButton *event, + GtkWidget *frame) +{ + /* facciamo qualcosa solo nel caso di rilascio del terzo bottone + * (quello piu' a destra) + */ + if (event->type==GDK_BUTTON_RELEASE && + event->button==3) { + GList *dlist, *free_list; + GtkWidget *new_prisoner; + + /* recuperiamo l'elemento di lista selezionato correntemente, + * che sara' il nostro prossimo prigioniero ;) + */ + dlist=GTK_LIST(gtklist)->selection; + if (dlist) + new_prisoner=GTK_WIDGET(dlist->data); + else + new_prisoner=NULL; + + /* cerchiamo elementi di lista gia' imprigionati, + * li rimetteremo nella lista. + * Ricordare di liberare la lista doppiamente collegata + * che viene restituita da gtk_container_children() + */ + dlist=gtk_container_children(GTK_CONTAINER(frame)); + free_list=dlist; + while (dlist) { + GtkWidget *list_item; + + list_item=dlist->data; + + gtk_widget_reparent(list_item, gtklist); + + dlist=dlist->next; + } + g_list_free(free_list); + + /* se abbiamo un nuovo prigioniero, lo rimuoviamo + * dalla GtkList e lo mettiamo nella cornice della + * "Prigione". Dobbiamo prima deselezionare l'elemento + */ + if (new_prisoner) { + GList static_dlist; + + static_dlist.data=new_prisoner; + static_dlist.next=NULL; + static_dlist.prev=NULL; + + gtk_list_unselect_child(GTK_LIST(gtklist), + new_prisoner); + gtk_widget_reparent(new_prisoner, frame); + } + } +} + +/* questo e' il gestore di segnaleche viene chiamato de la + * GtkList emette il segnale "selection_changed" + */ +void +sigh_print_selection (GtkWidget *gtklist, + gpointer func_data) +{ + GList *dlist; + + /* recuperiamo la lista doppiamente collegata degli + * elementi selezionati della GtkList, ricordate di + * trattarla come sola lettura + */ + dlist=GTK_LIST(gtklist)->selection; + + /* se non ci sono elementi selezionati non c'e' altro da + * fare che dirlo all'utente + */ + if (!dlist) { + g_print("Selection cleared\n"); + return; + } + /* ok, abbiamo una selezione e quindi lo scriviamo + */ + g_print("The selection is a "); + + /* ottieniamo l'elemento di lista dalla lista doppiamente + * collegata e poi richiediamo i dati associati con + * list_item_data_key. Poi semplicemente li stampiamo + */ + while (dlist) { + GtkObject *list_item; + gchar *item_data_string; + + list_item=GTK_OBJECT(dlist->data); + item_data_string=gtk_object_get_data(list_item, + list_item_data_key); + g_print("%s ", item_data_string); + + dlist=dlist->next; + } + g_print("\n"); +} +</verb></tscreen> + +<sect1> Il Widget Elemento di Lista (List Item) +<p> +Il widget GtkListItem è progettato allo scopo di essere un contenitore +collegato ad un figlio, per fornire le funzioni per la selezione e deselezione +allo stesso modo in cui il widget GtkList ne ha bisogno per i propri figli. + +Un GtkListItem ha la sua propria finestra per ricevere eventi, e ha il suo +proprio colore di sfondo, che di solito è bianco. + +Dal momento che questo widget deriva direttamente da GtkItem, può essere +trattato come tale usando la macro GTK_ITEM(ListItem), vedere il widget +GtkItem per ulteriori informazioni. +Di solito un GtkListItem ha solo un'etichetta per identificare per esempio +un nome di file all'interno di una GtkList -- per cui viene fornita la +funzione appropriata gtk_list_item_new_with_label(). Si può ottenere lo +stesso effetto creando una GtkLabel da sola, assegnando al suo allineamento +i valori xalign=0 e yalign=0.5, aggiungendo successivamente un contenitore +alla GtkListItem. + +Dal momento che non si è obbligati a mettere una GtkLabel, si può anche +aggiungere una GtkVBox una GtkArrow ecc. alla GtkListItem. + +<sect1> Segnali +<p> +Un GtkListItem non crea alcun nuovo segnale di per se, ma eredita +i segnali di GtkItem. Per ulteriori informazioni, vedere GtkItem::. + + +<sect1> Funzioni +<p> + +<tscreen><verb> +guint gtk_list_item_get_type (void) +</verb></tscreen> + +Restituisce l'identificatore di tipo `GtkListItem'. + +<tscreen><verb> +GtkWidget* gtk_list_item_new (void) +</verb></tscreen> + +Crea un nuovo oggetto `GtkListItem'. Il nuovo widget viene restituito +sottoforma di un puntatore ad un oggetto `GtkWidget'. In caso di +fallimento, viene restituito `NULL'. + +<tscreen><verb> +GtkWidget* gtk_list_item_new_with_label (gchar *LABEL) +</verb></tscreen> + +Cre un nuovo oggetto `GtkListItem', avente come unico figlio +un GtkLabel. Il nuovo widget viene restituito +sottoforma di un puntatore ad un oggetto `GtkWidget'. In caso di +fallimento, viene restituito `NULL'. + +<tscreen><verb> +void gtk_list_item_select (GtkListItem *LIST_ITEM) +</verb></tscreen> + +Questa funzione è essenzialmente un wrapper per una chiamata a +gtk_item_select (GTK_ITEM (list_item)) che emetterà il segnale +GtkItem::select. +Vedere GtkItem:: per maggiori informazioni. + +<tscreen><verb> +void gtk_list_item_deselect (GtkListItem *LIST_ITEM) +</verb></tscreen> + +Questa funzione è essenzialmente un wrapper per una chiamata a +gtk_item_deselect (GTK_ITEM (list_item)) che emetterà il segnale +GtkItem::deselect. +Vedere GtkItem:: per maggiori informazioni. + +<tscreen><verb> +GtkListItem* GTK_LIST_ITEM (gpointer OBJ) +</verb></tscreen> + +Effettua il cast di un puntatore generico a `GtkListItem*'. Vedere +Standard Macros:: per maggiorni informazioni. + +<tscreen><verb> +GtkListItemClass* GTK_LIST_ITEM_CLASS (gpointer CLASS) +</verb></tscreen> + +Effettua il cast di un puntatore generico a `GtkListItemClass*'. Vedere +Standard Macros:: per maggiorni informazioni. + +<tscreen><verb> +gint GTK_IS_LIST_ITEM (gpointer OBJ) +</verb></tscreen> + +Determina se un puntatore generico si riferisce ad un oggetto +`GtkListItem'. Vedere Standard Macros:: per maggiorni informazioni. + +<sect1> Esempio +<p> +Come esempio su questo argomento, si veda quello relativo alla GtkList, +che riguarda anche l'uso del GtkListItem. + +<sect> Selezione di File (File Selections) +<p> + +Il widget Selezione di File è un modo rapido e semplice per mostrare una +finestra di dialogo `File'. Questa si presenta completa di bottoni Ok, +Cancel e Help, un buon modo per tagliare i tempi di programmazione. + +Per creare una nuova finestra di selezione file usate: + +<tscreen><verb> +GtkWidget* gtk_file_selection_new (gchar *title); +</verb></tscreen> + +Per assegnare il nome del file, ad esempio per predisporre una certa +directory o per dare un certo nome di file per difetto, usate la seguente +funzione: + +<tscreen><verb> +void gtk_file_selection_set_filename (GtkFileSelection *filesel, gchar *filename); +</verb></tscreen> + +Per recuperare il testo che l'utente ha inserito o che ha selezionato con +il mouse, si usa la funzione: + +<tscreen><verb> +gchar* gtk_file_selection_get_filename (GtkFileSelection *filesel); +</verb></tscreen> + +Ci sono anche dei puntatori ai widget che sono contenuti all'interno +del widget di selezione file. Si tratta di: + +<itemize> +<item>dir_list +<item>file_list +<item>selection_entry +<item>selection_text +<item>main_vbox +<item>ok_button +<item>cancel_button +<item>help_button +</itemize> + +Molto probabilmente potreste voler usare i puntatori a ok_button, +cancel_button e help_button per segnalarne l'uso. + +Ecco un esempio rubato da testgtk.c, nodificato per essere eseguito da +solo. Come potrete vedere, non c'è molto più che la creazione di un +widget di selezione file. In questo esempio, il bottone Help non fa nulla +mentre è mostrato allo schermo, dal momento che non c'è alcun segnale +collegato con esso. + +<tscreen><verb> +#include <gtk/gtk.h> + +/* Recupera il nome di file selezionato e stampalo a console */ +void file_ok_sel (GtkWidget *w, GtkFileSelection *fs) +{ + g_print ("%s\n", gtk_file_selection_get_filename (GTK_FILE_SELECTION (fs))); +} + +void destroy (GtkWidget *widget, gpointer *data) +{ + gtk_main_quit (); +} + +int main (int argc, char *argv[]) +{ + GtkWidget *filew; + + gtk_init (&argc, &argv); + + /* Crea un nuovo widget di selezione file */ + filew = gtk_file_selection_new ("File selection"); + + gtk_signal_connect (GTK_OBJECT (filew), "destroy", + (GtkSignalFunc) destroy, &filew); + /* Connette ok_button alla funzione file_ok_sel */ + gtk_signal_connect (GTK_OBJECT (GTK_FILE_SELECTION (filew)->ok_button), + "clicked", (GtkSignalFunc) file_ok_sel, filew ); + + /* Connette cancel_button alla funzione di distruzione del widget */ + gtk_signal_connect_object (GTK_OBJECT (GTK_FILE_SELECTION (filew)->cancel_button), + "clicked", (GtkSignalFunc) gtk_widget_destroy, + GTK_OBJECT (filew)); + + /* Preassegnamo un nome di file, come se stessimo dando un valore per difetto in + dialogo di tipo `` salva con nome '' */ + gtk_file_selection_set_filename (GTK_FILE_SELECTION(filew), + "penguin.png"); + + gtk_widget_show(filew); + gtk_main (); + return 0; +} +</verb></tscreen> + +<sect>Il Widget Menù (Menu Widgets) +<p> +Ci sono due modi per creare dei menù, quello facile e quello difficile. +Ognuno è più adatto per certe circostanze, ma di solito si può usare il +modo semplice, cioé menu_factory (la ``fabbrica dei menù''). Il modo +``difficile'' è di crearsi tutti i menù usando direttamente le chiamate. +Quello semplice è di usare le chiamate di tipo gtk_menu_factory. Anche se +è un modo molto più semplice, ci sono svantaggi e vantaggi per ciascuno +dei due approcci. + +La menufactory è molto più semplice da usare e per aggiungere dei nuovi +menù, anche se scriversi un po' di funzioni per creare dei menù con il +metodo manuale può dare risultati molto migliori dal punto di vista +dell'usabilità. Con la menufactory, non è possibile mettere immagini o +segni '/' nei menù. +<p> +<sect1>Creazione Manuale di Menù +<p> +Seguendo la tradizionale arte dell'insegnamento, partiamo dal modo +difficile. <tt>:)</> +<p> +Diamo un'occhiata alle funzioni usate per creare dei menù. +Con questa prima funzione si crea un nuovo menù: + +<tscreen><verb> +GtkWidget *gtk_menu_bar_new() +</verb></tscreen> + +Questa funzione crea una nuova barra di menù. Per impacchettarla in una +finestra o si usa la funzione gtk_container_add, oppure, per impacchettarla +in una scatola, le funzioni box_pack - come con i bottoni. + +<tscreen><verb> +GtkWidget *gtk_menu_new(); +</verb></tscreen> + +Questa funzione restituisce un puntatore ad un nuovo menù, non viene mai +realmente mostrato (con gtk_widget_show), serve solo per contenere gli +elementi del menù. Spero che il tutto risulti più chiaro quando dare +un'occhiata all'esempio più sotto. +<p> +Le prossime due chiamate sono usate per creare degli elementi che poi +vengono impacchettati nel menù. + +<tscreen><verb> +GtkWidget *gtk_menu_item_new() +</verb></tscreen> + +e + +<tscreen><verb> +GtkWidget *gtk_menu_item_new_with_label(const char *label) +</verb></tscreen> + +Queste chiamate sono usate per creare i menu che devono essere mostrati. +Ricordate la differenza che esiste fra un ``menù'' come quelli creati con +gtk_menu_new e un ``elemento di menù'' (menu item) come quelli creati con +la funzione creata con gtk_menu_item_new. L'elemento di menù sarà un bottone +vero e proprio con una azione associata, mentre un menù è solo un contenitore +che li raccoglie. + +<tscreen><verb> +gtk_menu_item_append() + +gtk_menu_item_set_submenu() +</verb></tscreen> + +Le funzioni gtk_menu_item_new_with_label e gtk_menu_item_new si comportano esattamente come +vi aspettereste dopo aver visto le funzioni che riguardano i bottoni. La prima +crea un elemento di menù con un'etichetta già applicata, mentre la seconda crea +un nuovo elemento di menù vuoto. +<p> +Ecco i passi necessari per creare una barra di menù con i relativi menù collegati: +<itemize> +<item> Create un nuovo menù con gtk_menu_new() +<item> Create un elementoa di menù con using gtk_menu_item_new(). Questo rappresenta + la base del menù, e il testo che appare qui sarà sulla barra stessa. +<item> Usate delle chiamate multiple a gtk_menu_item_new() per ognuno degli + elementi che volete mettere nel vostro menù. Usate inoltre gtk_menu_item_append() + per mettere assieme ognuno di questi nuovo elementi. Si crea così una lista di + elementi di menù. +<item> Usate gtk_menu_item_set_submenu() per attaccare gli elementi di menù + creati all'elemento di menù base (quello creato nel secondo passaggio). +<item> Create una nuova barra di menù usando gtk_menu_bar_new. Questo passo + necessita di essere effettuato una sola volta quando si crea una serie di + menù su una serie di menù su una sola barra. +<item> Usate gtk_menu_bar_append per mettere il menù base sulla barra dei menù. +</itemize> +<p> +Creare un menù a comparsa è più o meno la stessa cosa. La differenza è che il +il menù non viene attivato ``automaticamente'' da una barra, bensì esplicitamente +con la chiamata alla funzione gtk_menu_popup() da un evento di pressione di +un pulsante. +Seguite questi passaggi: +<itemize> +<item>Create una funzione di gestione di un evento. Essa deve seguire il prototipo +<tscreen> +static gint handler(GtkWidget *widget, GdkEvent *event); +</tscreen> +e usare l'evento per scoprire dove il menu deve essere fatto comparire. +<item>Nel gestore di evento, se questo è la pressione di un bottone, trattate +<tt>event</tt> come l'evento relativo ad un bottone (cosa che in effetti è) +e usatelo come mostrato nel codice di esempio per passare informazioni a +gtk_menu_popup(). +<item>Collegate il gestore di evento a un widget con +<tscreen> +gtk_signal_connect_object(GTK_OBJECT(widget), "event", + GTK_SIGNAL_FUNC (handler), GTK_OBJECT(menu)); +</tscreen> +in cui <tt>widget</tt> è il widget a cui state effettuando il collegamento, e +<tt>handler</tt> è la funzione di gestione, mentre <tt>menu</tt> è un menù +creato con gtk_menu_new(). Quest'ultimo può essere un menù che viene anche +attivato da una barra di menù, come mostrato nel codice di esempio. +</itemize> +<p> +<sect1>Esempio di Menù Manuale +<p> +Per la teoria dovrebbe essere abbastanza. Diamo un'occhiata ad un esempio che +ci aiuti a chiarire le cose. + +<tscreen><verb> + +#include <gtk/gtk.h> + +static gint button_press (GtkWidget *, GdkEvent *); +static void menuitem_response (gchar *); + + +int main (int argc, char *argv[]) +{ + + GtkWidget *window; + GtkWidget *menu; + GtkWidget *menu_bar; + GtkWidget *root_menu; + GtkWidget *menu_items; + GtkWidget *vbox; + GtkWidget *button; + char buf[128]; + int i; + + gtk_init (&argc, &argv); + + /* crea una nuova finestra */ + window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_window_set_title(GTK_WINDOW (window), "GTK Menu Test"); + gtk_signal_connect(GTK_OBJECT (window), "delete_event", + (GtkSignalFunc) gtk_exit, NULL); + + /* Inizializziamo il menù, e ricordate: mai applicare + * gtk_show_widget() al widget menù!! + * Questo è il menù che contiene gli elementi, quello che + * spunta quando si fa click sul "Menù radice" nell'applicazione */ + menu = gtk_menu_new(); + + /* Questo è il menù radice, e l'etichetta sarà il nome del menù che + * verrà mostrato sulla barra dei menù. Non ci sarà alcun gestore di + * segnale collegato, dal momento che non fa altro che mostrare il resto + * del menù quando viene premuto. */ + root_menu = gtk_menu_item_new_with_label("Root Menu"); + + gtk_widget_show(root_menu); + + /* Ora creiamo un ciclo che crea tre elementi di menu per "test-menu". + * Notete la chiamata a gtk_menu_append. In questo punto aggiungiamo una + * lista di elementi al nostro menù. Normalmente, dovremmo poi catturare + * il segnale di attivazione per ognuno degli elementi del menu, e creare + * una funzione di ritorno per ciascuno di essi, ma qui non li mettiamo per + * brevità. */ + + for(i = 0; i < 3; i++) + { + /* Copia i nomi in buf. */ + sprintf(buf, "Test-undermenu - %d", i); + + /* Crea un nuovo elemento di menù con un nome... */ + menu_items = gtk_menu_item_new_with_label(buf); + + /* ...e aggiungilo al menù. */ + gtk_menu_append(GTK_MENU (menu), menu_items); + + /* Fa qualcosa di interessante quando si seleziona l'elemento */ + gtk_signal_connect_object(GTK_OBJECT(menu_items), "activate", + GTK_SIGNAL_FUNC(menuitem_response), (gpointer) g_strdup(buf)); + + /* Mostra il widget */ + gtk_widget_show(menu_items); + } + + /* Ora specifichiamo che vogliamo che il menù che abbiamo appena creato + * sia il menù radice *// + gtk_menu_item_set_submenu(GTK_MENU_ITEM (root_menu), menu); + + /* Una vbox in cui mettere un menù ed un bottone: */ + vbox = gtk_vbox_new(FALSE, 0); + gtk_container_add(GTK_CONTAINER(window), vbox); + gtk_widget_show(vbox); + + /* Crea una barra dei menù per metterci i menù e l'aggiunge alla finestra principale */ + menu_bar = gtk_menu_bar_new(); + gtk_box_pack_start(GTK_BOX(vbox), menu_bar, FALSE, FALSE, 2); + gtk_widget_show(menu_bar); + + /* Crea un bottone a cui collegare un menù */ + button = gtk_button_new_with_label("press me"); + gtk_signal_connect_object(GTK_OBJECT(button), "event", + GTK_SIGNAL_FUNC (button_press), GTK_OBJECT(menu)); + gtk_box_pack_end(GTK_BOX(vbox), button, TRUE, TRUE, 2); + gtk_widget_show(button); + + /* E finalmente attacchiamo l'elemento di menù alla barra dei menù -- questo + * è l'elemento di menù "radice" di cui parlavo */ + gtk_menu_bar_append(GTK_MENU_BAR (menu_bar), root_menu); + + /* La finestra va mostrata sempre come ultimo passo in modo che sia già + * completa di tutti i suoi elementi. */ + gtk_widget_show(window); + + gtk_main (); + + return 0; +} + + + +/* Risponde alla pressione di un bottone impostando un menù che + * viene passato come widget. + * Notate che l'argomento "widget" si riferisce al menù impostato + * e NON al bottone premuto. + */ + +static gint button_press (GtkWidget *widget, GdkEvent *event) +{ + + if (event->type == GDK_BUTTON_PRESS) { + GdkEventButton *bevent = (GdkEventButton *) event; + gtk_menu_popup (GTK_MENU(widget), NULL, NULL, NULL, NULL, + bevent->button, bevent->time); + /* Riferisce al codice chiamante che abbiamo trattato l'evento; + * la faccenda finisce qui. */ + return TRUE; + } + + /* Riferisce al codice chiamante che abbiamo trattato l'evento; passa avanti. */ + return FALSE; +} + + +/* Stampa una stringa quando viene selezionato un elemento di menù */ + +static void menuitem_response (gchar *string) +{ + printf("%s\n", string); +} +</verb></tscreen> + +Si può anche fare in modo che un elemento di menù sia insensibile e, usando +una tabella di acelleratori, collegare dei tasti a delle funzioni di menù. +<p> +<sect1>Usare GtkMenuFactory +<p> +Ora che vi abbiamo mostrato il modo difficile, ecco invece come si fa usando +le chiamate di gtk_menu_factory. +<p> +<sect1>Esempio di Menu Factory +<p> +Ecco un esempio di utilizzo della ``Fabbrica'' di Menù di GTK (Menu Factory). +Questo è il primo file, menus.h. Teniemo dei file menus.c e main.c separati +a causa delle variabili globali usate nel file menus.c. + +<tscreen><verb> +#ifndef __MENUS_H__ +#define __MENUS_H__ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +void get_main_menu (GtkWidget **menubar, GtkAcceleratorTable **table); +void menus_create(GtkMenuEntry *entries, int nmenu_entries); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __MENUS_H__ */ +</verb></tscreen> +<p> +Ed ecco il file menus.c. + +<tscreen><verb> + +#include <gtk/gtk.h> +#include <strings.h> + +#include "main.h" + + +static void menus_remove_accel(GtkWidget * widget, gchar * signal_name, gchar * path); +static gint menus_install_accel(GtkWidget * widget, gchar * signal_name, gchar key, gchar modifiers, gchar * path); +void menus_init(void); +void menus_create(GtkMenuEntry * entries, int nmenu_entries); + +/* Questa è la struttuta GtkMenuEntry, che viene usata per creare dei nuovi + * menù. Il primo membro à la stringa di definizione del menù. Il secondo + * è il tasto acceleratore predefinito, usato per accedere a questa funzione + * con la tastiera. Il terzo è la funzione di ritorno che viene chiamata + * quando si seleziona con la tastiera o il mouse questo elemento di menù. + * L'ultimo membro costituisce il dato che viene passato alla funzione di + * ritorno. */ + +static GtkMenuEntry menu_items[] = +{ + {"<Main>/File/New", "<control>N", NULL, NULL}, + {"<Main>/File/Open", "<control>O", NULL, NULL}, + {"<Main>/File/Save", "<control>S", NULL, NULL}, + {"<Main>/File/Save as", NULL, NULL, NULL}, + {"<Main>/File/<separator>", NULL, NULL, NULL}, + {"<Main>/File/Quit", "<control>Q", file_quit_cmd_callback, "OK, I'll quit"}, + {"<Main>/Options/Test", NULL, NULL, NULL} +}; + +/* calculail numero di menu_item */ +static int nmenu_items = sizeof(menu_items) / sizeof(menu_items[0]); + +static int initialize = TRUE; +static GtkMenuFactory *factory = NULL; +static GtkMenuFactory *subfactory[1]; +static GHashTable *entry_ht = NULL; + +void get_main_menu(GtkWidget ** menubar, GtkAcceleratorTable ** table) +{ + if (initialize) + menus_init(); + + if (menubar) + *menubar = subfactory[0]->widget; + if (table) + *table = subfactory[0]->table; +} + +void menus_init(void) +{ + if (initialize) { + initialize = FALSE; + + factory = gtk_menu_factory_new(GTK_MENU_FACTORY_MENU_BAR); + subfactory[0] = gtk_menu_factory_new(GTK_MENU_FACTORY_MENU_BAR); + + gtk_menu_factory_add_subfactory(factory, subfactory[0], "<Main>"); + menus_create(menu_items, nmenu_items); + } +} + +void menus_create(GtkMenuEntry * entries, int nmenu_entries) +{ + char *accelerator; + int i; + + if (initialize) + menus_init(); + + if (entry_ht) + for (i = 0; i < nmenu_entries; i++) { + accelerator = g_hash_table_lookup(entry_ht, entries[i].path); + if (accelerator) { + if (accelerator[0] == '\0') + entries[i].accelerator = NULL; + else + entries[i].accelerator = accelerator; + } + } + gtk_menu_factory_add_entries(factory, entries, nmenu_entries); + + for (i = 0; i < nmenu_entries; i++) + if (entries[i].widget) { + gtk_signal_connect(GTK_OBJECT(entries[i].widget), "install_accelerator", + (GtkSignalFunc) menus_install_accel, + entries[i].path); + gtk_signal_connect(GTK_OBJECT(entries[i].widget), "remove_accelerator", + (GtkSignalFunc) menus_remove_accel, + entries[i].path); + } +} + +static gint menus_install_accel(GtkWidget * widget, gchar * signal_name, gchar key, gchar modifiers, gchar * path) +{ + char accel[64]; + char *t1, t2[2]; + + accel[0] = '\0'; + if (modifiers & GDK_CONTROL_MASK) + strcat(accel, "<control>"); + if (modifiers & GDK_SHIFT_MASK) + strcat(accel, "<shift>"); + if (modifiers & GDK_MOD1_MASK) + strcat(accel, "<alt>"); + + t2[0] = key; + t2[1] = '\0'; + strcat(accel, t2); + + if (entry_ht) { + t1 = g_hash_table_lookup(entry_ht, path); + g_free(t1); + } else + entry_ht = g_hash_table_new(g_string_hash, g_string_equal); + + g_hash_table_insert(entry_ht, path, g_strdup(accel)); + + return TRUE; +} + +static void menus_remove_accel(GtkWidget * widget, gchar * signal_name, gchar * path) +{ + char *t; + + if (entry_ht) { + t = g_hash_table_lookup(entry_ht, path); + g_free(t); + + g_hash_table_insert(entry_ht, path, g_strdup("")); + } +} + +void menus_set_sensitive(char *path, int sensitive) +{ + GtkMenuPath *menu_path; + + if (initialize) + menus_init(); + + menu_path = gtk_menu_factory_find(factory, path); + if (menu_path) + gtk_widget_set_sensitive(menu_path->widget, sensitive); + else + g_warning("Impossibile assegnare sensibilità a menù inesistente: %s", path); +} + +</verb></tscreen> +<p> +Ed ecco main.h + +<tscreen><verb> +#ifndef __MAIN_H__ +#define __MAIN_H__ + + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +void file_quit_cmd_callback(GtkWidget *widget, gpointer data); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __MAIN_H__ */ + +</verb></tscreen> +<p> +E main.c + +<tscreen><verb> +#include <gtk/gtk.h> + +#include "main.h" +#include "menus.h" + + +int main(int argc, char *argv[]) +{ + GtkWidget *window; + GtkWidget *main_vbox; + GtkWidget *menubar; + + GtkAcceleratorTable *accel; + + gtk_init(&argc, &argv); + + window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_signal_connect(GTK_OBJECT(window), "destroy", + GTK_SIGNAL_FUNC(file_quit_cmd_callback), + "WM destroy"); + gtk_window_set_title(GTK_WINDOW(window), "Menu Factory"); + gtk_widget_set_usize(GTK_WIDGET(window), 300, 200); + + main_vbox = gtk_vbox_new(FALSE, 1); + gtk_container_border_width(GTK_CONTAINER(main_vbox), 1); + gtk_container_add(GTK_CONTAINER(window), main_vbox); + gtk_widget_show(main_vbox); + + get_main_menu(&menubar, &accel); + gtk_window_add_accelerator_table(GTK_WINDOW(window), accel); + gtk_box_pack_start(GTK_BOX(main_vbox), menubar, FALSE, TRUE, 0); + gtk_widget_show(menubar); + + gtk_widget_show(window); + gtk_main(); + + return(0); +} + +/* Questo è per mostrare come si usano le funzioni di ritorno quando + * si utilizza la MenuFactory. Spesso, si mettono tutte le funzioni di + * callback in un file separato, e le si fanno chiamare le funzioni + * appropriate da lì. Così le cose sono più organizzate. */ +void file_quit_cmd_callback (GtkWidget *widget, gpointer data) +{ + g_print ("%s\n", (char *) data); + gtk_exit(0); +} +</verb></tscreen> +<p> +Ed infine un bel makefile per semplificare la compilazione. + +<tscreen><verb> +CC = gcc +PROF = -g +C_FLAGS = -Wall $(PROF) -L/usr/local/include -DDEBUG +L_FLAGS = $(PROF) -L/usr/X11R6/lib -L/usr/local/lib +L_POSTFLAGS = -lgtk -lgdk -lglib -lXext -lX11 -lm +PROGNAME = at + +O_FILES = menus.o main.o + +$(PROGNAME): $(O_FILES) + rm -f $(PROGNAME) + $(CC) $(L_FLAGS) -o $(PROGNAME) $(O_FILES) $(L_POSTFLAGS) + +.c.o: + $(CC) -c $(C_FLAGS) $< + +clean: + rm -f core *.o $(PROGNAME) nohup.out +distclean: clean + rm -f *~ +</verb></tscreen> +<p> +Per il momento, accontentatevi di questo esempio. Più avanti aggiungeremo +una spiegazione ed un bel po' di commenti. + + +<sect> Widget non documentati +<p> +Per questi sarebbe utile il contributo degli autori! :) Prendete in +considerazione la possibilità di contribuire al nostro tutorial. + +Se dovete usare uno di questi widget non documentati, vi suggeriamo +caldamente di dare un'occhiata ai loro rispettivi file header nella +distribuzione di GTK. I nomi delle funzioni di GTK sono molto descrittivi. +Non appena si capisce come funzionano le cose, non è +difficile dedurre il modo d'uso di un widget semplicemente guardando la +dichiarazione di funzione ad esso associata. Aggiungendo a questo qualche +spunto tratto dal codice di altri non dovrebbero esserci problemi. + +Quando avrete raggiunto una comprensione globale di tutte le funzioni +di un widget non documentato, considerate la possibilità di scrivere +un tutorial su di esso, in modo che altri possano beneficiare del +vostro lavoro. + +<sect1> Ingressi di testo (Text Entries) +<p> + +<sect1> Selezioni di colore (Color Selections) +<p> + +<sect1> Controlli di intervallo (Range Controls) +<p> + +<sect1> Righelli (Rulers) +<p> + +<sect1> Caselle di testo (Text Boxes) +<p> + +<sect1> Anteprime +<p> + +(Potrebbe essere necessario riscrivere questa parte per conformarsi allo stile +del resto del tutorial) + +<p> +Le anteprime servono a un certo numero di cose in GIMP/GTK. La più +importante è questa: a risoluzioni molto alte le immagini possono +facilmente occupare diverse decine di megabyte di memoria; ogni operazione +su immagini così grosse può richiedere molto tempo. Se per la +scelta di una data modifica vi occorrono 5-10 tentativi (cioè 10-20 +passi, poiché è necessario ripristinare l'originale se si +è commesso un errore), possono volerci letteralmente delle ore per +fare quella giusta - se non si rimane a corto di memoria prima! Coloro che +hanno passato ore in camera oscura conoscono la sensazione. In questi casi +le anteprime sono utilissime! + +Ma la seccatura dell'attesa non è l'unico caso. Spesso è utile +confrontare la versione precedente con la successiva affiancandole, o almeno +alternandole. Se si sta lavorando con grandi immagini e ritardi di una decina +di secondi un confronto efficace è quantomeno difficile da fare. +Per immagini di 30 mega (4 pollici per 6 pollici, 600 punti per pollice, 24 bit) +tale confronto risulta impraticabile per la maggior parte degli utenti. In +questo caso le anteprime sono di grande aiuto! + +Ma c'è di più. Con le anteprime è possibile scrivere +plug-in per ottenere addirittura anteprime di anteprime (per esempio, la +simulazione del pacchetto di filtri). Questi plug-in possono così +fornire un certo numero di anticipazioni di quel che si otterrebbe applicando +certe opzioni. Un simile approccio funziona come una tavolozza di anteprime, +ed è molto efficace per piccoli cambiamenti! + +Non è finita. Per alcuni plug-in può essere necessario un +intervento umano in tempo reale specifico per ogni immagine. Nel plug-in +SuperNova, ad esempio, vengono chieste le coordinate del centro della +futura supernova. Il modo più semplice per fare questo è +senza dubbio quello di mostrare un'anteprima all'utente chiedendogli di +selezionare interattivamente il centro. + +Infine, un paio di applicazioni tipiche. Le anteprime possono essere usate +anche quando non si sta lavorando con grandi immagini. Per esempio, sono +utili quando si stanno calcolando dei pattern complicati (date un'occhiata +al venerabile plug in ``Diffraction'' e a molti altri!). Altro esempio: +date un'occhiata al plug-in di rotazione della mappa dei colori (in allestimento). +Le anteprime possono anche essere usate per visualizzare in un plug-in +piccoli logo o, addirittura, l'immagine dell'Autore! + +Quando non usare le anteprime + +Le anteprime non vanno usate per grafici, disegni ecc., poiché per +queste cose GDK è molto più veloce. Le anteprime vanno usate +solo per immagini derivate da un'elaborazione! + +Le anteprime possono essere inserite dappertutto. In un vbox, in un hbox, +in una tabella, in un bottone, ecc. Sicuramente però hanno il loro +look migliore se bordate con delle cornici (frame). Le anteprime non hanno +bordi propri e appaiono piatte senza (naturalmente, se quel che si vuole +è proprio un aspetto piatto...). I bordi possono essere creati con +delle cornici. + + [Image][Image] + +Le anteprime sono per molti aspetti simili agli altri widget in GTK (con +tutto ciò che questo implica), con l'eccezione di avere una +caratteristica in più: è necessario che siano riempite con +qualche tipo di immagine! Inizialmente parleremo solo dell'aspetto GTK +delle anteprime e successivamente discuteremo di come riempirle. + +Semplicemente: + +<tscreen><verb> + /* Crea un widget di anteprima, + inizializzane le dimensioni + e visualizzalo */ +GtkWidget *preview; +preview=gtk_preview_new(GTK_PREVIEW_COLOR) + /* Alternativamente: + GTK_PREVIEW_GRAYSCALE);*/ +gtk_preview_size (GTK_PREVIEW (preview), WIDTH, HEIGHT); +gtk_widget_show(preview); +my_preview_rendering_function(preview); +</verb></tscreen> + +Come già detto, le anteprime hanno un buon aspetto dentro le cornici, +quindi: + +<tscreen><verb> +GtkWidget *create_a_preview(int Width, + int Height, + int Colorfulness) +{ + GtkWidget *preview; + GtkWidget *frame; + + frame = gtk_frame_new(NULL); + gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN); + gtk_container_border_width (GTK_CONTAINER(frame),0); + gtk_widget_show(frame); + + preview=gtk_preview_new (Colorfulness?GTK_PREVIEW_COLOR + :GTK_PREVIEW_GRAYSCALE); + gtk_preview_size (GTK_PREVIEW (preview), Width, Height); + gtk_container_add(GTK_CONTAINER(frame),preview); + gtk_widget_show(preview); + + my_preview_rendering_function(preview); + return frame; +} + +</verb></tscreen> + +Questa è una semplice anteprima. Questa funzione restituisce la cornice +``madre'', in modo che sia possibile metterla in qualche altro posto nella vostra +interfaccia. Naturalmente è possibile passare alla routine la cornice +madre come parametro. In molte situazioni, comunque, il contenuto di un'anteprima +viene aggiornato continuamente dall'applicazione; in questi casi potreste +preferire passare alla funzione ``create_a_preview()'' un puntatore +all'anteprima, ottenendone così il controllo dopo. + +Un'avvertimento più importante che potrebbe un giorno risparmiarvi +tanto tempo perso: a volte è preferibile etichettare le anteprime; +ad esempio, è possibile etichettare l'anteprima contenente l'immagine +originale come ``Originale'' e quella contenente l'immagine modificata come +``Modificata''. Potrebbe capitarvi di impacchettare in un vbox l'anteprima +insieme con l'etichetta associata. L'insidia inattesa sta nel fatto che se +l'etichetta è più ampia dell'anteprima (cosa che può +accadere per una varietà di motivi da voi non prevedibili, come il +fatto che la dimensione dell'anteprima viene decisa dinamicamente, o la +dimensione del font), la cornice si espande e non risulta più +perfettamente aderente all'anteprima. Questo stesso problema probabilmente +può verificarsi anche in altre situazioni. + + [Image] + +La soluzione è quella di mettere l'anteprima e l'etichetta in una +tabella 2x1 e di legarle insieme chiamando la funzione gtk_table_attach con +i seguenti parametri (questa è una delle varianti possibili, +naturalmente; l'importante è che non ci sia GTK_FILL nella seconda +gtk_table_attach): + +<tscreen><verb> +gtk_table_attach(GTK_TABLE(table),label,0,1,0,1, + 0, + GTK_EXPAND|GTK_FILL, + 0,0); +gtk_table_attach(GTK_TABLE(table),frame,0,1,1,2, + GTK_EXPAND, + GTK_EXPAND, + 0,0); +</verb></tscreen> + +Ed ecco il risultato: + + [Image] + +Altri suggerimenti + +La maniera più semplice per rendere cliccabile un'anteprima è +quella di metterla dentro un bottone. Questo ha anche l'effetto di aggiungere +un bel bordo attorno all'anteprima, il che rende superfluo metterla in una +cornice. + +Questo è tutto per quel che riguarda GTK. + + +Completare un'anteprima + +Per impratichirci con le basi del completamento delle anteprime, creiamo +il seguente disegno (trovato per tentativi): + + [Image] + +<tscreen><verb> +void +my_preview_rendering_function(GtkWidget *preview) +{ +#define SIZE 100 +#define HALF (SIZE/2) + + guchar *row=(guchar *) malloc(3*SIZE); /* 3 bits per dot */ + gint i, j; /* Coordinates */ + double r, alpha, x, y; + + if (preview==NULL) return; /* Di solito aggiungo questo per */ + /* evitare piantamenti stupidi. */ + /* Probabilmente bisognerebbe */ + /* assicurarsi che tutto sia stato*/ + /* inizializzato con successo */ + for (j=0; j < ABS(cos(2*alpha)) ) { /* Siamo dentro la sagoma? */ + /* glib.h contiene ABS(x). */ + row[i*3+0] = sqrt(1-r)*255; /* Definisce il Rosso */ + row[i*3+1] = 128; /* Definisce il Verde */ + row[i*3+2] = 224; /* Definisce il Blu */ + } /* "+0" è per allineamento */ + else { + row[i*3+0] = r*255; + row[i*3+1] = ABS(sin((float)i/SIZE*2*PI))*255; + row[i*3+2] = ABS(sin((float)j/SIZE*2*PI))*255; + } + } + gtk_preview_draw_row( GTK_PREVIEW(preview),row,0,j,SIZE); + /* Inserisce "row" in "preview" a partire del punto avente */ + /* coordinate (0,j) prima colonna, j-esima riga, per SIZE */ + /* pixel verso destra */ + } + + free(row); /* libera un po' di memoria */ + gtk_widget_draw(preview,NULL); /* indovina cosa fa questo? */ + gdk_flush(); /* e questo? */ +} +</verb></tscreen> +Coloro che non usano GIMP probabilmente hanno già visto abbastanza +per fare molte cose. Per gli utenti GIMP c'è ancora qualcosa da +aggiungere. + +Anteprima dell'immagine + +Probabilmente è opportuno tenere pronta una versione ridotta dell'immagine, +grande quanto basta per riempire l'anteprima. Questo può essere fatto +selezionando un pixel ogni n, dove n è il rapporto tra la dimensione +dell'immagine e la dimensione dell'anteprima. Tutte le operazioni successive +(compreso il riempimento dell'anteprima) sono fatte solo sul ridotto numero +di pixel selezionati. Di seguito è riportata un'implementazione della +riduzione dell'immagine (si tenga presente che ho preso solo lezioni basilari +di C!). + + +(ATTENZIONE: CODICE NON VERIFICATO!!!) + +<tscreen><verb> + +typedef struct { + gint width; + gint height; + gint bbp; + guchar *rgb; + guchar *mask; +} ReducedImage; + +enum { + SELECTION_ONLY, + SELCTION_IN_CONTEXT, + ENTIRE_IMAGE +}; + +ReducedImage *Reduce_The_Image(GDrawable *drawable, + GDrawable *mask, + gint LongerSize, + gint Selection) +{ + /* Questa funzione riduce l'immagine alla dimens. scelta per l'anteprima */ + /* La dimensione dell'anteprima è determinata da LongerSize, cioè la più */ + /* grande delle dimensioni. Funziona solo per immagini RGB! */ + gint RH, RW; /* Altezza ridotta e larghezza ridotta */ + gint width, height; /* Larghezza e altezza dell'area da ridurre */ + gint bytes=drawable->bpp; + ReducedImage *temp=(ReducedImage *)malloc(sizeof(ReducedImage)); + + guchar *tempRGB, *src_row, *tempmask, *src_mask_row,R,G,B; + gint i, j, whichcol, whichrow, x1, x2, y1, y2; + GPixelRgn srcPR, srcMask; + gint NoSelectionMade=TRUE; /* Assumiamo di trattare l'intera immagine */ + + gimp_drawable_mask_bounds (drawable->id, &x1, &y1, &x2, &y2); + width = x2-x1; + height = y2-y1; + /* Se c'è una SELEZIONE, ne abbiamo avuto gli estremi! */ + + if (width != drawable->width && height != drawable->height) + NoSelectionMade=FALSE; + /* Controlliamo se l'utente ha una selezione attiva. Questo */ + /* diventerà importante dopo, alla creazione di una maschera ridotta */ + + /* Se si vuole l'anteprima dell'immagine intera, annulla quanto sopra */ + /* Naturalmente, in assenza di una selezione, questo non cambia nulla */ + if (Selection==ENTIRE_IMAGE) { + x1=0; + x2=drawable->width; + y1=0; + y2=drawable->height; + } + + /* Se si vuole l'anteprima di una selezione con parte dell'area */ + /* circostante bisogna espanderla un po'. */ + if (Selection==SELECTION_IN_CONTEXT) { + x1=MAX(0, x1-width/2.0); + x2=MIN(drawable->width, x2+width/2.0); + y1=MAX(0, y1-height/2.0); + y2=MIN(drawable->height, y2+height/2.0); + } + + /* Così si determinano larghezza e altezza dell'area da ridurre. */ + width = x2-x1; + height = y2-y1; + + /* Le linee seguenti determinano quale dimensione deve essere il */ + /* lato più lungo. L'idea è presa dal plug-in supernova. Ritengo */ + /* che avrei potuto pensarci da solo, ma la verità va detta. */ + /* Brutta cosa il plagio! */ + if (width>height) { + RW=LongerSize; + RH=(float) height * (float) LongerSize/ (float) width; + } + else { + RH=LongerSize; + RW=(float)width * (float) LongerSize/ (float) height; + } + + /* L'intera immagine viene "stirata" in una stringa! */ + tempRGB = (guchar *) malloc(RW*RH*bytes); + tempmask = (guchar *) malloc(RW*RH); + + gimp_pixel_rgn_init (&srcPR, drawable, x1, y1, width, height, FALSE, FALSE); + gimp_pixel_rgn_init (&srcMask, mask, x1, y1, width, height, FALSE, FALSE); + + /* Prendine abbastanza da contenere una riga di immagine e una di maschera */ + src_row = (guchar *) malloc (width*bytes); + src_mask_row = (guchar *) malloc (width); + + for (i=0; i < RH; i++) { + whichrow=(float)i*(float)height/(float)RH; + gimp_pixel_rgn_get_row (&srcPR, src_row, x1, y1+whichrow, width); + gimp_pixel_rgn_get_row (&srcMask, src_mask_row, x1, y1+whichrow, width); + + for (j=0; j < RW; j++) { + whichcol=(float)j*(float)width/(float)RW; + + /* Nessuna selezione = tutti i punti sono completamente selezionati */ + if (NoSelectionMade) + tempmask[i*RW+j]=255; + else + tempmask[i*RW+j]=src_mask_row[whichcol]; + + /* Aggiungi la riga alla lunga stringa che ora contiene l'immagine */ + tempRGB[i*RW*bytes+j*bytes+0]=src_row[whichcol*bytes+0]; + tempRGB[i*RW*bytes+j*bytes+1]=src_row[whichcol*bytes+1]; + tempRGB[i*RW*bytes+j*bytes+2]=src_row[whichcol*bytes+2]; + + /* Mantieni anche la trasparenza (alpha) */ + if (bytes==4) + tempRGB[i*RW*bytes+j*bytes+3]=src_row[whichcol*bytes+3]; + } + } + temp->bpp=bytes; + temp->width=RW; + temp->height=RH; + temp->rgb=tempRGB; + temp->mask=tempmask; + return temp; +} +</verb></tscreen> + + +La seguente è una funzione di anteprima che usa lo stesso tipo +ReducedImage! Si noti che usa una finta trasparenza - se ne è presente +una, tramite fake_transparency che è definita come segue: + +<tscreen><verb> +gint fake_transparency(gint i, gint j) +{ + if ( ((i%20)- 10) * ((j%20)- 10)>0 ) + return 64; + else + return 196; +} + +</verb></tscreen> +E adesso la funzione per l'anteprima: +<tscreen><verb> +void +my_preview_render_function(GtkWidget *preview, + gint changewhat, + gint changewhich) +{ + gint Inten, bytes=drawable->bpp; + gint i, j, k; + float partial; + gint RW=reduced->width; + gint RH=reduced->height; + guchar *row=malloc(bytes*RW);; + + + for (i=0; i < RH; i++) { + for (j=0; j < RW; j++) { + + row[j*3+0] = reduced->rgb[i*RW*bytes + j*bytes + 0]; + row[j*3+1] = reduced->rgb[i*RW*bytes + j*bytes + 1]; + row[j*3+2] = reduced->rgb[i*RW*bytes + j*bytes + 2]; + + if (bytes==4) + for (k=0; k<3; k++) { + float transp=reduced->rgb[i*RW*bytes+j*bytes+3]/255.0; + row[3*j+k]=transp*a[3*j+k]+(1-transp)*fake_transparency(i,j); + } + } + gtk_preview_draw_row( GTK_PREVIEW(preview),row,0,i,RW); + } + + free(a); + gtk_widget_draw(preview,NULL); + gdk_flush(); +} + +Funzioni Applicabili + +guint gtk_preview_get_type (void); +/* No idea */ +void gtk_preview_uninit (void); +/* No idea */ +GtkWidget* gtk_preview_new (GtkPreviewType type); +/* Descritta precedentemente */ +void gtk_preview_size (GtkPreview *preview, + gint width, + gint height); +/* Permette di ridimensionare un'anteprima esistente */ +/* Pare che un bug in GTK renda disordinato questo */ +/* processo. Un modo di rimettere le cose a posto */ +/* è quello di ridimensionare manualmente */ +/* la finestra contenente l'anteprima dopo aver */ +/* ridimensionato l'anteprima. */ + +void gtk_preview_put (GtkPreview *preview, + GdkWindow *window, + GdkGC *gc, + gint srcx, + gint srcy, + gint destx, + gint desty, + gint width, + gint height); +/* No idea */ + +void gtk_preview_put_row (GtkPreview *preview, + guchar *src, + guchar *dest, + gint x, + gint y, + gint w); +/* No idea */ + +void gtk_preview_draw_row (GtkPreview *preview, + guchar *data, + gint x, + gint y, + gint w); +/* Descritta nel testo */ + +void gtk_preview_set_expand (GtkPreview *preview, + gint expand); +/* No idea */ + +/* Nessun indizio per le seguenti, ma dovrebbero */ +/* essere standard per la maggior parte dei widget */ +void gtk_preview_set_gamma (double gamma); +void gtk_preview_set_color_cube (guint nred_shades, + guint ngreen_shades, + guint nblue_shades, + guint ngray_shades); +void gtk_preview_set_install_cmap (gint install_cmap); +void gtk_preview_set_reserved (gint nreserved); +GdkVisual* gtk_preview_get_visual (void); +GdkColormap* gtk_preview_get_cmap (void); +GtkPreviewInfo* gtk_preview_get_info (void); + +E' tutto! + +</verb></tscreen> + + +<sect1> Curve +<p> + + +<sect>Il Widget EventBox<label id="sec_The_EventBox_Widget"> +<p> +E' disponibile solo a partire dalla distribuzione gtk+970916.tar.gz. +<p> +Alcuni widget gtk non sono associati a finestre X, sicché +semplicemente disegnano sui loro genitori. Per questo motivo essi non possono +ricevere eventi e se sono sovradimensionati non vengono troncati, ma rischiano +di sovrapporsi, generando confusione. Se si vuole di più da questi +widget si può ricorrere agli EventBox. + +A prima vista il widget EventBox potrebbe sembrare completamente inutile. Non +disegna nulla sullo schermo e non risponde a nessun evento. Tuttavia ha +una funzione: fornire una finestra X al suo widget figlio. Ciò +è importante in quanto molti widget GTK non hanno una finestra X +associata. Se questo da una parte risparmia memoria e migliora le prestazioni, +dall'altra introduce degli svantaggi: un widget senza una finestra X non +può ricevere eventi, e non taglia in alcun modo il suo contenuto. +Sebbene il nome ``EventBox'' (casella di eventi) enfasizzi la funzione di +gestione degli eventi, il widget può essere usato anche per +limitare la dimensione dei widget figli (ma anche per altro: si veda +l'esempio seguente). + +<p> +Per creare un widget di tipo EventBox: + +<tscreen><verb> +GtkWidget* gtk_event_box_new (void); +</verb></tscreen> + +<p> +All'EventBox si può aggiungere un widget figlio: + +<tscreen><verb> +gtk_container_add (GTK_CONTAINER(event_box), widget); +</verb></tscreen> + +<p> +The following example demonstrates both uses of an EventBox - a label +is created that clipped to a small box, and set up so that a +mouse-click on the label causes the program to exit. +Il seguente esempio mostra entrambi gli usi di un EventBox - si crea +un'etichetta limitata da un rettangolo piccolo, fatta in modo che +cliccando con il mouse su di essa il programma termina. + +<tscreen><verb> +#include <gtk/gtk.h> + +int +main (int argc, char *argv[]) +{ + GtkWidget *window; + GtkWidget *event_box; + GtkWidget *label; + + gtk_init (&argc, &argv); + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + + gtk_window_set_title (GTK_WINDOW (window), "Event Box"); + + gtk_signal_connect (GTK_OBJECT (window), "destroy", + GTK_SIGNAL_FUNC (gtk_exit), NULL); + + gtk_container_border_width (GTK_CONTAINER (window), 10); + + /* Crea un EventBox e lo aggiunge alla finestra principale */ + + event_box = gtk_event_box_new (); + gtk_container_add (GTK_CONTAINER(window), event_box); + gtk_widget_show (event_box); + + /* Crea una etichetta lunga */ + + label = gtk_label_new ("Click here to quit, quit, quit, quit, quit"); + gtk_container_add (GTK_CONTAINER (event_box), label); + gtk_widget_show (label); + + /* Limitane le dimensioni */ + gtk_widget_set_usize (label, 110, 20); + + /* E collega ad essa una azione */ + gtk_widget_set_events (event_box, GDK_BUTTON_PRESS_MASK); + gtk_signal_connect (GTK_OBJECT(event_box), "button_press_event", + GTK_SIGNAL_FUNC (gtk_exit), NULL); + + /* Un'altra cosa per cui si ha bisogno di una finestra X ... */ + + gtk_widget_realize (event_box); + gdk_window_set_cursor (event_box->window, gdk_cursor_new (GDK_HAND1)); + + gtk_widget_show (window); + + gtk_main (); + + return 0; +} +</verb></tscreen> + +<sect>Selezionare gli Attributi dei Widget<label id="sec_setting_widget_attributes"> +<p> +Qui si descrivono le funzioni per la gestione dei widget. Esse possono essere +usate per impostarne lo stile, il padding, le dimensioni, ... + +(Forse andrebbe fatta un'intera sezione sugli acceleratori). + +<tscreen><verb> +void gtk_widget_install_accelerator (GtkWidget *widget, + GtkAcceleratorTable *table, + gchar *signal_name, + gchar key, + guint8 modifiers); + +void gtk_widget_remove_accelerator (GtkWidget *widget, + GtkAcceleratorTable *table, + gchar *signal_name); + +void gtk_widget_activate (GtkWidget *widget); + +void gtk_widget_set_name (GtkWidget *widget, + gchar *name); +gchar* gtk_widget_get_name (GtkWidget *widget); + +void gtk_widget_set_sensitive (GtkWidget *widget, + gint sensitive); + +void gtk_widget_set_style (GtkWidget *widget, + GtkStyle *style); + +GtkStyle* gtk_widget_get_style (GtkWidget *widget); + +GtkStyle* gtk_widget_get_default_style (void); + +void gtk_widget_set_uposition (GtkWidget *widget, + gint x, + gint y); +void gtk_widget_set_usize (GtkWidget *widget, + gint width, + gint height); + +void gtk_widget_grab_focus (GtkWidget *widget); + +void gtk_widget_show (GtkWidget *widget); + +void gtk_widget_hide (GtkWidget *widget); +</verb></tscreen> + + + +<sect>Funzioni periodiche, di I/O e di attesa<label id="sec_timeouts"> +<p> +<sect1>Funzioni periodiche +<p> +Probabilmente vi sarete chiesti come far fare qualcosa di utile a GTK +durante la chiamata alla gtk_main(). Ci sono diverse possibilità. +Usando le seguenti funzioni si possono creare funzioni che vengono chiamate +periodicamente. + +<tscreen><verb> +gint gtk_timeout_add (guint32 interval, + GtkFunction function, + gpointer data); +</verb></tscreen> + +Il primo argomento è il numero di millisecondi tra le chiamate alla +funzione. Il secondo è la funzione periodica, mentre il terzo +rappresenta i dati che vengono passati alla funzione. Il valore restituito +è un'etichetta che può essere utilizzata per fermare la chiamata +periodica, passandolo alla funzione: + +<tscreen><verb> +void gtk_timeout_remove (gint tag); +</verb></tscreen> + +La chiamata periodica si ferma anche se la funzione periodica ritorna zero +o FALSE. Naturalmente questo vuol dire che se si vuole che la funzione periodica +continui ad essere richiamata, essa deve restituire un valore non nullo, +cioè TRUE. + +La dichiarazione della funzione periodica dovrebbe essere come questa: + +<tscreen><verb> +gint timeout_callback (gpointer data); +</verb></tscreen> + +<sect1>Controllo dell'I/O +<p> +Un'altra utile caratteristica di GTK è la possibilità di fargli +controllare che siano verificate certe condizioni su un descrittore di file +(come quelli restituiti da open(2) o socket(2)). Questo è utile in +particolar modo per le applicazioni di rete. La funzione è la seguente: + +<tscreen><verb> +gint gdk_input_add (gint source, + GdkInputCondition condition, + GdkInputFunction function, + gpointer data); +</verb></tscreen> + +Il primo argomento è il descrittore che si desidera venga controllato, +mentre il secondo specifica quale condizione si vuole che GDK controlli. +Questa può essere una tra: +<p> +GDK_INPUT_READ - Chiama la funzione quando ci sono dati pronti per la lettura +nel descrittore di file. +<p> +GDK_INPUT_WRITE - Chiama la funzione quando il descrittore di file è +pronto per la scrittura. +<p> +Come sicuramente avrete già intuito, il terzo parametro è la +funzione da chiamare quando la condizione specificata è soddisfatta, +mentre il quarto rappresenta i dati da passare a questa funzione. +<p> +Il valore di ritorno è un etichetta che può essere usata per +fermare il controllo di GDK sul descrittore di file, usando la seguente +funzione: +<p> +<tscreen><verb> +void gdk_input_remove (gint tag); +</verb></tscreen> +<p> +La funzione da richiamare va dichiarata così: +<p> +<tscreen><verb> +void input_callback (gpointer data, gint source, + GdkInputCondition condition); +</verb></tscreen> +<p> + +<sect1>Funzioni di attesa (``Idle'') +<p> +Cosa fare se si ha una funzione che si vuole venga chiamata quando non +sta accadendo nient'altro? + +<tscreen><verb> +gint gtk_idle_add (GtkFunction function, + gpointer data); +</verb></tscreen> + +Questa fa si che GDK chiami la funzione specificata quando non c'è +nessuna altra operazione in corso. + +<tscreen><verb> +void gtk_idle_remove (gint tag); +</verb></tscreen> +<p> +Non ci soffermeremo sul significato dei parametri in quanto del tutto analoghi +ai precedenti. La funzione puntata dal primo argomento della gtk_idle_add +viene chiamata non appena se ne presenta l'opportunità; come +negli altri casi, se essa restituisce FALSE non viene più chiamata. + + +<sect>La gestione delle selezioni + +<sect1> Overview + +<p> + +Le <em>selezioni</em> sono un tipo di comunicazione tra processi +supportato da GTK. Una selezione identifica un frammento di dati; per +esempio, una porzione di testo selezionata dall'utente in qualche modo, +magari con il mouse. Su un display solo un'applicazione alla volta +(il <em>proprietario</em>) puó essere proprietaria di una +particolare selezione, sicché quando un'applicazione richiede +una selezione il precedente proprietario deve comunicare all'utente che +la selezione è stata ceduta. Altre applicazioni possono richiedere +il contenuto di una selezione in diverse forme, chiamate <em>obiettivi</em>. +Ci può essere un numero qualsiasi di selezioni, ma la maggior parte +delle applicazioni X può gestirne solo una, la <em>selezione +primaria</em>. + +<p> +Nella maggior parte dei casi per una applicazione GTK non è +necessario gestire esplicitamente le selezioni. I widget standard, +come quello di Ingresso, hanno già la capacità di +chiedere la selezione se necessario (p. e., quando l'utente +seleziona sul testo), e di recuperare il contenuto di una selezione +di un altro widget o di un'altra applicazione (p. e., quando l'utente +clicca il tasto centrale del mouse). Ci possono comunque essere dei +casi nei quali si vuole dare ad altri widget la capacità di +fornire la selezione, o si vogliono recuperare degli obiettivi non +supportati direttamente. + +<p> +Un concetto fondamentale necessario per comprendere la gestione delle +selezioni è quello di <em>atomo</em>. Un atomo è un intero +che identifica univocamente una stringa (su un certo display). +Certi atomi sono predefiniti dal server X, e in alcuni casi in <tt>gtk.h</tt> +ci sono costanti corrispondenti a questi atomi. Per esempio, la costante +<tt>GDK_PRIMARY_SELECTION</tt> corrisponde alla stringa ``PRIMARY''. +Negli altri casi bisogna usare le funzioni <tt>gdk_atom_intern()</tt> +per ottenere l'atomo corrispondente ad una stringa, e <tt>gdk_atom_name()</tt> +per ottenere il nome di un atomo. Sia le selezioni sia gli obiettivi sono +identificati da atomi. + +<sect1> Recuperare le selezioni + +<p> + +Il recupero di una selezione è un processo asincrono. Per iniziare +il processo, si chiama: +<tscreen><verb> +gint gtk_selection_convert (GtkWidget *widget, + GdkAtom selection, + GdkAtom target, + guint32 time) +</verb</tscreen> + +Questo <em>converte</em> la selezione nella forma specificata +dall'obiettivo <tt/target/. Se possibile, il campo <tt/time/ +dovrebbe essere il tempo dell'evento che ha attivato la selezione. +Questo aiuta a far si che gli eventi avvengano nell'ordine in cui +l'utente li ha richiesti. Se comunque non fosse disponibile (per +esempio, se la conversione è stata attivata da un segnale di +``cliccato''), allora si può usare la costante +<tt>GDK_CURRENT_TIME</tt>. + +<p> +Quando il proprietario di una selezione risponde ad una richiesta, +un segnale ``selection_received'' (selezione ricevuta) viene inviato +alla vostra applicazione. Il gestore di questo segnale riceve un +puntatore ad una struttura <tt>GtkSelectionData</tt>, che è +definita nel modo seguente: +<tscreen><verb> +struct _GtkSelectionData +{ + GdkAtom selection; + GdkAtom target; + GdkAtom type; + gint format; + guchar *data; + gint length; +}; +</verb></tscreen> +<tt>selection</tt> e <tt>target</tt> sono i valori da voi specificati +nella chiamata <tt>gtk_selection_convert()</tt>. <tt>type</tt> è +un atomo che identifica il tipo di dati restituiti dal proprietario della +selezione. Alcuni valori possibili sono ``STRING'', una stringa di +caratteri latin-1, ``ATOM'', una serie di atomi, ``INTEGER'', un intero, ecc. +La maggior parte degli obiettivi può restituire solo un tipo. +<tt/format/ ci dà la lunghezza delle unità (per esempio caratteri) +in bit. Di solito, quando si ricevono i dati non ci si cura di questo. +<tt>data</tt> è un puntatore ai dati restituiti, e <tt>length</tt> +è la lunghezza dei dati restituiti, in byte. Se <tt>length</tt> +è negativo allora si è verificato un errore e non è +stato possibile recuperare la selezione. Questo può avvenire se +nessuna applicazione era proprietaria della selezione, o se si è +richiesto un obiettivo non supportato dall'applicazione. Viene garantito +che il buffer sia un byte più lungo di <tt>length</tt>; il byte +in più sarà sempre zero, in modo che non sia necessario +ricopiare le stringhe solo per farle terminare con zero. + +<p> +Nell'esempio che segue viene recuperato l'obiettivo speciale ``TARGETS'', +che è una lista di tutti gli obiettivi in cui può essere +convertita la selezione. +<tscreen><verb> +#include <gtk/gtk.h> + +void selection_received (GtkWidget *widget, + GtkSelectionData *selection_data, + gpointer data); + +/* Gestore di segnale chiamato quando l'utente clicca nel bottone */ +/* "Get Targets" */ +void +get_targets (GtkWidget *widget, gpointer data) +{ + static GdkAtom targets_atom = GDK_NONE; + + /* Prende l'atomo corrispondente alla stringa "TARGETS" */ + if (targets_atom == GDK_NONE) + targets_atom = gdk_atom_intern ("TARGETS", FALSE); + + /* E richiede l'obiettivo "TARGETS" per la selezione primaria */ + gtk_selection_convert (widget, GDK_SELECTION_PRIMARY, targets_atom, + GDK_CURRENT_TIME); +} + +/* Gestore di segnale chiamato quando il proprietario della selezione */ +/* restituisce i dati */ +void +selection_received (GtkWidget *widget, GtkSelectionData *selection_data, + gpointer data) +{ + GdkAtom *atoms; + GList *item_list; + int i; + + /* **** IMPORTANTE **** Controlla che il recupero sia riuscito */ + if (selection_data->length < 0) + { + g_print ("Selection retrieval failed\n"); + return; + } + /* Make sure we got the data in the expected form */ + if (selection_data->type != GDK_SELECTION_TYPE_ATOM) + { + g_print ("Selection \"TARGETS\" was not returned as atoms!\n"); + return; + } + + /* Stampa gli atomi ricevuti */ + atoms = (GdkAtom *)selection_data->data; + + item_list = NULL; + for (i=0; i<selection_data->length/sizeof(GdkAtom); i++) + { + char *name; + name = gdk_atom_name (atoms[i]); + if (name != NULL) + g_print ("%s\n",name); + else + g_print ("(bad atom)\n"); + } + + return; +} + +int +main (int argc, char *argv[]) +{ + GtkWidget *window; + GtkWidget *button; + + gtk_init (&argc, &argv); + + /* Create the toplevel window */ + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_window_set_title (GTK_WINDOW (window), "Event Box"); + gtk_container_border_width (GTK_CONTAINER (window), 10); + + gtk_signal_connect (GTK_OBJECT (window), "destroy", + GTK_SIGNAL_FUNC (gtk_exit), NULL); + + /* Crea un bottone che l'utente può cliccare per ottenere gli obiettivi */ + + button = gtk_button_new_with_label ("Get Targets"); + gtk_container_add (GTK_CONTAINER (window), button); + + gtk_signal_connect (GTK_OBJECT(button), "clicked", + GTK_SIGNAL_FUNC (get_targets), NULL); + gtk_signal_connect (GTK_OBJECT(button), "selection_received", + GTK_SIGNAL_FUNC (selection_received), NULL); + + gtk_widget_show (button); + gtk_widget_show (window); + + gtk_main (); + + return 0; +} +</verb></tscreen> + +<sect1> Fornire una selezione + +<p> +Fornire la selezione è un po' più complicato. Bisogna +registrare i gestori che verranno chiamati quando viene richiesta la +propria selezione. Per ogni coppia selezione/obiettivo che si gestirà +occorre una chiamata a: + +<tscreen><verb> +void gtk_selection_add_handler (GtkWidget *widget, + GdkAtom selection, + GdkAtom target, + GtkSelectionFunction function, + GtkRemoveFunction remove_func, + gpointer data); +</verb></tscreen> + +<tt/widget/, <tt/selection/, e <tt/target/ identificano le richieste +che questo gestore soddisferà. <tt/remove_func/, se non è +NULL, verrà chiamato quando il gestore di segnale viene rimosso. +Questo è utile, per esempio, per linguaggi interpretati ai quali +serve di tener traccia di un conteggio di riferimento per <tt/data/. + +<p> +La funzione di richiamo ha la forma: + +<tscreen><verb> +typedef void (*GtkSelectionFunction) (GtkWidget *widget, + GtkSelectionData *selection_data, + gpointer data); + +</verb></tscreen> + +La GtkSelectionData è la stessa di prima, ma stavolta siamo +responsabili di riempire i campi <tt/type/, <tt/format/, <tt/data/, +e <tt/length/. (Il campo <tt/format/ qui è effettivamente +importante - il server X lo usa per capire se occorre che i byte +dei dati vengano scambiati o no. Di solito sarà 8 - cioè +un carattere - o 32 - cioè un intero.) Questo viene fatto +chiamando la funzione: + +<tscreen><verb> +void gtk_selection_data_set (GtkSelectionData *selection_data, + GdkAtom type, + gint format, + guchar *data, + gint length); +</verb></tscreen> +Questa funzione si prende cura di fare propriamente una copia dei dati +in modo che non ci si debba preoccupare di conservarli (è opportuno +evitare di riempire a mano i campi della struttura GtkSelectionData). + +<p> +Quando richiesto dall'utente, richiederete la proprietà della selezione +chiamando: + +<tscreen><verb> +gint gtk_selection_owner_set (GtkWidget *widget, + GdkAtom selection, + guint32 time); +</verb></tscreen> + +Se un'altra applicazione richiede la proprietà della selezione, +riceverete un evento di azzeramento della selezione (``selection_clear_event''). + +Come esempio di fornitura della selezione, il programma seguente aggiunge +la funzionalità di selezione a un bottone di attivazione. Quando il +bottone viene premuto, il programma richiede la selezione primaria. +L'unico obiettivo supportato (oltre a certi obiettivi come ``TARGETS'' +fornito dalla stessa GTK) è l'obiettivo ``STRING''. Quando viene +richiesto questo obiettivo, viene restituita una rappresentazione stringa +del tempo. + +<tscreen><verb> +#include <gtk/gtk.h> +#include <time.h> + +/* Richiamata quando l'utente attiva la selezione */ +void +selection_toggled (GtkWidget *widget, gint *have_selection) +{ + if (GTK_TOGGLE_BUTTON(widget)->active) + { + *have_selection = gtk_selection_owner_set (widget, + GDK_SELECTION_PRIMARY, + GDK_CURRENT_TIME); + /* se il richiamo della selezione è fallito, si riporta il + bottone nello stato non premuto */ + if (!*have_selection) + gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON(widget), FALSE); + } + else + { + if (*have_selection) + { + /* Prima di annullare la selezione mettendone a NULL il proprietario, + controlliamo se siamo i veri proprietari */ + if (gdk_selection_owner_get (GDK_SELECTION_PRIMARY) == widget->window) + gtk_selection_owner_set (NULL, GDK_SELECTION_PRIMARY, + GDK_CURRENT_TIME); + *have_selection = FALSE; + } + } +} + +/* Chiamata quando un'altra applicazione richiede la selezione */ +gint +selection_clear (GtkWidget *widget, GdkEventSelection *event, + gint *have_selection) +{ + *have_selection = FALSE; + gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON(widget), FALSE); + + return TRUE; +} + +/* Fornisce come selezione il tempo attuale */ +void +selection_handle (GtkWidget *widget, + GtkSelectionData *selection_data, + gpointer data) +{ + gchar *timestr; + time_t current_time; + + current_time = time (NULL); + timestr = asctime (localtime(&current_time)); + /* Quando si restituisce una singola stringa, non occorre che finisca + con NULL. Questo verrà fatto automaticamente */ + + gtk_selection_data_set (selection_data, GDK_SELECTION_TYPE_STRING, + 8, timestr, strlen(timestr)); +} + +int +main (int argc, char *argv[]) +{ + GtkWidget *window; + + GtkWidget *selection_button; + + static int have_selection = FALSE; + + gtk_init (&argc, &argv); + + /* Crea la finestra di livello superiore */ + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_window_set_title (GTK_WINDOW (window), "Event Box"); + gtk_container_border_width (GTK_CONTAINER (window), 10); + + gtk_signal_connect (GTK_OBJECT (window), "destroy", + GTK_SIGNAL_FUNC (gtk_exit), NULL); + + /* Crea un bottone a commutazione che agisce come la selezione */ + + selection_button = gtk_toggle_button_new_with_label ("Claim Selection"); + gtk_container_add (GTK_CONTAINER (window), selection_button); + gtk_widget_show (selection_button); + + gtk_signal_connect (GTK_OBJECT(selection_button), "toggled", + GTK_SIGNAL_FUNC (selection_toggled), &have_selection); + gtk_signal_connect (GTK_OBJECT(selection_button), "selection_clear_event", + GTK_SIGNAL_FUNC (selection_clear), &have_selection); + + gtk_selection_add_handler (selection_button, GDK_SELECTION_PRIMARY, + GDK_SELECTION_TYPE_STRING, + selection_handle, NULL, NULL); + + gtk_widget_show (selection_button); + gtk_widget_show (window); + + gtk_main (); + + return 0; +} +</verb></tscreen> + + + +<sect>La glib<label id="sec_glib"> +<p> +La glib fornisce molte funzioni e definizioni utili pronte all'uso quando si +creano applicazioni GDK e GTK. Qui verranno elencate tutte, con una +breve spiegazione. Molte sono duplicati delle funzioni standard della libc, +e quindi per queste non si scenderà nei dettagli. Questa vuole essere una +lista di riferimento, in modo che si sappia cosa è possibile usare. + +<sect1>Definizioni +<p> +Le definizioni per gli estremi di molti dei tipi standard sono: + +<tscreen><verb> +G_MINFLOAT +G_MAXFLOAT +G_MINDOUBLE +G_MAXDOUBLE +G_MINSHORT +G_MAXSHORT +G_MININT +G_MAXINT +G_MINLONG +G_MAXLONG +</verb></tscreen> + +Ci sono anche le seguenti definizioni di tipo. Quelle rimaste non specificate +sono dipendenti dall'architettura. Si ricordi di evitare di fare affidamento +sulla dimensione di un puntatore se si vuole la portabilità! P.e., un puntatore +su un Alpha è lungo 8 byte, ma 4 su un Intel. + +<tscreen><verb> +char gchar; +short gshort; +long glong; +int gint; +char gboolean; + +unsigned char guchar; +unsigned short gushort; +unsigned long gulong; +unsigned int guint; + +float gfloat; +double gdouble; +long double gldouble; + +void* gpointer; + +gint8 +guint8 +gint16 +guint16 +gint32 +guint32 +</verb></tscreen> + +<sect1>Liste a doppio collegamento +<p> +le seguenti funzioni sono usate per creare, gestire e distruggere liste a +doppio collegamento. Si assume che il lettore sappia già cosa sono le liste +collegate, poiché descriverle è fuori dagli scopi di questo documento. +Naturalmente non è necessario conoscerle per l'uso generale di GTK, per +quanto conoscerle sia comunque interessante. + +<tscreen><verb> +GList* g_list_alloc (void); + +void g_list_free (GList *list); + +void g_list_free_1 (GList *list); + +GList* g_list_append (GList *list, + gpointer data); + +GList* g_list_prepend (GList *list, + gpointer data); + +GList* g_list_insert (GList *list, + gpointer data, + gint position); + +GList* g_list_remove (GList *list, + gpointer data); + +GList* g_list_remove_link (GList *list, + GList *link); + +GList* g_list_reverse (GList *list); + +GList* g_list_nth (GList *list, + gint n); + +GList* g_list_find (GList *list, + gpointer data); + +GList* g_list_last (GList *list); + +GList* g_list_first (GList *list); + +gint g_list_length (GList *list); + +void g_list_foreach (GList *list, + GFunc func, + gpointer user_data); +</verb></tscreen> + + +<sect1>Liste a collegamento singolo +<p> +Molte delle funzioni per le liste a collegamento singolo sono identiche alle +precedenti. Eccone una lista completa: +<tscreen><verb> +GSList* g_slist_alloc (void); + +void g_slist_free (GSList *list); + +void g_slist_free_1 (GSList *list); + +GSList* g_slist_append (GSList *list, + gpointer data); + +GSList* g_slist_prepend (GSList *list, + gpointer data); + +GSList* g_slist_insert (GSList *list, + gpointer data, + gint position); + +GSList* g_slist_remove (GSList *list, + gpointer data); + +GSList* g_slist_remove_link (GSList *list, + GSList *link); + +GSList* g_slist_reverse (GSList *list); + +GSList* g_slist_nth (GSList *list, + gint n); + +GSList* g_slist_find (GSList *list, + gpointer data); + +GSList* g_slist_last (GSList *list); + +gint g_slist_length (GSList *list); + +void g_slist_foreach (GSList *list, + GFunc func, + gpointer user_data); + +</verb></tscreen> + +<sect1>Gestione della memoria +<p> +<tscreen><verb> +gpointer g_malloc (gulong size); +</verb></tscreen> + +Questa è una sostituta di malloc(). Non occorre controllare il valore +restituito, in quanto lo fa già questa funzione. + +<tscreen><verb> +gpointer g_malloc0 (gulong size); +</verb></tscreen> + +Come la precedente, ma la memoria viene azzerata prima di restituire un +puntatore ad essa. + +<tscreen><verb> +gpointer g_realloc (gpointer mem, + gulong size); +</verb></tscreen> + +Riloca ``size'' byte di memoria che inizia a ``mem''. Ovviamente, la memoria +dovrebbe essere stata allocata precedentemente. + +<tscreen><verb> +void g_free (gpointer mem); +</verb></tscreen> + +Libera la memoria. Facile! + +<tscreen><verb> +void g_mem_profile (void); +</verb></tscreen> + +Emette un profilo della memoria usata, ma occorre ricompilare e reinstallare +la libreria aggiungendo #define MEM_PROFILE all'inizio del file glib/gmem.c. + +<tscreen><verb> +void g_mem_check (gpointer mem); +</verb></tscreen> + +Controlla che una locazione di memoria sia valida. Occorre ricompilare e +reinstallare la libreria aggiungendo #define MEM_CHECK all'inizio del file +gmem.c. + +<sect1>Timer +<p> +Funzioni legate ai timer... + +<tscreen><verb> +GTimer* g_timer_new (void); + +void g_timer_destroy (GTimer *timer); + +void g_timer_start (GTimer *timer); + +void g_timer_stop (GTimer *timer); + +void g_timer_reset (GTimer *timer); + +gdouble g_timer_elapsed (GTimer *timer, + gulong *microseconds); +</verb></tscreen> + +<sect1>Gestione delle stringhe +<p> +Un'accozzaglia di funzioni per la gestione delle stringhe. Sembrano tutte molto +interessanti, e probabilmente migliori per molte caratteristiche delle funzioni +standard del C per le stringhe, ma necessitano di documentazione. + +<tscreen><verb> +GString* g_string_new (gchar *init); +void g_string_free (GString *string, + gint free_segment); + +GString* g_string_assign (GString *lval, + gchar *rval); + +GString* g_string_truncate (GString *string, + gint len); + +GString* g_string_append (GString *string, + gchar *val); + +GString* g_string_append_c (GString *string, + gchar c); + +GString* g_string_prepend (GString *string, + gchar *val); + +GString* g_string_prepend_c (GString *string, + gchar c); + +void g_string_sprintf (GString *string, + gchar *fmt, + ...); + +void g_string_sprintfa (GString *string, + gchar *fmt, + ...); +</verb></tscreen> + +<sect1>Funzioni d'utilità e di errore +<p> +<tscreen><verb> +gchar* g_strdup (const gchar *str); +</verb></tscreen> + +Funzione sostitutiva della strdup. Copia i contenuti originari delle stringhe +in memoria appena allocata, restituendo un puntatore ad essa. + +<tscreen><verb> +gchar* g_strerror (gint errnum); +</verb></tscreen> +Si raccomanda di usare questa gunzione per tutti i messaggi di errore. E' molto +più graziosa, e più portabile di perror() o di altre. L'output di solito ha la +forma: + +<tscreen><verb> +nome programma:funzione fallita:file o altre descrizioni:strerror +</verb></tscreen> + +Di seguito un esempio di una chiamata di questo tipo usata nel nostro +programma Hello World: + +<tscreen><verb> +g_print("hello_world:open:%s:%s\n", filename, g_strerror(errno)); +</verb></tscreen> + +<tscreen><verb> +void g_error (gchar *format, ...); +</verb></tscreen> + +Visualizza un messaggio di errore. Il formato è come quello di printf, +ma prepone ``** ERROR **: '' al messaggio e termina il programma. Da usare solo +per errori gravi. + +<tscreen><verb> +void g_warning (gchar *format, ...); +</verb></tscreen> + +Come la precedente, ma prepone ``** WARNING **: '' e non termina il programma. + +<tscreen><verb> +void g_message (gchar *format, ...); +</verb></tscreen> + +Visualizza ``message: '' e poi il messaggio. + +<tscreen><verb> +void g_print (gchar *format, ...); +</verb></tscreen> + +Sostituta di printf(). + +L'ultima funzione: + +<tscreen><verb> +gchar* g_strsignal (gint signum); +</verb></tscreen> + +Visualizza il nome del messaggio del sistema Unix associato al numero di +segnale. Utile nelle funzioni generiche di gestione dei segnali. + +Tutte le funzioni elencate sono più o meno prese da glib.h. Se qualcuno volesse +documentare qualche funzione, mandi una email all'autore! + +<sect>I file rc di GTK +<p> +GTK ha un suo modo di trattare le preferenze delle applicazioni, usando +i file rc. Questi possono essere usati per scegliere i colori di quasi tutti +i widget, e possono anche essere usati per inserire delle pixmap nello sfondo +di alcuni widget. + +<sect1>Funzioni per i file rc +<p> +All'inizio della vostra applicazione dovrebbe esserci una chiamata a +<tscreen><verb> +void gtk_rc_parse (char *filename); +</verb></tscreen> +<p> +passando come parametro il nome del vostro file rc. Questo farà si che GTK +analizzi tale file e usi le impostazioni di stile per i tipi di widget ivi +definite. +<p> +Se si desidera avere un insieme speciale di widget che abbia uno stile diverso +dagli altri, o qualsiasi altra divisione logica dei widget, si chiami +<tscreen><verb> +void gtk_widget_set_name (GtkWidget *widget, + gchar *name); +</verb></tscreen> +<p> +passando un widget appena creato come primo argomento, e il nome che gli si +vuole dare come secondo. Questo consentirà di cambiare gli attributi di +questo widget per nome tramite il file rc. +<p> +Effettuando una chiamata come questa: + +<tscreen><verb> +button = gtk_button_new_with_label ("Special Button"); +gtk_widget_set_name (button, "special button"); +</verb></tscreen> +<p> +allora a questo bottone viene dato il nome ``special button'' ed esso può essere +riferito per nome nel file rc come ``special button.GtkButton''. [<--- Verificatemi!] +<p> +Il seguente esempio di file rc imposta le proprietà della finestra principale, +e fa si che tutti i figli di questa finestra ereditino lo stile descritto +dallo stile ``main button''. Il codice usato nell'applicazione è: + +<tscreen><verb> +window = gtk_window_new (GTK_WINDOW_TOPLEVEL); +gtk_widget_set_name (window, "main window"); +</verb></tscreen> +<p> +Lo stile viene definito nel file rc usando: + +<tscreen><verb> +widget "main window.*GtkButton*" style "main_button" +</verb></tscreen> +<p> +che assegna a tutti i widget GtkButton nella finestra principale lo stile +``main_buttons'' secondo la definizione data nel file rc. +<p> +Come si può vedere, questo sistema è molto potente e flessibile. Usate la +vostra immaginazione per trarre il massimo vantaggio da esso. + +<sect1>Il formato dei file rc di GTK +<p> +Nell'esempio che segue viene illustrato il formato del file GTK. Si tratta +del file testgkrc dalla distribuzione del GTK, a cui sono stati aggiunti +vari commenti e varie cose. Potete includere questa spiegazione nella +vostra applicazione per consentire all'utente di personalizzarla finemente. +<p> +There are several directives to change the attributes of a widget. +Ci sono diverse direttive per cambiare gli attributi di un widget. +<itemize> +<item>fg - Assegna il colore di primo piano di un widget. +<item>bg - Assegna il colore di sfondo di un widget. +<item>bg_pixmap - Inserisce nello sfondo di un widget una pixmap. +<item>font - Sceglie il font da usarsi con il dato widget. +</itemize> +<p> +Inoltre ci sono diversi stati in cui può trovarsi un widget, e si possono +assegnare diversi colori, pixmap e font per ogni stato. Essi sono: +<itemize> +<item>NORMAL - Lo stato normale di un widget, quando il mouse non si trova su +di esso, quando non è premuto, ecc. +<item>PRELIGHT (evidenziato)- Quando il mouse si trova sopra al widget +verranno usati i colori assegnati per questo stato. +<item>ACTIVE (attivo) - Quando il widget è premuto o cliccato esso sarà attivo, +e verranno usati gli attributi assegnati da questa etichetta. +<item>INSENSITIVE (insensibile)- Quando un widget viene reso insensibile, +e non può essere attivato, prenderà questi attributi. +<item>SELECTED (selezionato) - Quando un oggetto viene selezionato, prende +questi attributi. +</itemize> +<p> +Quando si usano le parole chiave ``fg'' e ``bg'' per assegnare i colori dei +widget il formato è: +<tscreen><verb> +fg[<STATE>] = { Rosso, Verde, Blu } +</verb></tscreen> +<p> +Dove STATE è uno degli stati visti prima (PRELIGHT, ACTIVE ecc.), e Rosso, +Verde e Blu sono valori nell'intervallo 0 - 1.0; { 1.0, 1.0, 1.0 } rappresenta +il bianco. +Devono essere in formato float, o verranno visti come 0, sicché un ``1'' diretto +non funziona, deve essere ``1.0''. Uno ``0'' diretto va invece bene, poiché poco +importa se non viene riconosciuto: valori non riconosciuti vengono considerati +0. +<p> +bg_pixmap è molto simile al precedente, tranne per i colori che vengono +sostituiti dal nome di un file. + +pixmap_path è una lista di percorsi separati da ``:''. In questi percorsi vengono +cercate le pixmap specificate. +<p> +La direttiva font è semplicemente: +<tscreen><verb> +font = "<font name>" +</verb></tscreen> +<p> +dove l'unica parte complicata è immaginare la stringa del font. Allo scopo +può servire usare xfontsel o una utilità analoga. +<p> +``widget_class'' assegna lo stile di una classe di widget. Queste classi sono +elencate nell'introduzione ai widget sulla gerarchia delle classi. +<p> +La direttiva ``widget'' assegna un insieme di widget dal nome specificato ad +un dato stile, annullando qualsiasi stile assegnato per la data classe di widget. +Questi widget vengono registrati nell'applicazione usando la chiamata +gtk_widget_set_name(). Questo consente di specificare gli attributi di un +widget singlarmente, piuttosto che assegnando gli attributi di un'intera classe +di widget. E' opportuno documentare tutti questi widget speciali in modo che +gli utenti possano personalizzarli. +<p> +Quando la parola chiave ``<tt>parent</>'' viene usata come un attributo, il +widget erediterà gli attributi del suo genitore nell'applicazione. +<p> +Quando si definisce uno stile si possono assegnare gli attributi di uno +stile definito precedentemente a quello nuovo. +<tscreen><verb> +style "main_button" = "button" +{ + font = "-adobe-helvetica-medium-r-normal--*-100-*-*-*-*-*-*" + bg[PRELIGHT] = { 0.75, 0, 0 } +} +</verb></tscreen> +<p> +Questo esempio prende lo stile ``button'' e crea un nuovo stile +semplicemente cambiando il font e il colore di sfondo dello stato ``prelight'' +nello stile ``button''. +<p> +Naturalmente, molti di questi attributi non sono applicabili a tutti i widget. +E' veramente un semplice problema di buon senso. Tutto quello che potrebbe +applicarsi, dovrebbe. + +<sect1>Esempio di file rc +<p> + +<tscreen><verb> +# pixmap_path "<dir 1>:<dir 2>:<dir 3>:..." +# +pixmap_path "/usr/include/X11R6/pixmaps:/home/imain/pixmaps" +# +# style <name> [= <name>] +# { +# <option> +# } +# +# widget <widget_set> style <style_name> +# widget_class <widget_class_set> style <style_name> + + +# Ecco una lista di tutti gli stati possibili. Si noti che alcuni non sono +# applicabili a certi widget. +# +# NORMAL - Lo stato normale di un widget, quando il mouse non si trova su +# di esso, quando non è premuto, ecc. +# +# PRELIGHT (evidenziato)- Quando il mouse si trova sopra al widget +# verranno usati i colori assegnati per questo stato. +# +# ACTIVE (attivo) - Quando il widget è premuto o cliccato esso sarà attivo, +# e verranno usati gli attributi assegnati da questa etichetta. +# +# INSENSITIVE (insensibile)- Quando un widget viene reso insensibile, +# e non può essere attivato, prenderà questi attributi. +# +# SELECTED (selezionato) - Quando un oggetto viene selezionato, prende +# questi attributi. +# +# Dati questi stati, è possibile assegnare gli attributi dei widget in +# ognuno di questi stati usando le seguenti direttive. +# +# fg - Assegna il colore di primo piano di un widget. +# bg - Assegna il colore di sfondo di un widget. +# bg_pixmap - Inserisce nello sfondo di un widget una pixmap. +# font - Sceglie il font da usarsi con il dato widget. +# + +# Questo è uno stile chiamato "button". Il nome non è veramente importante, +# in quanto viene assegnato ai veri widget alla fine del file. + +style "window" +{ + # Questo inserisce nella spaziatura attorno alla finestra la pixmap + # specificata. + #bg_pixmap[<STATE>] = "<pixmap filename>" + bg_pixmap[NORMAL] = "warning.xpm" +} + +style "scale" +{ + # Mette il colore di primo piano (il colore del font) a rosso nello + # stato "NORMAL". + + fg[NORMAL] = { 1.0, 0, 0 } + + # Inserisce nello sfondo del gadget la stessa pixmap usata dal suo genitore. + bg_pixmap[NORMAL] = "<parent>" +} + +style "button" +{ + # Questo mostra tutti i possibili stati per un bottone. L'unico che + # non è applicabile è lo stato "SELECTED". + + fg[PRELIGHT] = { 0, 1.0, 1.0 } + bg[PRELIGHT] = { 0, 0, 1.0 } + bg[ACTIVE] = { 1.0, 0, 0 } + fg[ACTIVE] = { 0, 1.0, 0 } + bg[NORMAL] = { 1.0, 1.0, 0 } + fg[NORMAL] = { .99, 0, .99 } + bg[INSENSITIVE] = { 1.0, 1.0, 1.0 } + fg[INSENSITIVE] = { 1.0, 0, 1.0 } +} + +# In questi esempio ereditiamo gli attributi dello stile "button" e poi +# alteriamo il font e il colore di sfondo quando evidenziato per creare +# un nuovo stile "main_button". + +style "main_button" = "button" +{ + font = "-adobe-helvetica-medium-r-normal--*-100-*-*-*-*-*-*" + bg[PRELIGHT] = { 0.75, 0, 0 } +} + +style "toggle_button" = "button" +{ + fg[NORMAL] = { 1.0, 0, 0 } + fg[ACTIVE] = { 1.0, 0, 0 } + + # Questo seleziona come pixmap di sfondo per il toggle_button quella del + # suo widget genitore (definita nell'applicazione). + bg_pixmap[NORMAL] = "<parent>" +} + +style "text" +{ + bg_pixmap[NORMAL] = "marble.xpm" + fg[NORMAL] = { 1.0, 1.0, 1.0 } +} + +style "ruler" +{ + font = "-adobe-helvetica-medium-r-normal--*-80-*-*-*-*-*-*" +} + +# pixmap_path "~/.pixmaps" + +# Queste assegnano ai tipi di widget gli stili definiti prima. +# I tipi di widget sono elencati nella gerarchia delle classi, ma probabilmente +# dovrebbero essere elencati in questo documento come riferimento per l'utente. + +widget_class "GtkWindow" style "window" +widget_class "GtkDialog" style "window" +widget_class "GtkFileSelection" style "window" +widget_class "*Gtk*Scale" style "scale" +widget_class "*GtkCheckButton*" style "toggle_button" +widget_class "*GtkRadioButton*" style "toggle_button" +widget_class "*GtkButton*" style "button" +widget_class "*Ruler" style "ruler" +widget_class "*GtkText" style "text" + +# Questo assegna lo stile main_button a tutti i bottoni che sono figli della +# "main window" (finestra principale). Questi devono essere documenati per +# potersene avvantaggiare. +widget "main window.*GtkButton*" style "main_button" +</verb></tscreen> + + + +<sect>Scrivere un proprio Widget + +<p> +<sect1> Panoramica +<p> +Anche se la distribuzione GTK contiene molto tipi di widget che possono +coprire molte necessità basilari, può essere necessario costruirsi +un proprio widget. GTK usa molto l'ereditarietà tra i vari +widget e, di solito, vi è un widget che si avvicina a quello che ti +servirebbe, ed è spesso possibile creare un nuovo widget con poche linee +di codice. Ma prima di iniziare il lavoro su un nuovo widget, vediamo +se qualcuno non lo ha già creato. Questo eviterà un duplicazione +di lavoro e farà sì che i widget non-GTK puri siano minimi, così da +aiutare sia chi crea il codice che chi l'interfaccia per applicazioni GTK +molto grosse. D'altra parte, quando hai finito di scrivere un widget, +annuncialo a tutto il mondo così che le altre persone ne possano +beneficiare. Il miglioro modo dove farlo è la <tt>gtk-list</tt>. + +<sect1> L'anatomia di un widget + +<p> +Per creare un nuovo widget è importante aver capito come gli ogetti +di GTK lavorano. Questa sezione è solo una breve spiegazione. Guarda la +documentazione di riferimento per maggiori dettagli. + +<p> +I widget GTK sono implementati in un modo orientato agli oggetti, +anche se usando il C standard. Questo aumenta notevolmente la portabilità +e la stabilità, specialmente per le correnti generazioni di compilatori C++; +comunque questo significa che chi scrive un widget deve fare attenzione +ad alcuni dettagli di implementazione. L'informazione comune a tutte le +istanze di una classe di widget (ad esempio: a tutti i bottoni) è memorizzata +<em>class structure</em>. C'e' solamente una copia di questo in cui +sono memorizzate le informazioni riguardanti i segnali della classe +(assomiglia ad una funzione virtuale in C). Per supportare l'ereditarietà +il primo campo della struttura di una classe deve essere una copia della +struttura della classe genitore. La dichiarazione della struttura della +classe GtkButton è: + +<tscreen><verb> +struct _GtkButtonClass +{ + GtkContainerClass parent_class; + + void (* pressed) (GtkButton *button); + void (* released) (GtkButton *button); + void (* clicked) (GtkButton *button); + void (* enter) (GtkButton *button); + void (* leave) (GtkButton *button); +}; +</verb></tscreen> + +<p> +Quando un bottone viene trattato come un contenitore (ad esempio quando viene +ridimensionato) si può fare il cast della struttura della sua classe con la +GtkContainerClass, e usare i campi rilevanti per gestire i segnali. + +<p> +C'è anche una struttura per ogni widget che viene creata +ad ogni istanza. Questa struttura ha campi per +memorizzare le informazioni che sono differenti per ogni volta che il widget +viene istanziato. Chiameremo questa struttura la <em> struttura +oggetto</em>. Per la classe Bottone, questa ha l'aspetto: + +<tscreen><verb> +struct _GtkButton +{ + GtkContainer container; + + GtkWidget *child; + + guint in_button : 1; + guint button_down : 1; +}; +</verb></tscreen> + +<p> +Si noti che, similmente alla struttura della classe, il primo campo +è la struttura dell'oggetto della classe madre, così che, se necessario, si può fare il +cast di questa struttura con quella dell'oggetto della classe madre. + +<sect1> Creare un Widget composto + +<sect2> Introduzione + +<p> +Un tipo di widget a cui potreste essere interessati è un widget che +è semplicemnte un aggregato di altri widget GTK. Questo tipo di +widget non fa nulla che non possa essere fatto creando un nuovo +widget, ma fornisce un modo conveniente per inscatolare elementi +dell'interfaccia utente per poi riutilizzarli. +I widget FileSelection e ColorSelection della ditribuzione standard +sono esempi di questo tipo di widget. + +<p> +Il widget di esempio che creeremo in questo capitolo è il +Tictactoe, un vettore 3x3 di bottoni a commutazione il quale emette +un segnale quando tutti e 3 i bottoni di una riga, colonna o di una +diagonale sono premuti. + +<sect2> Scegliere la classe madre + +<p> +La classe madre per un widget composto e' tipicamente la classe +contenitrice che racchiude tutti gli elementi del widget composto. +Per esempio, la classe madre del widget FileSelection è la classe +Dialog. Visto che i nostri bottoni sono inseriti in una tabella, è +naturale pensare che la nostra classe madre possa essere la GtkTable. +Sfortunatamente, così non è. La creazione di un widget è diviso +tra 2 funzioni : la funzione <tt/WIDGETNAME_new()/ che viene invocata +dall'utente, e la funzione <tt/WIDGETNAME_init()/ che ha il compito +principale di inizializzare il widget che è indipendente dai valori +passati alla funzione <tt/_new()/. Widget figli o discendenti possono +chiamare, solamente, la funzione del loro widget genitore. +Ma questa divisione del lavoro non funziona bene per la tabella, la +quale, quando creata, necessita di conoscere il numero di righe e +colonne che la comporrà. A meno che non vogliamo duplicare molte delle +fuinzionalità della <tt/gtk_table_new()/ nel nostro widget +Tictactoe, faremmo meglio a evitare di derivarlo dalla GtkTable. Per questa +ragione lo deriviamo invece da GtkVBox, e uniamo la nostra tabella +dentro il VBox. + +<sect2> Il File Header + +<p> +Ogni classe di widget ha un file header il quale dichiara l'oggetto e la +struttura della classe del widget, comprese le funzioni pubbliche. +Per prevenire duplicati di definizioni, noi includiamo l'intero file header fra: + +<tscreen><verb> +#ifndef __TICTACTOE_H__ +#define __TICTACTOE_H__ +. +. +. +#endif /* __TICTACTOE_H__ */ +</verb></tscreen> + +E per far felici i programmi in C++ che includono il nostro file header, in: + +<tscreen><verb> +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ +. +. +. +#ifdef __cplusplus +} +#endif /* __cplusplus */ +</verb></tscreen> + +Insieme alle funzioni e alle strutture, dichiariamo tre macro +standard nel nostro file header, <tt/TICTACTOE(obj)/, +<tt/TICTACTOE_CLASS(klass)/, e <tt/IS_TICTACTOE(obj)/, i quali rispettivamente +fanno il cast di un puntatore ad un puntatore ad un ogetto od ad una struttura +di classe, e guarda se un oggetto è un widget Tictactoe. + + +Qui vi è il file header completo: + +<tscreen><verb> + +#ifndef __TICTACTOE_H__ +#define __TICTACTOE_H__ + +#include <gdk/gdk.h> +#include <gtk/gtkvbox.h> + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#define TICTACTOE(obj) GTK_CHECK_CAST (obj, tictactoe_get_type (), Tictactoe) +#define TICTACTOE_CLASS(klass) GTK_CHECK_CLASS_CAST (klass, tictactoe_get_type (), TictactoeClass) +#define IS_TICTACTOE(obj) GTK_CHECK_TYPE (obj, tictactoe_get_type ()) + + +typedef struct _Tictactoe Tictactoe; +typedef struct _TictactoeClass TictactoeClass; + +struct _Tictactoe +{ + GtkVBox vbox; + + GtkWidget *buttons[3][3]; +}; + +struct _TictactoeClass +{ + GtkVBoxClass parent_class; + + void (* tictactoe) (Tictactoe *ttt); +}; + +guint tictactoe_get_type (void); +GtkWidget* tictactoe_new (void); +void tictactoe_clear (Tictactoe *ttt); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __TICTACTOE_H__ */ + +</verb></tscreen> + +<sect2> La funzione <tt/_get_type()/ + +<p> +Continuiamo ora con l'implementazione del nostro widget. Una funzione +basilare di ogni widget è la funzione <tt/WIDGETNAME_get_type()/. +Questa funzione, quando chiamata la prima volta, comunica a GTK la classe +del widget, e ottiene un identificativo univoco per la classe del +widget. Chiamate successive restituiscono semplicemente l'identificativo. + +<tscreen><verb> +guint +tictactoe_get_type () +{ + static guint ttt_type = 0; + + if (!ttt_type) + { + GtkTypeInfo ttt_info = + { + "Tictactoe", + sizeof (Tictactoe), + sizeof (TictactoeClass), + (GtkClassInitFunc) tictactoe_class_init, + (GtkObjectInitFunc) tictactoe_init, + (GtkArgFunc) NULL, + }; + + ttt_type = gtk_type_unique (gtk_vbox_get_type (), &ttt_info); + } + + return ttt_type; +} +</verb></tscreen> + +<p> +La struttura GtkTypeInfo ha la seguente definizione: + +<tscreen><verb> +struct _GtkTypeInfo +{ + gchar *type_name; + guint object_size; + guint class_size; + GtkClassInitFunc class_init_func; + GtkObjectInitFunc object_init_func; + GtkArgFunc arg_func; +}; +</verb></tscreen> + +<p> +I campi di questa struttura sono abbastanza auto-esplicativi. +Ignoreremo, per ora, il campo <tt/arg_func/: ha un ruolo importante, ma +non ancora largamente implementato, nel permettere ai linguaggi interpretati +di settare convenientemente le opzioni del widget. +Una volta che il GTK ha completato correttamente una copia di questa +struttura, sa come creare un oggetto di un particolare widget. + +<sect2> La funzione <tt/_class_init()/ +<p> +La funzione <tt/WIDGETNAME_class_init()/ inizialiazza i campi della +struttura della classe del widget, e setta ogni segnale della classe. +Per il nostro widget Tictactoe ha il seguente aspetto: + +<tscreen><verb> + +enum { + TICTACTOE_SIGNAL, + LAST_SIGNAL +}; + +static gint tictactoe_signals[LAST_SIGNAL] = { 0 }; + +static void +tictactoe_class_init (TictactoeClass *class) +{ + GtkObjectClass *object_class; + + object_class = (GtkObjectClass*) class; + + tictactoe_signals[TICTACTOE_SIGNAL] = gtk_signal_new ("tictactoe", + GTK_RUN_FIRST, + object_class->type, + GTK_SIGNAL_OFFSET (TictactoeClass, tictactoe), + gtk_signal_default_marshaller, GTK_ARG_NONE, 0); + + + gtk_object_class_add_signals (object_class, tictactoe_signals, LAST_SIGNAL); + + class->tictactoe = NULL; +} +</verb></tscreen> + +<p> +Il nostro widget ha semplicemente il segnale ``tictactoe'' che è +invocato quando una riga, colonna o diagonale è completamente premuta. +Non tutti i widget composti necessitano di segnali, quindi se stai +leggendo questo per la prima volta, puoi anche saltare alla prossima sezione, +dal momento che a questo punto le cose diventano un po' complicate. + +La funzione: +<tscreen><verb> +gint gtk_signal_new (gchar *name, + GtkSignalRunType run_type, + gint object_type, + gint function_offset, + GtkSignalMarshaller marshaller, + GtkArgType return_val, + gint nparams, + ...); +</verb></tscreen> + +crea un nuovo segnale. I parametri sono: + +<itemize> +<item> <tt/name/: Il nome del segnale. +<item> <tt/run_type/: Se il segstore predefinito viene eseguito prima o dopo +di quello dell'utente. Di norma questo sarà <tt/GTK_RUN_FIRST/, o <tt/GTK_RUN_LAST/, +anche se ci sono altre possibilità. +<item> <tt/object_type/: l'identificativo dell'oggetto a cui questo segnale si +riferisce. Esso sarà anche applicato agli oggetti discendenti. +<item> <tt/function_offset/: L'offset nella struttura della classe di un +puntatore al gestore predefinito. +<item> <tt/marshaller/: una funzione che è usata per invocare il gestore +del segnale. Per gestori di segnali che non hanno argomenti oltre +all'oggetto che emette il segnale e i dati dell'utente, possiamo usare +la funzione predefinita <tt/gtk_signal_default_marshaller/ +<item> <tt/return_val/: Il tipo del valore di ritorno. +<item> <tt/nparams/: Il numero di parametri del gestore di segnali (oltre +ai due predefiniti menzionati sopra) +<item> <tt/.../: i tipi dei parametri +</itemize> + +Quando si specificano i tipi, si usa l'enumerazione <tt/GtkArgType/: + +<tscreen><verb> +typedef enum +{ + GTK_ARG_INVALID, + GTK_ARG_NONE, + GTK_ARG_CHAR, + GTK_ARG_SHORT, + GTK_ARG_INT, + GTK_ARG_LONG, + GTK_ARG_POINTER, + GTK_ARG_OBJECT, + GTK_ARG_FUNCTION, + GTK_ARG_SIGNAL +} GtkArgType; +</verb></tscreen> + +<p> +<tt/gtk_signal_new()/ restituisce un identificatore unico intero per il segnale, +che memorizziamo nel vettore <tt/tictactoe_signals/, che +indicizzeremo usando una enumerazione. (Convenzionalmente, gli elementi dell'enumerazione +sono i nomi dei segnali, in maiuscolo, +ma qui ci potrebbe essere un conflitto con la macro <tt/TICTACTOE()/, +quindi l'abbiamo chiamato <tt/TICTACTOE_SIGNAL/ + +Dopo aver creato un nostro segnale, abbiamo bisogno di dire a GTK +di associare il nostro segnale alla classe Tictactoe. Lo facciamo +invocando <tt/gtk_object_class_add_signals()/. Settiamo quindi a NULL +il puntatore che punta al gestore predefinito per il segnale +``tictactoe'' a NULL, indicando che non ci sono azioni predefinite. + +<sect2> La funzione <tt/_init()/ + +<p> + +Ogni classe di Widget necessita anche di una funzione per inizializzare +la struttura dell'oggetto. Usualmente questa funzione ha il ruolo abbastanza +limitato di assegnare ai campi della struttura i valori predefiniti. +Per widget composti, comunque, questa funzione crea, anche, +i widget componenti del widget composto. + +<tscreen><verb> + +static void +tictactoe_init (Tictactoe *ttt) +{ + GtkWidget *table; + gint i,j; + + table = gtk_table_new (3, 3, TRUE); + gtk_container_add (GTK_CONTAINER(ttt), table); + gtk_widget_show (table); + + for (i=0;i<3; i++) + for (j=0;j<3; j++) + { + ttt->buttons[i][j] = gtk_toggle_button_new (); + gtk_table_attach_defaults (GTK_TABLE(table), ttt->buttons[i][j], + i, i+1, j, j+1); + gtk_signal_connect (GTK_OBJECT (ttt->buttons[i][j]), "toggled", + GTK_SIGNAL_FUNC (tictactoe_toggle), ttt); + gtk_widget_set_usize (ttt->buttons[i][j], 20, 20); + gtk_widget_show (ttt->buttons[i][j]); + } +} +</verb></tscreen> + +<sect2> E il resto... + +<p> + +C'è un'altra funzione che ogni widget (eccetto i Widget di base come +GtkBin che non possono essere instanziati) deve avere : la funzione +che l'utente invoca per creare un oggetto di quel tipo. Questa è +convenzionalmente chiamata <tt/WIDGETNAME_new()/. In alcuni widget, +non nel caso del nostro Tictactoe, questa funzione richiede degli +argomenti, e fa alcune operazioni basandosi su di essi. Le altre +due funzioni sono specifiche del widget Tictactoe. + +<p> +<tt/tictactoe_clear()/ è una funzione pubblica che resetta tutti i +bottoni, nel widget, allo stato iniziale (non premuto). Notate l'uso +di <tt/gtk_signal_handler_block_by_data()/ per impedire che il nostro +gestore dei segnali venga attivato quando non ce n'è bisogno. + +<p> +<tt/tictactoe_toggle()/ è il gestore del segnale che viene invocato +quando l'utente preme il bottone. Esso guarda se vi è +qualche combinazione vincente che coinvolge i bottoni premuti, e nel +caso ci fosse, emette il segnale ``tictactoe''. + +<tscreen><verb> +GtkWidget* +tictactoe_new () +{ + return GTK_WIDGET ( gtk_type_new (tictactoe_get_type ())); +} + +void +tictactoe_clear (Tictactoe *ttt) +{ + int i,j; + + for (i=0;i<3;i++) + for (j=0;j<3;j++) + { + gtk_signal_handler_block_by_data (GTK_OBJECT(ttt->buttons[i][j]), ttt); + gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON (ttt->buttons[i][j]), + FALSE); + gtk_signal_handler_unblock_by_data (GTK_OBJECT(ttt->buttons[i][j]), ttt); + } +} + +static void +tictactoe_toggle (GtkWidget *widget, Tictactoe *ttt) +{ + int i,k; + + static int rwins[8][3] = { { 0, 0, 0 }, { 1, 1, 1 }, { 2, 2, 2 }, + { 0, 1, 2 }, { 0, 1, 2 }, { 0, 1, 2 }, + { 0, 1, 2 }, { 0, 1, 2 } }; + static int cwins[8][3] = { { 0, 1, 2 }, { 0, 1, 2 }, { 0, 1, 2 }, + { 0, 0, 0 }, { 1, 1, 1 }, { 2, 2, 2 }, + { 0, 1, 2 }, { 2, 1, 0 } }; + + int success, found; + + for (k=0; k<8; k++) + { + success = TRUE; + found = FALSE; + + for (i=0;i<3;i++) + { + success = success && + GTK_TOGGLE_BUTTON(ttt->buttons[rwins[k][i]][cwins[k][i]])->active; + found = found || + ttt->buttons[rwins[k][i]][cwins[k][i]] == widget; + } + + if (success && found) + { + gtk_signal_emit (GTK_OBJECT (ttt), + tictactoe_signals[TICTACTOE_SIGNAL]); + break; + } + } +} +</verb></tscreen> + +<p> + +E finalmente un programma di esempio che usa il nostro widget +Tictactoe: + +<tscreen><verb> +#include <gtk/gtk.h> +#include "tictactoe.h" + +/* Invocato quando una riga, colonna o diagonale e' completata. */ +void +win (GtkWidget *widget, gpointer data) +{ + g_print ("Yay!\n"); + tictactoe_clear (TICTACTOE (widget)); +} + +int +main (int argc, char *argv[]) +{ + GtkWidget *window; + GtkWidget *ttt; + + gtk_init (&argc, &argv); + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + + gtk_window_set_title (GTK_WINDOW (window), "Aspect Frame"); + + gtk_signal_connect (GTK_OBJECT (window), "destroy", + GTK_SIGNAL_FUNC (gtk_exit), NULL); + + gtk_container_border_width (GTK_CONTAINER (window), 10); + + /* Crea un nuovo widget Tictactoe. */ + ttt = tictactoe_new (); + gtk_container_add (GTK_CONTAINER (window), ttt); + gtk_widget_show (ttt); + + /* E gli aggancia il segnale "tictactoe" */ + gtk_signal_connect (GTK_OBJECT (ttt), "tictactoe", + GTK_SIGNAL_FUNC (win), NULL); + + gtk_widget_show (window); + + gtk_main (); + + return 0; +} + +</verb></tscreen> + + +<sect1> Creare un widget a partire da zero + +<sect2> Introduzione + +<p> + +In questa sezione impareremo meglio come i widget si mostrano sullo schermo +e interagiscono con gli eventi. Come esempio, creeremo +un widget di quadrante analogico con un puntatore che l'utente +può trascinare per assegnare il valore. + +<sect2> Mostrare un widget sullo schermo + +<p> +Ci sono alcuni passi che sono necessari nella visualizzazione sullo +schermo. Dopo che il widget è stato creato con una chiamata a +<tt/WIDGETNAME_new()/, sono necessarie alcune altre funzioni: + +<itemize> +<item> <tt/WIDGETNAME_realize()/ è responsabile della creazione di +una finestra X per il widget se ne ha una. +<item> <tt/WIDGETNAME_map()/ è invocata dopo che l'utente ha +chiamato <tt/gtk_widget_show()/. E' responsabile di vedere se il +widget è attualmente disegnato sullo schermo (<em/mappato/). Per +una classe contenitore, essa deve anche creare chiamate alle +funzioni <tt/map()/> per ogni widget figlio. +<item> <tt/WIDGETNAME_draw()/ è invocata quando +<tt/gtk_widget_draw()/ viene chiamata per il widget o per uno dei suoi +predecessori. Esso fa sì che l'attuale chiamata alla +funzione di disegno del widget disegni il widget sullo schermo. +Per la classe contenitore, questa funzione deve eseguire le +chiamate alla funzioni <tt/gtk_widget_draw()/ di ogni suo widget +figlio. +<item> <tt/WIDGETNAME_expose()/ è un gestore per l'evento di esposizione +per il widget. Esso crea le chiamate necessarie alle funzioni di disegno +per disegnare la porzione che si è resa visibile. Per le classi +contenitore, questa funzione deve generare gli eventi di ``expose'' per +tutti i widget figli che non hanno una propria finestra (se essi hanno +una loro finestra, sarà X che genererà i necessari eventi di expose). +</itemize> + +<p> +Potete notare che le ultime due funzioni sono molto simili, ognuna è +responsabile per il disegno del widget sullo schermo. Infatti molti +tipi di widget non sanno relamente la differenza tra le due. +La funzione di predefinita <tt/draw()/ nella classe widget, semplicemente +genera un sintetico evento di ``expose'' per l'area da ridisegnare. +Comunque, alcuni tipi di widget possono risparmiare tempo distinguendo +le due funzioni. Per esempio, se un widget ha piu' finestre X, allora +visto che l'evento ``expose'' identifica solo la finestra esposta, +esso può ridisegnare solo la finestra interessata, cosa che non è +possibile per chiamate a <tt/draw()/. + +<p> +I widget contenitori, anche se essi non farebbero differenze, +non possono semplicemente usare la funzione <tt/draw()/ perchè per i +loro widget figli la differenza potrebbere essere importante. Comunque, +sarebbe uno spreco duplicare il codice di disegno nelle due +funzioni. La convenzione è che questi widget abbiano una funzione +chiamata <tt/WIDGETNAME_paint()/ che disegna il widget, che è poi +chiamata dalle funzioni <tt/draw()/ e <tt/expose()/ + +<p> +Nell'approccio del nostro esempio, visto che il widget, ha +una sola finestra, possiamo utilizzare il modo piu' semplice +ed usare la funzione predefinita <tt/draw()/ e implementare +solamente la funzione <tt/expose()/. + +<sect2> Le origini del widget Dial + +<p> +Come tutti gli animali terresti sono semplicemente varianti del primo +amfibio, i widget Gtk tendono ad essere varianti di altri widget, precedentemente +scritti. Così, anche se questa sezione è intitolata ``Creare +un widget a partire da zero", il nostro widget inizia in realtà con il codice +sorgente del widget Range. Questo è stato preso come punto d'inizio +perche' sarebbe carino se il nostro widget avesse la +stessa interfaccia del widget Scale il quale è semplicemente una +specializzazione del widget Range. Così, sebbene il codice sorgente e' +presentato sotto in forma definitiva, non si deve pensare che sia stato +scritto <em>deus ex machina</em> in questo modo. Se poi non avete familiarità +con il funzionamento del widget Scale dal punto di vista di chi scrive +un'applicazione, potrebbe essere una buona idea guardare indietro prima +di continuare. + +<sect2> Le basi + +<p> +Una parte del nostro widget potrebbe essere simile +al widget Tictactoe. In primo luogo, abbiamo il file header: + +<tscreen><verb> +/* GTK - The GIMP Toolkit + * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free + * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef __GTK_DIAL_H__ +#define __GTK_DIAL_H__ + +#include <gdk/gdk.h> +#include <gtk/gtkadjustment.h> +#include <gtk/gtkwidget.h> + + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +#define GTK_DIAL(obj) GTK_CHECK_CAST (obj, gtk_dial_get_type (), GtkDial) +#define GTK_DIAL_CLASS(klass) GTK_CHECK_CLASS_CAST (klass, gtk_dial_get_type (), GtkDialClass) +#define GTK_IS_DIAL(obj) GTK_CHECK_TYPE (obj, gtk_dial_get_type ()) + + +typedef struct _GtkDial GtkDial; +typedef struct _GtkDialClass GtkDialClass; + +struct _GtkDial +{ + GtkWidget widget; + + /* Politica di update (GTK_UPDATE_[CONTINUOUS/DELAYED/DISCONTINUOUS]) */ + guint policy : 2; + + /* Bottone correntemente premuto o 0 altrimenti */ + guint8 button; + + /* Dimensione della componente Dial. */ + gint radius; + gint pointer_width; + + /* ID del timer di update, o 0 altrimenti */ + guint32 timer; + + /* Angolo corrente. */ + gfloat angle; + + /* Vecchi valori dell'aggiustamento così sappiamo quando + * qualcosa cambia */ + gfloat old_value; + gfloat old_lower; + gfloat old_upper; + + /* L'oggetto adjustament che memorizza i dati per questo dial */ + GtkAdjustment *adjustment; +}; + +struct _GtkDialClass +{ + GtkWidgetClass parent_class; +}; + + +GtkWidget* gtk_dial_new (GtkAdjustment *adjustment); +guint gtk_dial_get_type (void); +GtkAdjustment* gtk_dial_get_adjustment (GtkDial *dial); +void gtk_dial_set_update_policy (GtkDial *dial, + GtkUpdateType policy); + +void gtk_dial_set_adjustment (GtkDial *dial, + GtkAdjustment *adjustment); +#ifdef __cplusplus +} +#endif /* __cplusplus */ + + +#endif /* __GTK_DIAL_H__ */ + +</verb></tscreen> + +Essendoci più cose da fare con questo widget, rispetto al precedente, +abbiamo più cambi nella struttura dati, ma le altre cose sono +abbastamza simili. + +<p> + +Dopo aver incluso i file di header e aver dichiarato alcune costanti, +dobbiamo fornire alcune funzioni circa il widget e la sua +inizializzazione. + +<tscreen><verb> +#include <math.h> +#include <stdio.h> +#include <gtk/gtkmain.h> +#include <gtk/gtksignal.h> + +#include "gtkdial.h" + +#define SCROLL_DELAY_LENGTH 300 +#define DIAL_DEFAULT_SIZE 100 + +/* Dichiarazioni di funzioni successive */ + +[ omesse per salvare spazio ] + +/* variabili locali. */ + +static GtkWidgetClass *parent_class = NULL; + +guint +gtk_dial_get_type () +{ + static guint dial_type = 0; + + if (!dial_type) + { + GtkTypeInfo dial_info = + { + "GtkDial", + sizeof (GtkDial), + sizeof (GtkDialClass), + (GtkClassInitFunc) gtk_dial_class_init, + (GtkObjectInitFunc) gtk_dial_init, + (GtkArgFunc) NULL, + }; + + dial_type = gtk_type_unique (gtk_widget_get_type (), &dial_info); + } + + return dial_type; +} + +static void +gtk_dial_class_init (GtkDialClass *class) +{ + GtkObjectClass *object_class; + GtkWidgetClass *widget_class; + + object_class = (GtkObjectClass*) class; + widget_class = (GtkWidgetClass*) class; + + parent_class = gtk_type_class (gtk_widget_get_type ()); + + object_class->destroy = gtk_dial_destroy; + + widget_class->realize = gtk_dial_realize; + widget_class->expose_event = gtk_dial_expose; + widget_class->size_request = gtk_dial_size_request; + widget_class->size_allocate = gtk_dial_size_allocate; + widget_class->button_press_event = gtk_dial_button_press; + widget_class->button_release_event = gtk_dial_button_release; + widget_class->motion_notify_event = gtk_dial_motion_notify; +} + +static void +gtk_dial_init (GtkDial *dial) +{ + dial->button = 0; + dial->policy = GTK_UPDATE_CONTINUOUS; + dial->timer = 0; + dial->radius = 0; + dial->pointer_width = 0; + dial->angle = 0.0; + dial->old_value = 0.0; + dial->old_lower = 0.0; + dial->old_upper = 0.0; + dial->adjustment = NULL; +} + +GtkWidget* +gtk_dial_new (GtkAdjustment *adjustment) +{ + GtkDial *dial; + + dial = gtk_type_new (gtk_dial_get_type ()); + + if (!adjustment) + adjustment = (GtkAdjustment*) gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0); + + gtk_dial_set_adjustment (dial, adjustment); + + return GTK_WIDGET (dial); +} + +static void +gtk_dial_destroy (GtkObject *object) +{ + GtkDial *dial; + + g_return_if_fail (object != NULL); + g_return_if_fail (GTK_IS_DIAL (object)); + + dial = GTK_DIAL (object); + + if (dial->adjustment) + gtk_object_unref (GTK_OBJECT (dial->adjustment)); + + if (GTK_OBJECT_CLASS (parent_class)->destroy) + (* GTK_OBJECT_CLASS (parent_class)->destroy) (object); +} +</verb></tscreen> + +Notate che questa funzione <tt/init()/ fa meno rispetto all'analoga del +widget Tictactoe, essendo questo un widget non composto, e la +funzione <tt/new()/ fa di più, essendoci un argomento. Inoltre, +notate che quando memorizziamo un puntatore all'oggetto Adjustment, +incrementiamo il conteggio dei suoi riferimenti(e corrispondentemente +lo decrementato quando non lo usiamo più) così che GTK può tener traccia di +quando è possibile distruggerlo senza causare guai. + +<p> +Inoltre, ci sono alcune funzioni per manipolare le opzioni del widget: + +<tscreen><verb> +GtkAdjustment* +gtk_dial_get_adjustment (GtkDial *dial) +{ + g_return_val_if_fail (dial != NULL, NULL); + g_return_val_if_fail (GTK_IS_DIAL (dial), NULL); + + return dial->adjustment; +} + +void +gtk_dial_set_update_policy (GtkDial *dial, + GtkUpdateType policy) +{ + g_return_if_fail (dial != NULL); + g_return_if_fail (GTK_IS_DIAL (dial)); + + dial->policy = policy; +} + +void +gtk_dial_set_adjustment (GtkDial *dial, + GtkAdjustment *adjustment) +{ + g_return_if_fail (dial != NULL); + g_return_if_fail (GTK_IS_DIAL (dial)); + + if (dial->adjustment) + { + gtk_signal_disconnect_by_data (GTK_OBJECT (dial->adjustment), (gpointer) dial); + gtk_object_unref (GTK_OBJECT (dial->adjustment)); + } + + dial->adjustment = adjustment; + gtk_object_ref (GTK_OBJECT (dial->adjustment)); + + gtk_signal_connect (GTK_OBJECT (adjustment), "changed", + (GtkSignalFunc) gtk_dial_adjustment_changed, + (gpointer) dial); + gtk_signal_connect (GTK_OBJECT (adjustment), "value_changed", + (GtkSignalFunc) gtk_dial_adjustment_value_changed, + (gpointer) dial); + + dial->old_value = adjustment->value; + dial->old_lower = adjustment->lower; + dial->old_upper = adjustment->upper; + + gtk_dial_update (dial); +} +</verb></tscreen> + +<sect2> <tt/gtk_dial_realize()/ + +<p> +Abbiamo ora raggiunto alcuni nuovi tipi di funzione. In primo luogo, +abbiamo una funzione che crea la finestra di X. Noterete che viene +passata alla funzione <tt/gdk_window_new()/ una maschera che +specifica quali campi della struttura GdkWindowAttr non sono vuoti +(ai rimanenti campi può essere dato il valore predefinito). Anche +il modo con cui la maschera degli eventi del widget creata non è +complicato. Chiameremo <tt/gtk_widget_get_events()/ per sapere la +maschera degli eventi che l'utente ha specificato per questo widget +(con <tt/gtk_widget_set_events()/) e aggiungeremo gli eventi che ci possono +interessare. + +<p> +Dopo aver creato la finestra, settiamo lo stile e lo sfondo, +e creiamo un puntatore al widget nel campo dei dati utente (user data) +del GdkWindow. Quest'ultimo passo permette a GTK di mandare gli +eventi della finestra al widget corretto. + +<tscreen><verb> +static void +gtk_dial_realize (GtkWidget *widget) +{ + GtkDial *dial; + GdkWindowAttr attributes; + gint attributes_mask; + + g_return_if_fail (widget != NULL); + g_return_if_fail (GTK_IS_DIAL (widget)); + + GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED); + dial = GTK_DIAL (widget); + + attributes.x = widget->allocation.x; + attributes.y = widget->allocation.y; + attributes.width = widget->allocation.width; + attributes.height = widget->allocation.height; + attributes.wclass = GDK_INPUT_OUTPUT; + attributes.window_type = GDK_WINDOW_CHILD; + attributes.event_mask = gtk_widget_get_events (widget) | + GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK | + GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK | + GDK_POINTER_MOTION_HINT_MASK; + attributes.visual = gtk_widget_get_visual (widget); + attributes.colormap = gtk_widget_get_colormap (widget); + + attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP; + widget->window = gdk_window_new (widget->parent->window, &attributes, attributes_mask); + + widget->style = gtk_style_attach (widget->style, widget->window); + + gdk_window_set_user_data (widget->window, widget); + + gtk_style_set_background (widget->style, widget->window, GTK_STATE_ACTIVE); +} +</verb></tscreen> + +<sect2> Negoziazione della dimensione + +<p> +Prima di visualizzare per la prima volta la finestra, e se il +layout della finestra cambia, GTK chiede ad ogni widget, incluso nella +finestra, la propria dimensione. Questa richiesta è fatta dalla +funzione <tt/gtk_dial_size_request()/. Non essendo il nostro widget +un contenitore, e non avendo dei veri limiti per la propria +dimensione, restituiamo semplicemnte un valore ragionevole. + +<tscreen><verb> +static void +gtk_dial_size_request (GtkWidget *widget, + GtkRequisition *requisition) +{ + requisition->width = DIAL_DEFAULT_SIZE; + requisition->height = DIAL_DEFAULT_SIZE; +} +</verb></tscreen> + +<p> +Dopo che tutti i widget hanno restituito una dimensione ideale, viene +calcolata la disposizione della finestra e ad ogni widget figlio è +notificata la propria dimensione attuale <!--ndMichel : che può essere diversa +da quella restitutita con la funzione sopra -->. Usualmente, questo sarà +almeno quanto richiesto, ma occasionalmente può essere più piccolo. +La notifica della dimensione viene fatta dalla funzione + <tt/gtk_dial_size_allocate()/. Notate che questa funzione è utilizzata +anche quando la finestra X del widget è spostata o modificata come +dimensione. + +<tscreen><verb> +static void +gtk_dial_size_allocate (GtkWidget *widget, + GtkAllocation *allocation) +{ + GtkDial *dial; + + g_return_if_fail (widget != NULL); + g_return_if_fail (GTK_IS_DIAL (widget)); + g_return_if_fail (allocation != NULL); + + widget->allocation = *allocation; + if (GTK_WIDGET_REALIZED (widget)) + { + dial = GTK_DIAL (widget); + + gdk_window_move_resize (widget->window, + allocation->x, allocation->y, + allocation->width, allocation->height); + + dial->radius = MAX(allocation->width,allocation->height) * 0.45; + dial->pointer_width = dial->radius / 5; + } +} +</verb></tscreen>. + +<sect2> <tt/gtk_dial_expose()/ + +<p> +Come menzionato sopra, tutto il lavoro di questo widget viene fatto nella +gestione dell'evento ``expose''. Non c'è molto da notare su questo eccetto +l'uso della funzione <tt/gtk_draw_polygon/ per disegnare il +puntatore con un'ombreggiatura a tre dimensioni in accordo con il colore +memorizzato nello stile del wiget. + +<tscreen><verb> +static gint +gtk_dial_expose (GtkWidget *widget, + GdkEventExpose *event) +{ + GtkDial *dial; + GdkPoint points[3]; + gdouble s,c; + gdouble theta; + gint xc, yc; + gint tick_length; + gint i; + + g_return_val_if_fail (widget != NULL, FALSE); + g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE); + g_return_val_if_fail (event != NULL, FALSE); + + if (event->count > 0) + return FALSE; + + dial = GTK_DIAL (widget); + + gdk_window_clear_area (widget->window, + 0, 0, + widget->allocation.width, + widget->allocation.height); + + xc = widget->allocation.width/2; + yc = widget->allocation.height/2; + + /* Draw ticks */ + + for (i=0; i<25; i++) + { + theta = (i*M_PI/18. - M_PI/6.); + s = sin(theta); + c = cos(theta); + + tick_length = (i%6 == 0) ? dial->pointer_width : dial->pointer_width/2; + + gdk_draw_line (widget->window, + widget->style->fg_gc[widget->state], + xc + c*(dial->radius - tick_length), + yc - s*(dial->radius - tick_length), + xc + c*dial->radius, + yc - s*dial->radius); + } + + /* Draw pointer */ + + s = sin(dial->angle); + c = cos(dial->angle); + + + points[0].x = xc + s*dial->pointer_width/2; + points[0].y = yc + c*dial->pointer_width/2; + points[1].x = xc + c*dial->radius; + points[1].y = yc - s*dial->radius; + points[2].x = xc - s*dial->pointer_width/2; + points[2].y = yc - c*dial->pointer_width/2; + + gtk_draw_polygon (widget->style, + widget->window, + GTK_STATE_NORMAL, + GTK_SHADOW_OUT, + points, 3, + TRUE); + + return FALSE; +} +</verb></tscreen> + +<sect2> Gestore degli eventi + +<p> + +Il resto del codice del widget manipola vari tipi di eventi, e non +è differente da quello che può essere trovato in molte applicazione +GTK. Due tipi di eventi possono verificarsi: l'utente può +clickare sul widget con il mouse e trascinare per muovere il puntatore, +o il valore dell'oggetto Adjustmente può cambiare a causa di alcune +circostanze esterne. + +<p> +Quando l'utente clicka sul widget, noi vediamo se la pressione +era veramente vicina al puntatore, e se così, memorizziamo il bottone +premuto dall'utente con il campo <tt/button/ della struttura del +widget, e prendiamo tutti gli eventi del mouse con una chiamata alla +funzione <tt/gtk_grab_add()/. Successivi movimenti del mouse causano il +ricalcolo dei valori di controllo (fatto dalla funzione +<tt/gtk_dial_update_mouse/). Dipendentemente dalla politica che abbiamo +stabilito, gli eventi ``value_changed'' possono essere generati +istantaneamente (<tt/GTK_UPDATE_CONTINUOUS/), dopo un certo tempo aggiunto +con la funzione <tt/gtk_timeout_add()/ (<tt/GTK_UPDATE_DELAYED/), o +solamente quando il bottone del mouse e' rilasciato +(<tt/GTK_UPDATE_DISCONTINUOUS/). + +<tscreen><verb> +static gint +gtk_dial_button_press (GtkWidget *widget, + GdkEventButton *event) +{ + GtkDial *dial; + gint dx, dy; + double s, c; + double d_parallel; + double d_perpendicular; + + g_return_val_if_fail (widget != NULL, FALSE); + g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE); + g_return_val_if_fail (event != NULL, FALSE); + + dial = GTK_DIAL (widget); + + /* Determina se il bottone premuto era dentro la regione del puntatore: + lo facciamo calcolando la distanza parallela e + perpendicolare dal punto dove il bottone del mouse e' stato premuto + alla linea passante per il puntatore. */ + + dx = event->x - widget->allocation.width / 2; + dy = widget->allocation.height / 2 - event->y; + + s = sin(dial->angle); + c = cos(dial->angle); + + d_parallel = s*dy + c*dx; + d_perpendicular = fabs(s*dx - c*dy); + + if (!dial->button && + (d_perpendicular < dial->pointer_width/2) && + (d_parallel > - dial->pointer_width)) + { + gtk_grab_add (widget); + + dial->button = event->button; + + gtk_dial_update_mouse (dial, event->x, event->y); + } + + return FALSE; +} + +static gint +gtk_dial_button_release (GtkWidget *widget, + GdkEventButton *event) +{ + GtkDial *dial; + + g_return_val_if_fail (widget != NULL, FALSE); + g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE); + g_return_val_if_fail (event != NULL, FALSE); + + dial = GTK_DIAL (widget); + + if (dial->button == event->button) + { + gtk_grab_remove (widget); + + dial->button = 0; + + if (dial->policy == GTK_UPDATE_DELAYED) + gtk_timeout_remove (dial->timer); + + if ((dial->policy != GTK_UPDATE_CONTINUOUS) && + (dial->old_value != dial->adjustment->value)) + gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), "value_changed"); + } + + return FALSE; +} + +static gint +gtk_dial_motion_notify (GtkWidget *widget, + GdkEventMotion *event) +{ + GtkDial *dial; + GdkModifierType mods; + gint x, y, mask; + + g_return_val_if_fail (widget != NULL, FALSE); + g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE); + g_return_val_if_fail (event != NULL, FALSE); + + dial = GTK_DIAL (widget); + + if (dial->button != 0) + { + x = event->x; + y = event->y; + + if (event->is_hint || (event->window != widget->window)) + gdk_window_get_pointer (widget->window, &x, &y, &mods); + + switch (dial->button) + { + case 1: + mask = GDK_BUTTON1_MASK; + break; + case 2: + mask = GDK_BUTTON2_MASK; + break; + case 3: + mask = GDK_BUTTON3_MASK; + break; + default: + mask = 0; + break; + } + + if (mods & mask) + gtk_dial_update_mouse (dial, x,y); + } + + return FALSE; +} + +static gint +gtk_dial_timer (GtkDial *dial) +{ + g_return_val_if_fail (dial != NULL, FALSE); + g_return_val_if_fail (GTK_IS_DIAL (dial), FALSE); + + if (dial->policy == GTK_UPDATE_DELAYED) + gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), "value_changed"); + + return FALSE; +} + +static void +gtk_dial_update_mouse (GtkDial *dial, gint x, gint y) +{ + gint xc, yc; + gfloat old_value; + + g_return_if_fail (dial != NULL); + g_return_if_fail (GTK_IS_DIAL (dial)); + + xc = GTK_WIDGET(dial)->allocation.width / 2; + yc = GTK_WIDGET(dial)->allocation.height / 2; + + old_value = dial->adjustment->value; + dial->angle = atan2(yc-y, x-xc); + + if (dial->angle < -M_PI/2.) + dial->angle += 2*M_PI; + + if (dial->angle < -M_PI/6) + dial->angle = -M_PI/6; + + if (dial->angle > 7.*M_PI/6.) + dial->angle = 7.*M_PI/6.; + + dial->adjustment->value = dial->adjustment->lower + (7.*M_PI/6 - dial->angle) * + (dial->adjustment->upper - dial->adjustment->lower) / (4.*M_PI/3.); + + if (dial->adjustment->value != old_value) + { + if (dial->policy == GTK_UPDATE_CONTINUOUS) + { + gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), "value_changed"); + } + else + { + gtk_widget_draw (GTK_WIDGET(dial), NULL); + + if (dial->policy == GTK_UPDATE_DELAYED) + { + if (dial->timer) + gtk_timeout_remove (dial->timer); + + dial->timer = gtk_timeout_add (SCROLL_DELAY_LENGTH, + (GtkFunction) gtk_dial_timer, + (gpointer) dial); + } + } + } +} +</verb></tscreen> + +<p> +Cambiamenti esterni all'Adjustment sono comunicati al nostro widget +dai segnali ``changed'' e ``value_changed''. Il gestore per +queste funzioni chiama <tt/gtk_dial_update()/ per validare gli +argomenti, calcolare il nuovo angolo del puntatore e ridisegnare il +widget (chiamando <tt/gtk_widget_draw()/). + +<tscreen><verb> +static void +gtk_dial_update (GtkDial *dial) +{ + gfloat new_value; + + g_return_if_fail (dial != NULL); + g_return_if_fail (GTK_IS_DIAL (dial)); + + new_value = dial->adjustment->value; + + if (new_value < dial->adjustment->lower) + new_value = dial->adjustment->lower; + + if (new_value > dial->adjustment->upper) + new_value = dial->adjustment->upper; + + if (new_value != dial->adjustment->value) + { + dial->adjustment->value = new_value; + gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), "value_changed"); + } + + dial->angle = 7.*M_PI/6. - (new_value - dial->adjustment->lower) * 4.*M_PI/3. / + (dial->adjustment->upper - dial->adjustment->lower); + + gtk_widget_draw (GTK_WIDGET(dial), NULL); +} + +static void +gtk_dial_adjustment_changed (GtkAdjustment *adjustment, + gpointer data) +{ + GtkDial *dial; + + g_return_if_fail (adjustment != NULL); + g_return_if_fail (data != NULL); + + dial = GTK_DIAL (data); + + if ((dial->old_value != adjustment->value) || + (dial->old_lower != adjustment->lower) || + (dial->old_upper != adjustment->upper)) + { + gtk_dial_update (dial); + + dial->old_value = adjustment->value; + dial->old_lower = adjustment->lower; + dial->old_upper = adjustment->upper; + } +} + +static void +gtk_dial_adjustment_value_changed (GtkAdjustment *adjustment, + gpointer data) +{ + GtkDial *dial; + + g_return_if_fail (adjustment != NULL); + g_return_if_fail (data != NULL); + + dial = GTK_DIAL (data); + + if (dial->old_value != adjustment->value) + { + gtk_dial_update (dial); + + dial->old_value = adjustment->value; + } +} +</verb></tscreen> + +<sect2> Possibili Miglioramenti + +<p> + +Il widget Dial, da come l'abbiamo costruito, è lungo circa 670 linee +di codice C. Anche se questo potrebbe sembrare un po' troppo, abbiamo +realmente fatto un bel po' con quel tanto di codice, specialmente +considerando che molta della lunghezza è costituita da file header e +commmenti. Comunque ci sono alcuni miglioramenti che potrebbero essere +fatti a questo widget: + +<itemize> +<item> Se tu provate questo widget, troverete che ci sono alcuni lampeggiamenti +quando il puntatore viene trascinato in giro. Questo +perchè l'intero widget è cancellato ogni volta che il +puntatore viene mosso, prima di essere ridisegnato. Spesso, il modo migliore +per gestire questo tipo di problema è il disegnare il tutto su una +pixmap non visibile, poi copiare il risultato finale sullo schermo +in una passata sola (il widget ProgressBar viene disegnato in questo +modo). + +<item> L'utente potrebbe essere abilitato ad usare le frecce su e giu per +incrementare e diminuire il valore. + +<item> Potrebbe essere carino se il widget avesse i bottoni per +incrementare e decrementare il valore di step. Anche se potrebbe essere +possibile usare dei widget Bottone incorporati per questo, possiamo anche +far sì che il bottone sia auto-ripentente quando premuto, come le frecce +in una barra di scorrimento. Molto del codice per implementare questo tipo di +comportamento può essere trovato nel widget GtkRange. + +<item> il widget Dial potrebbe essere fatto/creato dentro un widget +contenitore con un singolo widget figlio posizionato all'inizio tra i +2 bottoni menzionati prima. L'utente potrebbe poi aggiungere o una etichetta +o un widget ``entry'' per mostrare il valore corrente del dial. + +</itemize> + +<sect1> Impararne di più + +<p> +Fin qui abbiamo esposto solo una piccola parte di tutto quello che serve +per creare un widget. Se volete davvero scrivere un vostro widget, la +miglior risorsa di esempi è lo stesso codice sorgente GTK. Chiedete a voi +stessi alcune cose su come deve essere il widget che volete scrivere: è +un widget contenitore? dovrà avere una propria finestra? è una modifica di +un widget precedente? Trovate poi un widget simile e iniziate a fargli +delle modifiche. +Buone Fortuna. + + +<sect>Scribble, Un semplice esempio di Programma di Disegno + +<sect1> Panoramica + +<p> +In questa sezione, creeremo un semplice programma di disegno. Durante +questo processo, esamineremo come gestire gli eventi generati dal mouse, +come disegnare all'interno di una finestra e come disegnare in modo migliore +usando una pixmap di supporto. Dopo averlo creato, lo amplieremo aggiungendo +il supporto per i dispositivi XInput, per esempio le tavolette grafiche. +Il GTK fornisce delle routine di supporto grazie alle quali risulta piuttosto +semplice ottenere informazioni estese, come la pressione o l'inclinazione. + +<sect1> Gestione degli Eventi + +<p> +I segnali di GTK che abbiamo discusso finora si riferivano ad azioni di +alto livello, ad esempio la selezione di un elemento di un menù. Però, a volte +è utile sapere qualcosa su cose che si svolgono a livello più basso livello, +come possono essere il movimento del mouse o la pressione di un tasto. +Ci sono segnali di GTK anche per questi <em>eventi</em> di basso livello. +I gestori di questo tipo di segnali hanno un parametro caratteristico in più, +che è il puntatore ad una struttura che contiene informazioni riguardo +all'evento. Per esempio, ai gestori di eventi che riguardano dei movimenti, +si passa un puntatore ad una struttura GdkEventMotion, che è fatta (in parte) +così: + +<tscreen><verb> +struct _GdkEventMotion +{ + GdkEventType type; + GdkWindow *window; + guint32 time; + gdouble x; + gdouble y; + ... + guint state; + ... +}; +</verb></tscreen> + +<tt/type/ avrà il valore del tipo di evento, in questo caso +<tt/GDK_MOTION_NOTIFY/, <tt/window/ rappresenta la finestra in cui l'evento +si è verificato. <tt/x/ e <tt/y/ forniscono le coordinate dell'evento e +<tt/state/ specifica lo stato dei modificatori nel momento in cui l'evento +si è verificato (cioè, specifica quali tasti modificatori e tasti del mouse +erano premuti in quel momento). E' un OR bit per bit dei seguenti valori: + +<tscreen><verb> +GDK_SHIFT_MASK +GDK_LOCK_MASK +GDK_CONTROL_MASK +GDK_MOD1_MASK +GDK_MOD2_MASK +GDK_MOD3_MASK +GDK_MOD4_MASK +GDK_MOD5_MASK +GDK_BUTTON1_MASK +GDK_BUTTON2_MASK +GDK_BUTTON3_MASK +GDK_BUTTON4_MASK +GDK_BUTTON5_MASK +</verb></tscreen> + +<p> +Come succede per gli altri segnali, per determinare cosa deve accadere in +corrispondenza di un evento, si chiama <tt>gtk_signal_connect()</tt>. Ma +è anche necessario far sì che GTK sappia di quali eventi vogliamo essere +informati. A questo fine, chiamiamo la funzione: + +<tscreen><verb> +void gtk_widget_set_events (GtkWidget *widget, gint events); +</verb></tscreen> + +Il secondo campo specifica gli eventi che ci interessano. Si tratta dell'OR +bit per bit delle costanti che identificano i diversi tipi di eventi. La lista +dei tipi di eventi è la seguente: + +<tscreen><verb> +GDK_EXPOSURE_MASK +GDK_POINTER_MOTION_MASK +GDK_POINTER_MOTION_HINT_MASK +GDK_BUTTON_MOTION_MASK +GDK_BUTTON1_MOTION_MASK +GDK_BUTTON2_MOTION_MASK +GDK_BUTTON3_MOTION_MASK +GDK_BUTTON_PRESS_MASK +GDK_BUTTON_RELEASE_MASK +GDK_KEY_PRESS_MASK +GDK_KEY_RELEASE_MASK +GDK_ENTER_NOTIFY_MASK +GDK_LEAVE_NOTIFY_MASK +GDK_FOCUS_CHANGE_MASK +GDK_STRUCTURE_MASK +GDK_PROPERTY_CHANGE_MASK +GDK_PROXIMITY_IN_MASK +GDK_PROXIMITY_OUT_MASK +</verb></tscreen> + +Per chiamare <tt/gtk_widget_set_events()/, si devono fare alcune osservazioni +sottili. In primo luogo, la si deve chiamare prima che sia stata creata la +finestra X per il widget GTK. In pratica, ciò significa che la si deve +chiamare subito dopo aver creato il widget. In secondo luogo, il widget +deve avere una finestra X associata. Molti widget, per ragioni di +efficienza, non hanno una propria finetra, e vengono mostrati nella +finestra madre. Questi widget sono: + +<tscreen><verb> +GtkAlignment +GtkArrow +GtkBin +GtkBox +GtkImage +GtkItem +GtkLabel +GtkPaned +GtkPixmap +GtkScrolledWindow +GtkSeparator +GtkTable +GtkViewport +GtkAspectFrame +GtkFrame +GtkVPaned +GtkHPaned +GtkVBox +GtkHBox +GtkVSeparator +GtkHSeparator +</verb></tscreen> + +Per catturare degli eventi per questo tipo di widget, si deve fare uso +del widget EventBox. Si veda a questo proposito la sezione su +<ref id="sec_The_EventBox_Widget" name="The EventBox Widget">. + +<p> +Per il nostro programma di disegno, vogliamo sapere quando il pulsante del +mouse è premuto e quando viene mosso, quindi specificheremo +<tt/GDK_POINTER_MOTION_MASK/ e <tt/GDK_BUTTON_PRESS_MASK/. Vogliamo anche +essere informati su quando è necessario ridisegnare la nostra finestra, +quindi specifichiamo <tt/GDK_EXPOSURE_MASK/. Anche se vogliamo essere +avvertiti con un evento ``Configure'' se la dimensione della nostra finestra +cambia, non è necessario specificare il flag <tt/GDK_STRUCTURE_MASK/, dal +momento che questo viene specificato automaticamente per tutte le finestre. + +<p> +Risulta, conunque, che specificando semplicemente <tt/GDK_POINTER_MOTION_MASK/ +si crea un problema. Ciò infatti fa sì che il server aggiunga nella coda un +un nuovo evento di movimento ogni volta che l'utente muovoe il mouse. Immaginate +che ci vogliano 0.1 secondi per gestire uno di questi eventi, e che il server +X metta in coda un nuovo evento ogni 0.05 secondi. Rimarremo ben presto indietro +rispetto al disegno dell'utente. Se l'utente disegna per 5 secondi, ci metteremmo +altri 5 secondi prima di finire dopo che l'utente ha rilasciato il pulsante del +mouse! Vorremmo quindi che venga notificato un solo evento di movimento per +ogni evento che processiamo. Il modo per farlo è di specificare +<tt/GDK_POINTER_MOTION_HINT_MASK/. + +<p> +Quando specifichiamo <tt/GDK_POINTER_MOTION_HINT_MASK/, il server ci notifica +un evento di movimento la prima volta che il puntatore si muove dopo essere +entrato nella nostra finestra, oppure dopo ogni rilascio di un pulsante del +mouse. Gli altri eventi di movimento verranno soppressi finché non richiediamo +esplicitamente la posizione del puntatore con la funzione: + +<tscreen><verb> +GdkWindow* gdk_window_get_pointer (GdkWindow *window, + gint *x, + gint *y, + GdkModifierType *mask); +</verb></tscreen> + +(c'è anche un'altra funzione, <tt>gtk_widget_get_pointer()</tt>, che ha +un'interfaccia più semplice, ma che non risulta molto utile dal momento +che restituisce solo la posizione del puntatore, senza dettagli sullo +sato dei pulsanti.) + +<p> +Quindi, il codice per assegnare gli eventi per la nostra finestra, avrà l'aspetto: + +<tscreen><verb> + gtk_signal_connect (GTK_OBJECT (drawing_area), "expose_event", + (GtkSignalFunc) expose_event, NULL); + gtk_signal_connect (GTK_OBJECT(drawing_area),"configure_event", + (GtkSignalFunc) configure_event, NULL); + gtk_signal_connect (GTK_OBJECT (drawing_area), "motion_notify_event", + (GtkSignalFunc) motion_notify_event, NULL); + gtk_signal_connect (GTK_OBJECT (drawing_area), "button_press_event", + (GtkSignalFunc) button_press_event, NULL); + + gtk_widget_set_events (drawing_area, GDK_EXPOSURE_MASK + | GDK_LEAVE_NOTIFY_MASK + | GDK_BUTTON_PRESS_MASK + | GDK_POINTER_MOTION_MASK + | GDK_POINTER_MOTION_HINT_MASK); +</verb></tscreen> + +Teniamo per dopo i gestori di ``expose_event'' e ``configure_event''. Quelli di +``motion_notify_event'' e ``button_press_event'' sono piuttosto semplici: + +<tscreen><verb> +static gint +button_press_event (GtkWidget *widget, GdkEventButton *event) +{ + if (event->button == 1 && pixmap != NULL) + draw_brush (widget, event->x, event->y); + + return TRUE; +} + +static gint +motion_notify_event (GtkWidget *widget, GdkEventMotion *event) +{ + int x, y; + GdkModifierType state; + + if (event->is_hint) + gdk_window_get_pointer (event->window, &x, &y, &state); + else + { + x = event->x; + y = event->y; + state = event->state; + } + + if (state & GDK_BUTTON1_MASK && pixmap != NULL) + draw_brush (widget, x, y); + + return TRUE; +} +</verb></tscreen> + + +<sect1> Il widget Area di Disegno (DrawingArea) e il procedimento per Disegnare + +<p> +Vediamo ora il procedimento per disegnare sullo schermo. Il +widget da usare è l'Area di Disegno (DrawingArea). Essenzialmente si +tratta di una finestra X e nient'altro. E' una tela bianca su cui possimo +disegnare tutto quello che vogliamo. Per crearne una usiamo la chiamata: + +<tscreen><verb> +GtkWidget* gtk_drawing_area_new (void); +</verb></tscreen> + +Per specificare una dimensione predefinita, si puo fare: + +<tscreen><verb> +void gtk_drawing_area_size (GtkDrawingArea *darea, + gint width, + gint height); +</verb></tscreen> + +Come è vero per tutti i widget, si può modificare questa dimensione +predefinita, tramite la chamata a <tt>gtk_widget_set_usize()</tt>, e +questa a sua volta può essere modificata dall'utente ridimensionando +manualmente la finestra che contiene l'area di disegno. + +<p> +Si deve notare che nel momento in cui creiamo un widget DrawingArea, siamo +<em>completamente</em> responsabili di disegnarne il contenuto. Se ad +esempio la nostra finestra viene prima nascosta e poi dinuovo portata in +primo piano, otteniamo un evento di ``esposizione'' e doppiamo ridisegnare +ciò che era stato precedente nascosto. + +<p> +Dover ricordare tutto quello che era disegnato sulla finestra in modo da +poterlo ridisegnare successivamente, può essere, come minimo, noioso. +In più, può essere spiacevole dal punto di vista visivo, se delle porzioni +dello schermo vengono prima cancellate e poi ridisegnate passo per passo. +La soluzione per questo problema è di usare una <em>pixmap di supporto</em>. +Invece di disegnare direttamente sullo schermo, disegnamo su un'iimagine +conservata nella memoria del server ma che non viene mostrata; quindi, quando +l'immagine cambia o ne vengono mostrate nuove porzioni, copiamo sullo schermo +le parti corrispondenti. + +<p> +Per creare una ppixmap fuori dallo schermo, usiamo la funzione: + +<tscreen><verb> +GdkPixmap* gdk_pixmap_new (GdkWindow *window, + gint width, + gint height, + gint depth); +</verb></tscreen> + +Il parametro <tt>window</tt>specifica una finestra GDK dalla quale questa +pixmap prende alcune delle sue proprietà. <tt>width</tt> e <tt>height</tt> +specificano le dimensioni della pixmap. <tt>depth</tt> specifica la +<em>profondità di colore</em>, cioè il numero di bit per ogni pixel, per +la nuova pixmap. Se alla profondità è assegnato il valore <tt>-1</tt>, questa +verrà posta identica a quella di <tt>window</tt>. + +<p> +Creiamo la pixmap all'interno del gestore di ``configure_event''. Questo evento +è generato ogni volta che la finestra cambia di dimensione, compreso il +momento in cui viene creata per la prima volta. + +<tscreen><verb> +/* Pixmap di supporto per l'area di disegno */ +static GdkPixmap *pixmap = NULL; + +/* Creare una pixmap della dimensione appropriata */ +static gint +configure_event (GtkWidget *widget, GdkEventConfigure *event) +{ + if (pixmap) + { + gdk_pixmap_destroy(pixmap); + } + pixmap = gdk_pixmap_new(widget->window, + widget->allocation.width, + widget->allocation.height, + -1); + gdk_draw_rectangle (pixmap, + widget->style->white_gc, + TRUE, + 0, 0, + widget->allocation.width, + widget->allocation.height); + + return TRUE; +} +</verb></tscreen> + +La chiamata a <tt>gdk_draw_rectangle()</tt> inizialmente rende bianca l'intera +pixmap. Fra un momento ne riparleremo. + +<p> +Il gestore dell'evento ``esposizione'', copia quindi la porzione appropriata +della pixmap sullo schermo (determiniamo qual è l'area da ridisegnare usando +il campo event->area dell'evento di esposizione): + +<tscreen><verb> +/* Ridisegna sullo schermo a partire dalla pixmap di supporto */ +static gint +expose_event (GtkWidget *widget, GdkEventExpose *event) +{ + gdk_draw_pixmap(widget->window, + widget->style->fg_gc[GTK_WIDGET_STATE (widget)], + pixmap, + event->area.x, event->area.y, + event->area.x, event->area.y, + event->area.width, event->area.height); + + return FALSE; +} +</verb></tscreen> + +Abbiamo quindi visto come tenete aggiornato lo schermo con la nostra +pixmap, ma come facciamo per disegnare delle cose interessanti sulla +pixmap? Ci sono un bel po' di funzioni nella libreria GDK di GTK che +servono per disegnare su superfici <em>disegnabili</em>. Una superficie +disegnabile è semplicemente qualcosa su cui si può disegnare un'immagine. +Può essere una finestra, una pixmap o una bitmap (un'immagine in bianco e +nero). Abbiamo già visto sopra due di chiamate, +<tt>gdk_draw_rectangle()</tt> and <tt>gdk_draw_pixmap()</tt>. La lista +completa è la seguente: + +<tscreen><verb> +gdk_draw_line () +gdk_draw_rectangle () +gdk_draw_arc () +gdk_draw_polygon () +gdk_draw_string () +gdk_draw_text () +gdk_draw_pixmap () +gdk_draw_bitmap () +gdk_draw_image () +gdk_draw_points () +gdk_draw_segments () +</verb></tscreen> + +Per ulteriori dettagli su queste funzioni, vedete la documentazione di +riferimento nei file header <tt><gdk/gdk.h></tt>. +Tutte queste funzioni hanno i medesimi primi due argomenti. Il primo +è la superficie disegnabili su cui disegnare, il secondo è un +<em>contesto grafico</em> (GC). + +<p> +Un contesto grafico incapsula delle informazioni riguardo a cose come +il colore di sfondo e di primo piano e lo spessore della linea. +GDK ha un ampio insieme di funzioni per crare e modificare contesti grafici, +ma per tenere le cose semplici useremo solo dei contesti grafici predefiniti. +Ogni widget ha uno stile associato (che può essere modificato agendo su un +file gtkrc). Questo, fra le altre cose, contiene un certo numero di contesti +grafici. Alcuni esempi di come accedere a questi contesti grafici sono +i seguenti: + +<tscreen><verb> +widget->style->white_gc +widget->style->black_gc +widget->style->fg_gc[GTK_STATE_NORMAL] +widget->style->bg_gc[GTK_WIDGET_STATE(widget)] +</verb></tscreen> + +I campi <tt>fg_gc</tt>, <tt>bg_gc</tt>, <tt>dark_gc</tt>, e +<tt>light_gc</tt> sono indicizzati tramite un parametri di tipo +<tt>GtkStateType</tt>, che può assumere i valori: + +<tscreen><verb> +GTK_STATE_NORMAL, +GTK_STATE_ACTIVE, +GTK_STATE_PRELIGHT, +GTK_STATE_SELECTED, +GTK_STATE_INSENSITIVE +</verb></tscreen> + +Per esempio, per <tt/GTK_STATE_SELECTED/ il colore di sfondo predefinito +è blu scuro e quello di primo piano bianco. + +<p> +La nostra funzione <tt>draw_brush()</tt>, che efettivamente disegna sullo +schermo, diventa quindi: + +<tscreen><verb> +/* Disegna un rettangolo sullo schermo */ +static void +draw_brush (GtkWidget *widget, gdouble x, gdouble y) +{ + GdkRectangle update_rect; + + update_rect.x = x - 5; + update_rect.y = y - 5; + update_rect.width = 10; + update_rect.height = 10; + gdk_draw_rectangle (pixmap, + widget->style->black_gc, + TRUE, + update_rect.x, update_rect.y, + update_rect.width, update_rect.height); + gtk_widget_draw (widget, &update_rect); +} +</verb></tscreen> + +Dopo aver disegnato il rettangolo sulla pixmap, chiamiamo la funzione: + +<tscreen><verb> +void gtk_widget_draw (GtkWidget *widget, + GdkRectangle *area); +</verb></tscreen> + +che notifica a X che l'area data dal parametro <tt>area</tt> deve essere +aggiornata. X poi genererà un evento di esposizione (che può essere combinato +con le aree passate da diverse chiamate a <tt>gtk_widget_draw()</tt>) che +farà sì che il nostro gestore dell'evento di esposizione, copi le porzioni +rilevanti sullo schermo. + +<p> +Abbiamo a questo punto creato tutto il programma di disegno, tranne che +per qualche dettaglio irrilevante come la creazione della finestra principale. +Il codice sorgente completo è reperibile dove avete ottenuto questo tutorial. + +<sect1> Aggiungere il supporto per XInput + +<p> +Al giorno d'oggi è possibile acquistare dei dispositivi abbastanza a buon +mercato, come tavolette grafice, che permettono di disegnare con una +espressività artistica molto semplificata rispetto ad un mouse. +Il modo più semplice per usare questi dispositivi è di sostituirli +semplicemente al mouse, ma in questo modo si perdono molti dei loro +vantaggi, come: + +<itemize> +<item> Sensibilità alla pressione +<item> Sensibilità all'inclinazione +<item> Posizionamento infra-pixel +<item> Ingressi multipli (per esempio, uno stilo che contiene sia una ``matita'' +sia una ``gomma'') +</itemize> + +Per ulteriori informazioni sulle estensioni XInput, vedere l'<url +url="http://www.msc.cornell.edu/~otaylor/xinput/XInput-HOWTO.html" +name="XInput-HOWTO">. + +<p> +Se esaminiamo, per esempio, la definizione completa della struttura +GdkEventMotion, possiamo vedere che contiene dei campi per il supporto +delle informazioni estese dai dispositivi. + +<tscreen><verb> +struct _GdkEventMotion +{ + GdkEventType type; + GdkWindow *window; + guint32 time; + gdouble x; + gdouble y; + gdouble pressure; + gdouble xtilt; + gdouble ytilt; + guint state; + gint16 is_hint; + GdkInputSource source; + guint32 deviceid; +}; +</verb></tscreen> + +<tt/pressure/ fornisce la pressione sotto forma di un numero decimale +compreso fra 0 e 1. <tt/xtilt/ e <tt/ytilt/ possono assumere valori +compresi fra -1 e 1, corrispondenti al grado di inclinazione in ciascuna +direzione. <tt/source/ e <tt/deviceid/ specificano il dispositivo per il +quale si è verificato l'evento in due modi distinti. <tt/source/ da alcune +semplici informazioni sul tipo di dispositivo, e può assumere i valori: + +<tscreen><verb> +GDK_SOURCE_MOUSE +GDK_SOURCE_PEN +GDK_SOURCE_ERASER +GDK_SOURCE_CURSOR +</verb></tscreen> + +<tt/deviceid/ specifica invece un identificativo numerico univoco per il +dispositivo. Questo può essere a sua volta utilizzato per avere ulteriori +informazioni sul dispositivo tramite la chiamata a <tt/gdk_input_list_devices()/ +(vedi sotto). Il valore speciale <tt/GDK_CORE_POINTER/ viene usato per identificare +il dispositivo di puntamento principale (di solito il mouse). + +<sect2> Abilitare le informazioni estese + +<p> +Per far sì che GTK sappia che ci interessano le informazioni estese dai +dispositivi, basta aggiungere un'unica linea al nostro programma: + +<tscreen><verb> +gtk_widget_set_extension_events (drawing_area, GDK_EXTENSION_EVENTS_CURSOR); +</verb></tscreen> + +Dando il valore <tt/GDK_EXTENSION_EVENTS_CURSOR/, diciamo che ci interessano +gli eventi relativi alle estensioni, ma solo se non dobbiamo disegnare da noi +il nostro cursore. Si veda più sotto alla sezione <ref +id="sec_Further_Sophistications" name="Ulteriori Sofisticazioni"> per ulteriori +informazioni sul modo si disegnare i cursori. Potremmo anche dare i valori +<tt/GDK_EXTENSION_EVENTS_ALL/ se vogliamo disegnare il nostro cursore o +<tt/GDK_EXTENSION_EVENTS_NONE/ se vogliamo tornare alle condizioni predefinite. + +<p> +Comunque, non finisce tutto qui. Non ci sono estensioni abilitate per difetto. +Abbiamo bisogno di un meccanismo per permettere agli utenti l'abilitazione e +la configurazione delle estensioni dei loro dispositivi, GTK fornisce il +widget InputDialog per automatizzare questo processo. La seguente procedura +mostra come gestire un widget InputDialog. Crea la finestra di dialogo nel +caso non sia presente, mentre la porta in primo piano in caso contrario. + +<tscreen><verb> +void +input_dialog_destroy (GtkWidget *w, gpointer data) +{ + *((GtkWidget **)data) = NULL; +} + +void +create_input_dialog () +{ + static GtkWidget *inputd = NULL; + + if (!inputd) + { + inputd = gtk_input_dialog_new(); + + gtk_signal_connect (GTK_OBJECT(inputd), "destroy", + (GtkSignalFunc)input_dialog_destroy, &inputd); + gtk_signal_connect_object (GTK_OBJECT(GTK_INPUT_DIALOG(inputd)->close_button), + "clicked", + (GtkSignalFunc)gtk_widget_hide, + GTK_OBJECT(inputd)); + gtk_widget_hide ( GTK_INPUT_DIALOG(inputd)->save_button); + + gtk_widget_show (inputd); + } + else + { + if (!GTK_WIDGET_MAPPED(inputd)) + gtk_widget_show(inputd); + else + gdk_window_raise(inputd->window); + } +} +</verb></tscreen> + +(Notate come gestiamo questo dialogo. Con la connessione del segnale +``destroy'' ci assicuriamo di non tenerci in giro il puntatore al dialogo +dopo che lo abbiamo distrutto, cosa che potrebbe portare ad un errore di +segmentazione.) + +<p> +L'InputDialog ha due pulsanti, ``Close'' e ``Save'', i quali non hanno alcuna +azione predefinita assegnata ad essi. Nella funzione precedente, abbiamo +fatto in modo che ``Close'' nasconda la finestra di dialogo, e abbiamo nascosto +il pulsante ``Save'' dal momento che in questo programma non implementiamo il +salvataggio delle opzioni di XInput. + +<sect2> Usare le informazioni estese + +<p> +Una volta abilitato il dipositivo, possiamo usare le informazioni estese +che si trovano nei corrispondenti campi delle strutture che descrivono gli +eventi. A dire il vero, l'utilizzo di questi campi è sempre sicuro, perché +sono tutti posti per difetto a valori ragionevoli ancje quando la gestione +degli eventi estesi non è abilitata. + +<p> +Un cambiamento che dobbiamo fare è di chiamare <tt/gdk_input_window_get_pointer()/ +invece di <tt/gdk_window_get_pointer/. Ciò si rende necessario perché +<tt/gdk_window_get_pointer/ non restituisce le informazioni esetese. + +<tscreen><verb> +void gdk_input_window_get_pointer (GdkWindow *window, + guint32 deviceid, + gdouble *x, + gdouble *y, + gdouble *pressure, + gdouble *xtilt, + gdouble *ytilt, + GdkModifierType *mask); +</verb></tscreen> + +Quando chiamiamo questa funzione, dobbiamo specificare l'identificativo +del dispositivo e la finestra. Normalmente questo identificativo lo si +ottiene dal campo <tt/deviceid/ della struttura dell'evento. +Questa funzione restituirà valori ragionevoli nel caso che la gestione +degli eventi estesi non sia attivata (in questo caso, <tt/event->deviceid/ +avrà il valore <tt/GDK_CORE_POINTER/). + +Quindi, la struttura di base dei gestori degli eventi relativi alla +pressione di bottoni e ai movomenti non cambia molto - abbiamo solo +bisogno di aggiungere il codice necessario per tenere conto delle +informazioni estese. + +<tscreen><verb> +static gint +button_press_event (GtkWidget *widget, GdkEventButton *event) +{ + print_button_press (event->deviceid); + + if (event->button == 1 && pixmap != NULL) + draw_brush (widget, event->source, event->x, event->y, event->pressure); + + return TRUE; +} + +static gint +motion_notify_event (GtkWidget *widget, GdkEventMotion *event) +{ + gdouble x, y; + gdouble pressure; + GdkModifierType state; + + if (event->is_hint) + gdk_input_window_get_pointer (event->window, event->deviceid, + &x, &y, &pressure, NULL, NULL, &state); + else + { + x = event->x; + y = event->y; + pressure = event->pressure; + state = event->state; + } + + if (state & GDK_BUTTON1_MASK && pixmap != NULL) + draw_brush (widget, event->source, x, y, pressure); + + return TRUE; +} +</verb></tscreen> + +Avremo anche bisogno di fare qualcosa con queste nuove informazioni. La +nostra nuova funzione <tt/draw_brush/ disegna con un colore diverso per +ogni <tt/event->source/ e cambia la dimensione della linea in funzione +della pressione. + +<tscreen><verb> +/* Disegna un rettangolo sullo schermo, con la dimensione dipendente + dalla pressione e il colore dipendente dal tipo di dispositivo */ +static void +draw_brush (GtkWidget *widget, GdkInputSource source, + gdouble x, gdouble y, gdouble pressure) +{ + GdkGC *gc; + GdkRectangle update_rect; + + switch (source) + { + case GDK_SOURCE_MOUSE: + gc = widget->style->dark_gc[GTK_WIDGET_STATE (widget)]; + break; + case GDK_SOURCE_PEN: + gc = widget->style->black_gc; + break; + case GDK_SOURCE_ERASER: + gc = widget->style->white_gc; + break; + default: + gc = widget->style->light_gc[GTK_WIDGET_STATE (widget)]; + } + + update_rect.x = x - 10 * pressure; + update_rect.y = y - 10 * pressure; + update_rect.width = 20 * pressure; + update_rect.height = 20 * pressure; + gdk_draw_rectangle (pixmap, gc, TRUE, + update_rect.x, update_rect.y, + update_rect.width, update_rect.height); + gtk_widget_draw (widget, &update_rect); +} +</verb></tscreen> + +<sect2> Trovare ulteriori informazioni su di un dispositivo + +<p> +Come esempio del modo di trovare altre informazioni su di un dispositivo, +il nostro programma stamperà il nome di ogni dispositivo che genera un +evento di pressione di un pulsante. Per avere il nome di un dispositivo, +chiamiamo la funzione + +<tscreen><verb> +GList *gdk_input_list_devices (void); +</verb></tscreen> + +che restituisce una GList (un tipo di lista collegata che si trova nella +libreria glib) di strutture di tipo GdkDeviceInfo. La definizione di +GdkDeviceInfo è la seguente: + +<tscreen><verb> +struct _GdkDeviceInfo +{ + guint32 deviceid; + gchar *name; + GdkInputSource source; + GdkInputMode mode; + gint has_cursor; + gint num_axes; + GdkAxisUse *axes; + gint num_keys; + GdkDeviceKey *keys; +}; +</verb></tscreen> + +La maggior parte di questi campi rappresentano informazioni di configurazione +che potete ignorare a meno che non implementiate il salvataggio della +configurazione di un XInput. Quelle che ci interessano sono <tt/name/, che +è semplicemente il nome che X assegna al dispositivo, e <tt/has_cursor/. Anche +<tt/has_cursor/ non è informazione di configurazione, e indica, nel caso +abbia valore ``falso'', che dobbiamo disegnare da soli il nostro cursore. Ma +dal momento che abbiamo specificato <tt/GDK_EXTENSION_EVENTS_CURSOR/, +possiamo anche non preoccuparcene. + +<p> + +La nostra funzione <tt/print_button_press()/ scorre semplicemente la lista +che è stata restituita finché non trova il valore corretto, e poi stampa +il nome del dispositivo. + +<tscreen><verb> +static void +print_button_press (guint32 deviceid) +{ + GList *tmp_list; + + /* gdk_input_list_devices restituisce una lista interna, così poi + non dobbiamo liberarla */ + tmp_list = gdk_input_list_devices(); + + while (tmp_list) + { + GdkDeviceInfo *info = (GdkDeviceInfo *)tmp_list->data; + + if (info->deviceid == deviceid) + { + printf("Button press on device '%s'\n", info->name); + return; + } + + tmp_list = tmp_list->next; + } +} +</verb></tscreen> +Questo completa i cambiamenti necessari per usare gli XInput nel nostro +programma. Come per la prima versione, i sorgenti completi sono prelevabili +da dove avete prelevato questo tutorial. + +<sect2> Ulteriori sofisticazioni <label id="sec_Further_Sophistications"> + +<p> +Anche se ora il nostro programma supporta XInput pittosto bene, gli mancano +alcune caratteristiche che probabilmente vorremmo mettere in una applicazione +completa. In primo luogo, probabilmente all'utente non farà piacere dover +configurare i propri dispositivi ogni volta che lanciano il programma, per +cui dovremmo dare la possibilità di salvare la configurazione dei dispositivi. +Ciò può essere fatto scorrendo la lista restituita da <tt/gdk_input_list_devices()/ +e scrivendo la configurazione su di un file. + +<p> +Per tornare allo stato salvato la prossima volta che il programma viene +eseguito, GDK mette a disposizione delle funzioni per cambiare la configurazione +dei dispositivi: + +<tscreen><verb> +gdk_input_set_extension_events() +gdk_input_set_source() +gdk_input_set_mode() +gdk_input_set_axes() +gdk_input_set_key() +</verb></tscreen> + +(La lista restituita da <tt/gdk_input_list_devices()/ non dovrebbe +essere modificata direttamente.) Un esempio di come fare può essere +trovato nel programma di disegno gsumi (disponibile da <htmlurl +url="http://www.msc.cornell.edu/~otaylor/gsumi/" +name="http://www.msc.cornell.edu/~otaylor/gsumi/">). Sarebbe bello +avere alla fine un modo standard di recuperare le informazioni per tutte +le applicazioni. Questo probabilmente appartiene ad un livello un po' +più elevato ripetto a GTK, forse alla libreria GNOME. + +<p> +Un'altra notevole omissione a cui abbiamo accennato precedentemente è il +fatto di non disegnare il cursore direttamente. Piattaforme diverse da +XFree86 non permettono in questo momento di usare contemporaneamente un +dispositivo sia come puntatore principale sia direttamente da una +applicazione. Vedere <url url="http://www.msc.cornell.edu/~otaylor/xinput/XInput-HOWTO.html" +name="XInput-HOWTO"> per ulteriori informazioni. Ciò significa che le +applicazioni che vogliono rivolgersi al pubblico più ampio dovranno prevedere +di disegnare esse stesse il proprio cursore. + +<p> +Un'applicazione che voglia disegnare il proprio cursore dovrà fare due cose: +determinare se il dispositivo corrente necessita che venga disegnato un +cursore, e determinare se il dispositivo corrente è in prossimità. (Se il +dispositivo è una tavoletta grafica, un tocco di finezza è fare sparire +il puntatore quando lo stilo viene sollevato dalla tavoletta. Quando c'è +contatto fra lo stilo e la tavoletta, si dice che il dispositivo è ``in +prossimità".) La prima cosa viene fatta scorrendo la lista dei dispositivi, +come abbiamo fatto per trovare il nome del dispositivo. La seconda cosa +viene ottenuta selezionando gli eventi ``proximity_out''. Un esempio di +disegno del proprio cursore si trova nel programma 'testinput' incluso nella +distribuzione di GTK. + +<sect>Consigli per scrivere Applicazioni GTK + +<p> + +Questa sezione è semplicemente una raccolta di saggezza, una +guida di stile e un aiuto per creare buone applicazioni GTK. E' totalmente +inutile per ora perché è solamente un appunto. + +Usa autoconf e automake! Sono tuoi amici :) Ho intenzione di fare una +piccola introduzione su di loro qui. + +<sect>Contributi +<p> + +Questo documento, come molti altri grandi software fuori di qui, è stato +creato da volontari. Se sai tutto quello che c'è da sapere su GTK e non +lo hai trovato qui allora considera la possibilità di contribuire a questo +documento. + +<p> +Se decidi di contribuire, per favore trasmettimi il tuo testo a +<tt><htmlurl url="mailto:slow@intergate.bc.ca" +name="slow@intergate.bc.ca"></tt>. Inoltre, Si consapevole che l'intero +documento è ``free'', e ogni tua aggiunta sarà considerata allo stesso modo. +Per questo motivo le persone possono usare porzioni dei tuoi esempi nei loro +programmi, copie di questo documento possono essere distribuite all'infinito, +ecc... + +<p> + +Grazie. + + +<sect>Credits +<p> +Voglio qui ringraziare le persone che seguono, per il loro contributo +alla stesura di questo testo. + +<itemize> +<item>Bawer Dagdeviren, <tt><htmlurl url="mailto:chamele0n@geocities.com" +name="chamele0n@geocities.com"></tt> per il tutorial sui menù. + +<item>Raph Levien, <tt><htmlurl url="mailto:raph@acm.org" + name="raph@acm.org"></tt> +per il "hello world" alla GTK, l'immpacchettamento del widget, e in generale +per tutta la sua saggezza. +Lui ha anche donato una casa per questo tutorial. + +<item>Peter Mattis, <tt><htmlurl url="mailto:petm@xcf.berkeley.edu" +name="petm@xcf.berkeley.edu"></tt> Per il più semplice programma GTK e l'abilità +di farlo. :) + +<item>Werner Koch <tt><htmlurl url="mailto:werner.koch@guug.de" +name="werner.koch@guug.de"></tt> per la conversione da testo semplice a SGML +e la gerarchia delle classi di widget. + +<item>Mark Crichton <tt><htmlurl url="mailto:crichton@expert.cc.purdue.edu" +name="crichton@expert.cc.purdue.edu"></tt> per il codice della "MenuFactory" +e per la parte sull'impacchettamento nelle tabelle del tutorial. + +<item>Owen Taylor <tt><htmlurl url="mailto:owt1@cornell.edu" +name="mailto:owt1@cornell.edu"></tt> per la sezione del widget EventBox +(e il patch alla distribuzione). Lui è anche responsabile per il codice +e il tutorial delle selezioni, come per la sezione sulla scrittura di un +proprio widget, e l'applicazione d'esempio. Grazie di tutto Owen. + +<item>Mark VanderBoom <tt><htmlurl url="mailto:mvboom42@calvin.edu" +name="mailto:mailto:mvboom42@calvin.edu"></tt> per il suo meraviglioso lavoro +sul Notebook, Progres Bar, Dialogs e File selection. Grazie molto Mark. Sei +stato di grande aiuto. + +<item>Tim Janik <tt><htmlurl url="mailto:timj@psynet.net" +name="mailto:timj@psynet.net"></tt> per il suo grande lavoro sul widget List. +Grazie Tim :) + +<item> Michael K. Johnson <tt><htmlurl url="mailto:johnsonm@redhat.com" +name="johnsonm@redhat.com"> </tt> per le informazioni e il codice dei menu +a comparsa. + +</itemize> +<p> +E a tutti voi che avete fatto commenti e avete aiutato a raffinare questo documento. +<p> + +Thanks. + +<sect> Copying +<p> +This tutorial is Copyright (c) 1997 Ian Main + +La traduzione italiana è sotto Copyright (c) 1997-1998 di Michel Morelli, +Daniele Canazza e Antonio Schifano. + +<p> + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. +<p> +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +<p> +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +</article> + |