17.15 EVENT PROCESSING IN GNOMEGTK


17.15 EVENT PROCESSING IN GNOME/GTK+

An event processing loop in a GNOME/GTK+ program is started by invoking gtk_main() and terminated by invoking gtk_main_quit(). Invocation of gtk_main() basically wrests the control away from the GUI program and gives it to the GNOME/GTK+ system. The system waits for the events to appear in the event queue, where they are placed by the window manager (see Section 17.12). When an event is retrieved from the event queue, the GNOME/GTK+ system first determines the identity of the generating widget. After ascertaining the identity of the widget, GTK+ passes the event information packet to a function such as

      gtk_signal_emit() 

where the information packet is reformatted into a signal and then passed onto the widget that generated it.[25] The widget looks at the list of all the callback functions that have been connected to this signal and invokes them all.

In the example that follows, we have a single button, created in line (B), in the middle of a top-level window. When the mouse is clicked on the button, we want the following message to be displayed on the terminal screen: "Hello from GNOME/GTK+". So we need a callback function that would respond to a button click and print out the message. In the following program, the callback function, sayHello(), is defined in line (D). This callback is connected in line (C) to the clicked signal of the button by

     gtk_signal_connect( GTK_OBJECT(myButton),                         "clicked",                         GTK_SIGNAL_FUNC(sayHello),                         NULL); 

where the first argument points to the source of the signal, the second is the name of the signal, the third is a pointer to the callback function, and the last, when non-null, is a pointer to an object for providing the callback with arguments. The "connect" function expects the first argument to be of type GTK_OBJECT and the third of type GTK_SIGNAL_FUNC.hence the casts shown.

 
//WindowWithHelloButton.c #include <gnome.h> gint eventDestroy(GtkWidget* widget, GdkEvent* event, gpointer data); void sayHello(GtkWidget* widget, GdkEvent* event, gpointer data); int main(int argc, char* argv[]) { GtkWidget* window; GtkWidget* myButton; gnome_init("hellobutton", "1.0", argc, argv); window = gnome_app_new( "hellobutton", "Window with Hello Button"); gtk_container_set_border_width( GTK_CONTAINER(window), 100); //(A) gtk_signal_connect(GTK_OBJECT(window), "destroy", GTK_SIGNAL_FUNC(eventDestroy), NULL); myButton = gtk_button_new_with_label("Say Hello"); //(B) gtk_signal_connect(GTK_OBJECT(myButton), //(C) "clicked", GTK_SIGNAL_FUNC(sayHello), NULL); gnome_app_set_contents(GNOME_APP(window), myButton); gtk_widget_show_all(window); gtk_main(); exit(0); } void sayHello (GtkWidget* widget, GdkEvent* event, gpointer data){ //(D) g_print("Hello from GNOME/GTK+\n"); } gint eventDestroy (GtkWidget* widget, GdkEvent* event, gpointer data) { gtk_main_quit (); return 0; }

The window produced by this program is shown in Figure 17.29.

click to expand
Figure 17.29

17.15.1 Communicating Events to Other Widgets in GNOME/GTK+

We will now present a more elaborate example of event processing in GNOME/GTK+. The example presented here, CrazyWindow, parallels the examples presented earlier in Section 17.13.1 for AWT/Swing and in Section 17.14.1 for Qt.

In the AWT/Swing and Qt implementations of CrazyWindow, we used the grid layout for the two panels. What a grid layout does in AWT/Swing and Qt, a table layout does in GNOME/GTK+. In main below, in line (A) we call on the makeTable() function to create a two-paneled widget, the left side of which will accept text from a user and the right side of which will then display colored squares at random positions, the color of each block corresponding to the color name entered by the user in the left panel. Before invoking makeTable(), main carries out the usual top-level initializations, constructs a top-level window by invoking gtk_window_new(GTK_WINDOW_TOPLEVEL), sets a default size for the window, and connects the "destroy" signal of the top-level window with the eventDestroy() callback function.

In the makeTable() function, we first declare the two panels, textPanel and drawPanel in lines (C) and (D). For textPanel, we invoke in line (E) the constructor gtk_scrolled_window_new(NULL,NULL), where the two NULL arguments are for the adjustments for the horizontal and the vertical scrollbars. By setting these to NULL. we let the panel create its own scrollbars but only if needed. To allow for automatic creation of the scrollbars should the user enter text whose width exceeds that of the viewable area, we also invoke in line (H) the function gtk_scrolled_window_set_policy() with the automatic option for its second and the third arguments.

