Event Handling

GTK+ 2.0 Tutorial
<<< Previous | Scribble, A Simple Example Drawing Program | Next >>>

# Event Handling

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 events. The handlers for these signals have an extra parameter which is a pointer to a structure containing information about the event. For instance, motion event handlers are passed a pointer to a GdkEventMotion structure which looks (in part) like:

    struct _GdkEventMotion
    {
      GdkEventType type;
      GdkWindow *window;
      guint32 time;
      gdouble x;
      gdouble y;
      ...
      guint state;
      ...
    };

`type` will be set to the event type, in this case `GDK_MOTION_NOTIFY`, window is the window in which the event occurred. `x` and `y` give the coordinates of the event. `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:

    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

As for other signals, to determine what happens when an event occurs we call `gtk_signal_connect()`. But we also need let GTK know which events we want to be notified about. To do this, we call the function:

    void gtk_widget_set_events (GtkWidget *widget,
                                gint      events);

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:

    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

There are a few subtle points that have to be observed when calling `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:

    GtkAlignment
    GtkArrow
    GtkBin
    GtkBox
    GtkImage
    GtkItem
    GtkLabel
    GtkPixmap
    GtkScrolledWindow
    GtkSeparator
    GtkTable
    GtkAspectFrame
    GtkFrame
    GtkVBox
    GtkHBox
    GtkVSeparator
    GtkHSeparator

To capture events for these widgets, you need to use an EventBox widget. See the section on the EventBox widget for details.

For our drawing program, we want to know when the mouse button is pressed and when the mouse is moved, so we specify `GDK_POINTER_MOTION_MASK` and `GDK_BUTTON_PRESS_MASK`. We also want to know when we need to redraw our window, so we specify `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 `GDK_STRUCTURE_MASK` flag, because it is automatically specified for all windows.

It turns out, however, that there is a problem with just specifying `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 `GDK_POINTER_MOTION_HINT_MASK`.

When we specify `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 explicitly ask for the position of the pointer using the function:

    GdkWindow*    gdk_window_get_pointer     (GdkWindow       *window,
    					  gint            *x,
    					  gint            *y,
    					  GdkModifierType *mask);

(There is another function, `gtk_widget_get_pointer()` 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.)

The code to set the events for our window then looks like:

      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);

We'll save the "expose_event" and "configure_event" handlers for later. The "motion_notify_event" and "button_press_event" handlers are pretty simple:

    static gboolean
    button_press_event( GtkWidget *widget, GdkEventButton *event )
    {
      if (event->button == 1 && pixmap != NULL)
          draw_brush (widget, event->x, event->y);

      return TRUE;
    }

    static gboolean
    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;
    }

<<< Previous | Home | Next >>>
Scribble, A Simple Example Drawing Program | Up | The DrawingArea Widget, And Drawing