Re: Global motion events



Rather then comment on the patch immediately, I thought I'd go back
and try to write up the problems we are trying to solve here in some
detail so I'd have them clear in my own mind.

						- Owen

Issues to solve
===============

There are two basic problems that come up repeatedly with handling of
mouse events in GTK+:

 1. With the modern method of using input-only windows to catch
    events, we've lost the correspondance between the widget
    hierarchy and the window hierarchy. Consider a
    GtkDrawingArea in a notebook tab.

    We used to have:

      toplevel->window contains 
          notebook->window contains 
              drawing_area->window

    Now we have:

      toplevel->window contains 
          notebook->window 
	  drawing_area->window

    And drawing_area->window just coincidentally" sits on top of
    notebook->window. With this change, a lot of properties of
    X events no longer work right. Two examples:
 
     - When the mouse moves from notebook->window to
       drawing_area->window, we don't get a LEAVE detail=INFERIOR,
       but rather a LEAVE detail=NONLINEAR, so it looks like
       the mouse has left the notebook.

     - If drawing_area->window doesn't select for button press
       events, then the event is propagated at the X level not 
       to notebook->window but to toplevel->window. The notebook
       doens't receive the events.

 2. The need to have windows to catch mouse events in itself
    is a problem. It's a frequent GTK+ FAQ, GTK+ widgets like 
    GtkLabel have complex logic for sometimes adding an event
    widget, and an artificial restriction is introduced for
    for drag sources and tooltips.

In addition, there are some other things about mouse event handling
which are less than ideal:

 3. You can't reliably do prelighting on nested widgets. Even 
    if you have a solution for 1, there is still a problem with
    a button in a notebook tab. When you get the detail=inferior
    LeaveEvent, you don't know whether the the pointer left
    the notebook to go to a drawing area (doesn't select for
    button events, notebook still gets the event), or to a 
    button (selects for button events, notebook doesn't get
    the event)

 4. Shaped input areas don't work very well. We can return
    TRUE or FALSE for button-press-event, based on where the 
    mouse was pressed, and the parent only gets the events where
    we returned TRUE. But most widgets ignore button press
    events with an unexpected window field. Prelighting on 
    the parent also doesn't work right, but its broken by 
    3 already.

    (You can solve shaped input areas by using 
    gdk_window_shape_combine_mask() on an InputOnly window)

Event classification
====================

So, what events are we dealing with here? Basically the mouse events.

  BUTTON_PRESS, 2BUTTON_PRESS,  3BUTTON_PRESS, BUTTON_RELEASE,
  ENTER_NOTIFY, LEAVE_NOTIFY, MOTION_NOTIFY, PROXIMITY_IN, PROXIMITY_OUT
  SCROLL

Other events we have are key events, which are delivered to toplevels
and propagated along the widget heirarchy already:

  KEY_PRESS, KEY_RELEASE

A set of events that are related to windows (mostly toplevels) where
hierarchical propagation doesn't make sense:

  CLIENT_EVENT, CONFIGURE, DELETE, DESTROY, EXPOSE, FOCUS_CHANGE,
  MAP, PROPERTY_NOTIFY, UNMAP, VISIBILITY_NOTIFY, WINDOW_STATE

And events that are either obsolete or not directly relevant to 
applications:

  DRAG_ENTER, DRAG_LEAVE, DRAG_MOTION, DRAG_STATUS, DROP_START,
  DROP_FINISHED, NO_EXPOSE, SETTING, SELECTION_CLEAR, SELECTION_REQUEST
  SELECTION_NOTIFY, OWNER_CHANGE

So 12 events types, corresponding to 8 signals
(button-press-event, button-release-event, enter-notify-event, 
leave-notify-event, motion-notify-event, proximity-in-event,
proximity-out-event, scroll-event.)

There is some subdivision of the mouse signals that makes sense:

 - BUTTON_PRESS, 2BUTTON_PRESS, 3BUTTON_PRESS, BUTTON_RELEASE, SCROLL
   
   These indicate explicit actions by the user, and must only be acted 
   on by only one recipient. If the event gets acted on twice, we may
get 
   stuck grabs or similar.
 
   The general expected propation behavior is that if not handled
   on a widget, they should get passed to the parent.

 - MOTION_NOTIFY, PROXIMITY_IN, PROXIMITY_OUT

   These indicate state changes rather than action. A signal handler
   for one of these that returns TRUE is likely a bug. 
  
   The general expected propation behavior is that if they should
   be sent to a widget, then to passed to the parent.

 - ENTER_NOTIFY, LEAVE_NOTIFY

   Instead of being generated once, these are generated on the 
   full set of affected widgets. So, propation doesn't make sense. 

   (However, X doesn't send  motion events when it sends enter/leave 
   events, so they need to get propagated to get full motion 
   information)

Difficulties
============

