24.2. Event Handling
Prev | Chapter 24. Scribble, A Simple Example Drawing Program | Next

## 24.2. 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 `gtk.gdk.Event` object containing information about the event. For instance, motion event handlers are passed a `gtk.gdk.Event` object containing EventMotion information which has (in part) attributes like:

      type
      window
      time
      x
      y
        ...
      state
        ...

`window` is the window in which the event occurred.

`x` and `y` give the coordinates of the event.

`type` will be set to the event type, in this case `MOTION_NOTIFY`. The types (in module gtk.gdk) are:

    NOTHING                a special code to indicate a null event.

    DELETE                 the window manager has requested that the toplevel window be
                           hidden or destroyed, usually when the user clicks on a special
                           icon in the title bar.

    DESTROY                the window has been destroyed.

    EXPOSE                 all or part of the window has become visible and needs to be
                           redrawn.

    MOTION_NOTIFY          the pointer (usually a mouse) has moved.

    BUTTON_PRESS           a mouse button has been pressed.

    _2BUTTON_PRESS         a mouse button has been double-clicked (clicked twice within
                           a short period of time). Note that each click also generates a
                           BUTTON_PRESS event.

    _3BUTTON_PRESS         a mouse button has been clicked 3 times in a short period of
                           time. Note that each click also generates a BUTTON_PRESS event.

    BUTTON_RELEASE         a mouse button has been released.

    KEY_PRESS              a key has been pressed.

    KEY_RELEASE            a key has been released.

    ENTER_NOTIFY           the pointer has entered the window.

    LEAVE_NOTIFY           the pointer has left the window.

    FOCUS_CHANGE           the keyboard focus has entered or left the window.

    CONFIGURE              the size, position or stacking order of the window has changed.
                           Note that GTK+ discards these events for GDK_WINDOW_CHILD windows.

    MAP                    the window has been mapped.

    UNMAP                  the window has been unmapped.

    PROPERTY_NOTIFY        a property on the window has been changed or deleted.

    SELECTION_CLEAR        the application has lost ownership of a selection.

    SELECTION_REQUEST      another application has requested a selection.

    SELECTION_NOTIFY       a selection has been received.

    PROXIMITY_IN           an input device has moved into contact with a sensing surface
                           (e.g. a touchscreen or graphics tablet).

    PROXIMITY_OUT          an input device has moved out of contact with a sensing surface.

    DRAG_ENTER             the mouse has entered the window while a drag is in progress.

    DRAG_LEAVE             the mouse has left the window while a drag is in progress.

    DRAG_MOTION            the mouse has moved in the window while a drag is in progress.

    DRAG_STATUS            the status of the drag operation initiated by the window has changed.

    DROP_START             a drop operation onto the window has started.

    DROP_FINISHED          the drop operation initiated by the window has completed.

    CLIENT_EVENT           a message has been received from another application.

    VISIBILITY_NOTIFY      the window visibility status has changed.

    NO_EXPOSE              indicates that the source region was completely available when parts
                           of a drawable were copied. This is not very useful.

    SCROLL                 ?

    WINDOW_STATE           ?

    SETTING                ?