For the left panel, we create in line (F) a text area by invoking gtk_text_new(NULL,NULL) where, as for textPanel, the two arguments are supposed to be pointers to adjustment objects for the scrollbars; we set these to NULL to elicit the default behavior. To take advantage of the scrolling for the enclosing widget. we turn off the automatic line wrapping option in the text area by invoking gtk_text_set_line_wrap((GtkText*) textarea, FALSE) in line (G). In line (I). the text area is made a child of textPanel by the invocation of gtk_scrolled_window_add_with_viewport().

The rest of the code for the left panel consists of setting the text area to be editable in line (J) so that the user can enter/modify text in it and, in line (K), inserting the scrollable container containing the text area in the left cell of the table.

The last thing we do for the text panel is to connect in line (L) the insert-text signal of the text widget with the callback textEnteredDoSomething() defined in the code at line (S).

For the draw panel, we create in line (M) a window as before, but make it nonscrol-lable by invoking in line (N) the option GTK_POLICY_NEVER for the last two arguments of the function gtk_scrolled_window_set_policy(). In line (R), we then make a child of this window a canvas for drawing colored squares. The canvas is created in line (O). A canvas has associated with it an object of type GnomeCanvasGroup, in our example called rootGroup in line (Q), into which you add widgets of type GnomeCanvasItem if you'd like to see them drawn on the canvas. In line (R), we instantiate rootGroup by the call

      rootGroup = gnome_canvas_root(GNOME_CANVAS(canvas) ); 

This brings us to the callback textChangedDoSomething which is fired each time the user enters a new character into the text area of the left panel. This function retrieves the latest character entered by the user by invoking the following two function calls in lines (T) and (U):

 gint end_pos = gtk_editable_get_position((GtkEditable*) widget); gchar* str = gtk_editable_get_chars( (GtkEditable*) widget,                                         end_pos - 1,                                         end_pos); 

where the first call retrieves the current cursor position and the second call retrieves the last character by virtue of how the second and the third arguments are set.

The logic in the callback textChangedDoSomething() is based on joining together the characters entered by the user, looking for word breaks, and comparing the words with the names of the colors. When the word retrieved is, say, "red", we invoke the following function in line (V):

      item = gnome_canvas_item_new(rootGroup,                        gnome_canvas_rect_get_type(),                        "x1", xpos,                        "y1", ypos,                        "x2", xpos + 20.0,                        "y2", ypos + 20.0,                        "fill_color", "red",                        "outline_color", "white",                        NULL); 