In coming up with a fix for these problems, we have a number of 
difficulties:

 - If we want to have some other way of delivering events with
   respect to widgets rather than windows, then we either have to:

    - Reinvent the GdkEvent wheel. (GdkEventButton, GdkEventMotion,
      GdkEventCrossing, GdkEventProximity, GdkEventScroll)

    - Reinterpret the GdkEvent fields in some creative way.
      We could set event->window to the widget->window always
      and use coordinates relative to that. But what to do 
      about subwindow in GdkEventCrossing? The subwidget isn't
      identified by a window. (Of course, I've never found a
      use for this field, so NULL probably would be fine.)

 - For compatibility, we still have to keep delivering events
   the existing way. 

 - Implicit pointer grabs should be generated at the widget
   level. (If you press the mouse in a widget, that widget
   should be guaranteed to get the release.).

 - Explicit point grabs should be honored. (If an app
   calls gdk_pointer_grab() on a window, the event shouldn't
   be delivered to other windows.
   
 - We only have 7 available padding slots in GtkWidget for adding 
   signals with virtual functions. While having virtual function
   slots for each signal is not crucial, it's generally something
   we'd like to do. (To avoid having widgets connecting to 
   themselves.)

 - Leaving that aside, we have 61 (yes, 61) signals on GtkWidget
   currently. I'm reluctant to add 8 more.

 - As much as possible, we need to avoid confusing programmers
   using the GTK+ API with multiple ways to do the same thing.

Feel challenged yet? 

Søren's proposal
================

So, how does Søren's proposal fit into what we've discussed above?
It handles is restricted to three events (enter/leave/motion) and
adds a signal for each:

 void ::global-enter (widget, x, y);
 void ::global-leave (widget);
 void ::global-motion (widget, x, y);

So, very simple. Note that these events do not pay attention to
the widget hierarchy:

 - motion events are delivered to *all* widgets that lie under the
pointer
   (to *both* the notebook and the drawing area)

 - There is no idea of an "inferior" leave/enter in the X sense:
   no event is generated on the parent when the pointer moves from
   parent to child.

   (But see issue 3. for why "inferior" leaves aren't very interesting)

Other ideas
===========

What are some other ways we could do the API? (brainstorming, in order
of increasing radicalism)

 - We could bundle everything into a single signal, along the
   lines of ::event.

   g_signal_connect (gtk_widget_get_eventer (widget), "mouse-event",
                     on_mouse_event, NULL);

 - We could use an aggregate object for GtkWidget to provide
   a new namespace for signals:

   g_signal_connect (gtk_widget_get_eventer (widget), "enter",
                     on_enter, NULL);

 - We could use the existing event signals, but deliver additional
   events to widgets that wouldn't normally get events. 

   In the notebook tab case, when the user clicks on the drawing
   area and X delivers the event to toplevel->window, we note that
   it is over the area of the notebook tab, and propagate it 
   starting from the notebook rather than starting from the 
   toplevel.

   Somewhat similarly, for enter/leave events, we generate
   synthetic enter/leave events for a widget when a widget-crossing
   doesn't correspond to a window-crossing.   

   To make the notebook-tab example work properly, we need to do 
   one other thing: we need to munge the detail on crossing
   events when the window hierarchy doesn't correspond to the
   widget hierarchy.

   This should fix issue 1. and 2.

   Two potential problems here:

   - We're changing what events get delivered fairly radically,
     and apps could get confused. Even if all the changes
     "make sense", then 

   - If we allow no-window widgets to receive events, we
     will get confusing inferior LeaveNotify events.

 - Modification on the above, add a signal to GtkWidget to allow
   a widget to decide whether it contains the pointer or not. 
   (Anybody returns TRUE, the widget contains the pointer)
   
   This:

    - Allows shaped input areas
    - Fixes the inferior LeaveNotify problem noted in the last
      problem, if we require a connection to ::hit-test for a 
      no-window widget to receive events
    - And by fixing the inferior LeaveNotify it becomes reasonable
      to enable nested-prelighting.

   Simplest is to say that ::hit-test only applies when there
   isn't a separate GdKWindow for the widget.

 - We could make GDK allow (via emulation) an InputOnly window
   to contain an InputOutput window. Then we get rid of the
   window vs. widget hierarchy mismatch widget by widget. 
 
   With this label's widget->window ends up pointing to an
   InputOnly window, so we also have to say that drawing to an
   InputOnly window is redirected to the enearest InputOutput 
   ancestor

Appendix: a reference
=====================

While looking at a new way to handle mouse events in GTK+, it's
worth comparing with the DOM2 event model:

 http://www.w3.org/TR/2000/REC-DOM-Level-2-Events-20001113/

There are various differences from GTK+, but the major one I'll
point out is:

 Before the propagate-up stage (bubbling in DOM terminology)
 there is a propagate-down stage where parent elements can
 receive and optionally block events.

Attachment: signature.asc
Description: This is a digitally signed message part



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]