`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 (in module gtk.gdk):

      SHIFT_MASK
      LOCK_MASK
      CONTROL_MASK
      MOD1_MASK
      MOD2_MASK
      MOD3_MASK
      MOD4_MASK
      MOD5_MASK
      BUTTON1_MASK
      BUTTON2_MASK
      BUTTON3_MASK
      BUTTON4_MASK
      BUTTON5_MASK

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

      widget.set_events(events)

The `events` argument 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 (in module gtk.gdk) are:

      EXPOSURE_MASK
      POINTER_MOTION_MASK
      POINTER_MOTION_HINT_MASK
      BUTTON_MOTION_MASK
      BUTTON1_MOTION_MASK
      BUTTON2_MOTION_MASK
      BUTTON3_MOTION_MASK
      BUTTON_PRESS_MASK
      BUTTON_RELEASE_MASK
      KEY_PRESS_MASK
      KEY_RELEASE_MASK
      ENTER_NOTIFY_MASK
      LEAVE_NOTIFY_MASK
      FOCUS_CHANGE_MASK
      STRUCTURE_MASK
      PROPERTY_CHANGE_MASK
      VISIBILITY_NOTIFY_MASK
      PROXIMITY_IN_MASK
      PROXIMITY_OUT_MASK
      SUBSTRUCTURE_MASK

There are a few subtle points that have to be observed when calling the `set_events`() method. First, it must be called before the X window for a PyGTK widget is created. In practical terms, this means you should call it immediately after creating the widget. Second, the widget must be one which will be realized with an associated X window. For efficiency, many widget types do not have their own window, but draw in their parent's window. These widgets include:

      gtk.Alignment
      gtk.Arrow
      gtk.Bin
      gtk.Box
      gtk.Image
      gtk.Item
      gtk.Label
      gtk.Layout
      gtk.Pixmap
      gtk.ScrolledWindow
      gtk.Separator
      gtk.Table
      gtk.AspectFrame
      gtk.Frame
      gtk.VBox
      gtk.HBox
      gtk.VSeparator
      gtk.HSeparator

To capture events for these widgets, you need to use an `EventBox` widget. See Section 10.1, “The EventBox” widget for details.

The event attributes that are set by PyGTK for each type of event are:

    every event             type
                            window
                            send_event

    NOTHING
    DELETE
    DESTROY                                    # no additional attributes

    EXPOSE                 area
                           count

    MOTION_NOTIFY          time
                           x
                           y
                           pressure
                           xtilt
                           ytilt
                           state
                           is_hint
                           source
                           deviceid
                           x_root
                           y_root

    BUTTON_PRESS
    _2BUTTON_PRESS
    _3BUTTON_PRESS
    BUTTON_RELEASE         time
                           x
                           y
                           pressure
                           xtilt
                           ytilt
                           state
                           button
                           source
                           deviceid
                           x_root
                           y_root

    KEY_PRESS
    KEY_RELEASE            time
                           state
                           keyval
                           string

    ENTER_NOTIFY
    LEAVE_NOTIFY           subwindow
                           time
                           x
                           y
                           x_root
                           y_root
                           mode
                           detail
                           focus
                           state

    FOCUS_CHANGE           _in

    CONFIGURE              x
                           y
                           width
                           height

    MAP
    UNMAP                                  # no additional attributes

    PROPERTY_NOTIFY        atom
                           time
                           state

    SELECTION_CLEAR
    SELECTION_REQUEST
    SELECTION_NOTIFY       selection
                           target
                           property
                           requestor
                           time

    PROXIMITY_IN
    PROXIMITY_OUT          time
                           source
                           deviceid

    DRAG_ENTER
    DRAG_LEAVE
    DRAG_MOTION
    DRAG_STATUS
    DROP_START
    DROP_FINISHED          context
                           time
                           x_root
                           y_root

    CLIENT_EVENT           message_type
                           data_format
                           data

    VISIBILTY_NOTIFY       state

    NO_EXPOSE                            # no additional attributes

### 24.2.1. Scribble - Event Handling

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

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

When we specify `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 `gtk.gdk.Window` method:

      x, y, mask = window.get_pointer()

`window` is a `gtk.gdk.Window` object. `x` and `y` are the coordinates of the pointer and `mask` is the modifier mask to detect which keys are pressed. (There is a `gtk.Widget` method, `get_pointer`() which provides the same information as the `gtk.gdk.Window get_pointer`() method but it does not return the mask information)

The scribblesimple.py example program demonstrates the basic use of events and event handlers. Figure 24.2, “Simple Scribble Example” illustrates the program in action:

Figure 24.2. Simple Scribble Example

The event handlers are connected to the drawing_area by the following lines:

       92       # Signals used to handle backing pixmap
       93       drawing_area.connect("expose_event", expose_event)
       94       drawing_area.connect("configure_event", configure_event)
       95
       96       # Event signals
       97       drawing_area.connect("motion_notify_event", motion_notify_event)
       98       drawing_area.connect("button_press_event", button_press_event)
       99
      100       drawing_area.set_events(gtk.gdk.EXPOSURE_MASK
      101                               | gtk.gdk.LEAVE_NOTIFY_MASK
      102                               | gtk.gdk.BUTTON_PRESS_MASK
      103                               | gtk.gdk.POINTER_MOTION_MASK
      104                               | gtk.gdk.POINTER_MOTION_HINT_MASK)

The `button_press_event`() and`motion_notify_event`() event handlers in scribblesimple.py are:

       57   def button_press_event(widget, event):
       58       if event.button == 1 and pixmap != None:
       59           draw_brush(widget, event.x, event.y)
       60       return True
       61
       62   def motion_notify_event(widget, event):
       63       if event.is_hint:
       64           x, y, state = event.window.get_pointer()
       65       else:
       66           x = event.x
       67           y = event.y
       68           state = event.state
       69
       70       if state & gtk.gdk.BUTTON1_MASK and pixmap != None:
       71           draw_brush(widget, x, y)
       72
       73       return True

The `expose_event`() and `configure_event`() handlers will be described later.

Prev | Up | Next
Chapter 24. Scribble, A Simple Example Drawing Program | Home | 24.3. The DrawingArea Widget, And Drawing