to actually draw a red colored square of size 20 × 20 pixels on the canvas. This function returns an object of type GnomeCanvasItem, which is then added to the rootGroup object associated with the canvas.

 
//CrazyWindow.c #include <gnome.h> #include <stdio.h> #include <stdlib.h> // for rand() #include <time.h> // for seed for rand() GtkWidget* makeTable(); gint eventDestroy(GtkWidget* widget, GdkEvent* event, gpointer data); void textEnteredDoSomething(GtkWidget* widget, GdkEvent* event, gpointer data); char* word = NULL; // for the keyword GtkWidget* textarea; // for incorporation in textPanel GtkWidget* canvas; // for incorporation in drawPanel GnomeCanvasGroup* rootGroup; // for canvas GnomeCanvasItem* item; // for canvas int main(int argc, char* argv[]) { GtkWidget* window; GtkWidget* table; gnome_init("aspect", "1.0", argc, argv); srand( (unsigned) time(NULL) ); window = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_window_set_default_size(GTK_WINDOW(window), 300, 200); gtk_signal_connect(GTK_OBJECT(window), "destroy", GTK_SIGNAL_FUNC(eventDestroy), NULL); table = makeTable(); //(A) gtk_container_add(GTK_CONTAINER(window), table); gtk_widget_show_all(window); gtk_main(); exit(0); } GtkWidget* makeTable() { GtkWidget* table = gtk_table_new(1, 2, TRUE); //(B) GtkWidget* textPanel; //(C) GtkWidget* drawPanel; //(D) // textPanel: textPanel = gtk_scrolled_window_new(NULL, NULL); //(E) textarea = gtk_text_new(NULL, NULL); //(F) gtk_text_set_line_wrap( (GtkText*) textarea, FALSE); //(G) gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(textPanel), //(H) GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); gtk_scrolled_window_add_with_viewport( //(I) GTK_SCROLLED_WINDOW( textPanel ), textarea ); gtk_text_set_editable( (GtkText*) textarea, TRUE ); //(J) gtk_table_attach_defaults( //(K) GTK_TABLE( table ), textPanel, 0, 1, 0, 1 ); gtk_widget_show( textPanel ); gtk_signal_connect( GTK_OBJECT( textarea ), //(L) "changed", GTK_SIGNAL_FUNC( textEnteredDoSomething ), NULL ); // drawPanel: drawPanel = gtk_scrolled_window_new( NULL, NULL ); //(M) gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW(drawPanel),//(N) GTK_POLICY_NEVER, GTK_POLICY_NEVER); canvas = gnome_canvas_new(); //(O) gtk_widget_set_usize( canvas, 100, 100 ); //(P) rootGroup = gnome_canvas_root( GNOME_CANVAS( canvas ) ); //(Q) gtk_container_add( GTK_CONTAINER( drawPanel ), canvas ); //(R) gtk_table_attach_defaults( GTK_TABLE( table ), drawPanel, 1, 2, 0, 1 ); return table; } gint eventDestroy( GtkWidget* widget, GdkEvent* event, gpointer data ) { gtk_main_quit(); return 0; } void textEnteredDoSomething( GtkWidget* widget, //(S) GdkEvent* event, gpointer data ) { double xpos; double ypos; gint end_pos = gtk_editable_get_position( (GtkEditable*) widget ); //(T) gchar* str = gtk_editable_get_chars( (GtkEditable*) widget, //(U) end_pos - 1, end_pos ); if ( word == NULL) { word = malloc( 50 ); *word = '\0'; } if (strlen (word) >= 50) { //guard against buffer breaking 50 chars free (word); word = malloc ( 50 ); *word = '\0'; } if ( str != NULL ) { if ( *str == ' ' || *str == '\n') { if ( strcmp( word, "red" ) == 0 ) { xpos = rand() %75; ypos = rand() %75; item = gnome_canvas_item_new( rootGroup, //(V) gnome_canvas_rect_get_type(), "x1", xpos, "y1", ypos, "x2", xpos + 20.0 , "y2", ypos + 20.0 , "fill_color", "red", "outline_color", "white", NULL ); gnome_canvas_item_show( item ); } if ( strcmp( word, "green" ) == 0 ) { xpos = rand() %75; ypos = rand() %75; item = gnome_canvas_item_new( rootGroup, gnome_canvas_rect_get_type(), "x1", xpos, "y1", ypos, "x2", xpos + 20.0 , "y2", ypos + 20.0 , "fill_color", "green", "outline_color", "white", NULL ); gnome_canvas_item_show( item ); } if ( strcmp( word, "blue" ) == 0 ) { xpos = rand() %75; ypos = rand() %75; item = gnome_canvas_item_new( rootGroup, gnome_canvas_rect_get_type(), "x1", xpos, "y1", ypos, "x2", xpos + 20.0 , "y2", ypos + 20.0 , "fill_color", "blue", "outline_color", "white", NULL ); gnome_canvas_item_show( item ); } if ( strcmp( word, "magenta" ) == 0 ) { xpos = rand() %75; ypos = rand() %75; item = gnome_canvas_item_new( rootGroup, gnome_canvas_rect_get_type(), "x1", xpos, "y1", ypos, "x2", xpos + 20.0 , "y2", ypos + 20.0 , "fill_color", "magenta", "outline_color", "white", NULL ); gnome_canvas_item_show( item ); } free (word); word = NULL; gnome_canvas_update_now( (GnomeCanvas* ) canvas ); } else strcat( word, str ); } g_free( str ); }

Here is a makefile that produces the executable for this program:

 
#Makefile_GTK_CrazyWindow CC=gcc LDLIBS='gnome-config --libs gnomeui' CFLAGS=-Wall -g 'gnome-config --cflags gnomeui' CrazyWindow: CrazyWindow.o Makefile_GTK_CrazyWindow $(CC) $(LDLIBS) CrazyWindow.o -o CrazyWindow CrazyWindow.o: CrazyWindow.c $(CC) $(CFLAGS) -c CrazyWindow.c clean: rm -f CrazyWindow rm -f CrazyWindow.o

The GUI for this program is shown in Figure 17.30.

click to expand
Figure 17.30

17.15.2 Summary of Facts about Callbacks in GNOME/Gtk+

