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 structure containing information about the event. For instance, motion event handlers are passed a GdkEvent.Motion structure which looks (in part) like: see GdkEvent.Motion

type t = [ `MOTION_NOTIFY ] Gdk.event 
val cast : GdkEvent.any -> t

val time : [< GdkEvent.timed ] Gdk.event -> int32
val x : t -> float
val y : t -> float
val axes : t -> (float * float) option
val state : t -> int
val is_hint : t -> bool
val device : t -> Gdk.device
val x_root : t -> float
val y_root : t -> float

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 can contain some of the following:

`SHIFT
`LOCK
`CONTROL
`MOD1
`MOD2
`MOD3
`MOD4
`MOD5
`BUTTON1
`BUTTON2
`BUTTON3
`BUTTON4
`BUTTON5

You can test the state whether it includes the given modifier or not, using one of the followings:

val Gdk.Convert.test_modifier : Gdk.Tags.modifier -> int -> bool
val Gdk.Convert.modifier : int -> Gdk.Tags.modifier list

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

method event#add : Gdk.Tags.event_mask list -> unit

The argument specifies the events we are interested in. It is the list of constants that specify different types of events. For future reference the Gdk.Tags.event_mask are:

type event_mask = [ `ALL_EVENTS
	| `BUTTON1_MOTION
	| `BUTTON2_MOTION
	| `BUTTON3_MOTION
	| `BUTTON_MOTION
	| `BUTTON_PRESS
	| `BUTTON_RELEASE
	| `ENTER_NOTIFY
	| `EXPOSURE
	| `FOCUS_CHANGE
	| `KEY_PRESS
	| `KEY_RELEASE
	| `LEAVE_NOTIFY
	| `POINTER_MOTION
	| `POINTER_MOTION_HINT
	| `PROPERTY_CHANGE
	| `PROXIMITY_IN
	| `PROXIMITY_OUT
	| `SCROLL
	| `STRUCTURE
	| `SUBSTRUCTURE
	| `VISIBILITY_NOTIFY ] 

There are a few subtle points that have to be observed when calling event#add method. 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 `POINTER_MOTION and `BUTTON_PRESS. We also want to know when we need to redraw our window, so we specify `EXPOSURE. Although we want to be notified via a Configure event when our window size changes, we don’t have to specify the corresponding `STRUCTURE flag, because it is automatically specified for all windows.

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

When we specify `POINTER_MOTION_HINT, 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:

val Gdk.Window.get_pointer_location : Gdk.window -> int * int

There is another method, misc#pointer method which has a simpler interface:

method misc#pointer : int * int

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

  drawing_area#event#connect#expose ~callback:expose;
  drawing_area#event#connect#configure ~callback:configure;
  drawing_area#event#connect#motion_notify ~callback:motion_notify;
  drawing_area#event#connect#button_press ~callback:button_pressed;

  drawing_area#event#add [`EXPOSURE;
    `LEAVE_NOTIFY;
    `BUTTON_PRESS;
    `POINTER_MOTION;
    `POINTER_MOTION_HINT];

We’ll save the “expose” and “configure” callbacks for later. The “motion_notify” and “button_pressed” callbacks are pretty simple:

let button_pressed area backing ev =
  if GdkEvent.Button.button ev = 1 then (
    let x = int_of_float (GdkEvent.Button.x ev) in
    let y = int_of_float (GdkEvent.Button.y ev) in
    draw_brush area backing x y
  );
  true

let motion_notify area backing ev =
  let (x, y) =
    if GdkEvent.Motion.is_hint ev
	then area#misc#pointer
	else
      (int_of_float (GdkEvent.Motion.x ev), int_of_float (GdkEvent.Motion.y ev))
  in
  let state = GdkEvent.Motion.state ev in
  if Gdk.Convert.test_modifier `BUTTON1 state
  then draw_brush area backing x y;
  true
comments powered by Disqus