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