The following list, from Griffith [25], summarizes the important facts about the callbacks in GNOME/GTK+:

  1. The callback function you write for a given signal must have its return type, the argument types, and the number of arguments exactly the same as are specified for the signal.

  2. The header format of a callback function is not standardized.

  3. Since the call to a callback function is generated at run time, there is no way for a compiler to make sure that your callback function has the correct return type and the number of arguments. All that the compiler can do is to make sure that the header of your callback function matches the prototype. In fact, at compile time, the system does not really know that what you have written as a callback function is really a callback function. To the compiler, it is like any other function. However, at run time, when the function gtk_signal_connect() is invoked and the name of the callback function is encountered as one of the arguments, the system knows that it is dealing with a callback function. It is at that time that the system first checks that your callback function meets the specifications for the signal in question.

  4. Each widget class defines its own set of signals. For example, the class GtkButton has defined for it directly the following five signals:

          clicked      pressed      released      enter      leave 
  5. A widget class also inherits all the signals from all its parent classes. GtkButton inherits from GtkBin, GtkContainer, GtkWidget, and GtkObject. GtkBin has no signals defined for it directly, GtkContainer has 5, GtkWidget 54, and GtkObject 1. So GtkButton inherits 60 signals from its parents. Together with its own 5, GtkButton has 65 signals.

  6. Every callback function has at least the two parameters, but many have many more. The prototype of the simplest callback function looks like

          void callBack( GtkWidget*, gpointer ); 
  7. If a callback function has additional parameters, they are always between the two standard ones shown above. For example, a callback function that includes a GdkEvent* in its parameter list is

          void callBack( GtkWidget*, GdkEvent*, gpointer ); 
  8. A callback function will have one of the following return types:

          void      gint      gboolean 
  9. When a new widget is constructed at run time, the signals for that widget are automatically registered by entering them into an internal table. Each signal is assigned an ID number in this table. The entries in this table can be queried by invoking

          gtk_signal_query( i ) 

    This will return the information on the signal which was assigned the ID number i. The returned information is a pointer to a struct of type GtkSignalQuery. This struct contains the the following fields

          object_type      return_val      signal_name      nparams      params[j] 

    The following invocations on these fields return the information shown below:

          GtkSignalQuery* q = gtk_signal_query( i );      gtk_type_name( q->object_type );                          //returns the name of the object for                          //which the signal is directly defined      gtk_type_name( q->return_val );                          //returns the type of the value                          //returned by the callback function 

    The value stored in field nparams is the number of parameters in the callback for the signal whose names is stored in signal_name. This number is in addition to the two default parameters that every callback must have. The names of these additional parameters are stored in the array params[].

As was mentioned before, once you invoke gtk_main() in your program, you are handing over the control to the GNOME/GTK+ system for the event processing loop. The system will wait (unless terminated by the invocation of gtk_main_quit()) for the next event to show up in the event queue for the window. Ordinarily, while waiting for the next event, the system will simply block, meaning it will sit idle. That can result in a waste of computing resources on modern fast machines. To get around this problem, GTK makes available a special facility for invoking background processing of whatever you'd want the computer to do in the background should the event queue become and remain empty. This background activity can be invoked by calling

      gtk_idle_add( functionName, pointerToData ) 

where functionName is a pointer to the function you'd want to invoke in the background. If the function needs some input data for processing, that can be sent to it via the parameter pointerToData.

A function that is executed in the background in this manner is called an idle function, not because the function is idling-it could actually be engaged in some very important task such as accessing and retrieving information from a database-but because the function is invoked while the event queue is idle. However, this solves only half of the problem. The other half of the problem is reacquiring the control of the CPU when the window manager deposits something in the event queue while the idle function is being executed. This problem can be solved by having the idle function relinquish control back to gtk_main() periodically. The reader is referred to Griffith [25] for further details regarding the implementation of idle functions.

[25]This function can also be used to simulate a signal corresponding to an event by supplying it with a signal ID number. An alternative to using a signal ID number in simulations is to invoke the following version of the function:

      gtk_signal_emit_by_name() 
Both these functions need a pointer to the widget that is to receive the signal.




Programming With Objects[c] A Comparative Presentation of Object-Oriented Programming With C++ and Java
Programming with Objects: A Comparative Presentation of Object Oriented Programming with C++ and Java
ISBN: 0471268526
EAN: 2147483647
Year: 2005
Pages: 273
Authors: Avinash Kak

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net