diff --git a/gtk/gtkmain.c b/gtk/gtkmain.c index c0cdabd9c8..68ecbfeb65 100644 --- a/gtk/gtkmain.c +++ b/gtk/gtkmain.c @@ -1471,6 +1471,7 @@ gtk_main_do_event (GdkEvent *event) { GtkWidget *event_widget; GtkWidget *grab_widget = NULL; + GtkWidget *topmost_widget = NULL; GtkWindowGroup *window_group; GdkEvent *rewritten_event = NULL; GdkDevice *device; @@ -1530,6 +1531,14 @@ gtk_main_do_event (GdkEvent *event) if (!grab_widget) grab_widget = gtk_window_group_get_current_grab (window_group); + /* Find out the topmost widget where captured event propagation + * should start, which is the widget holding the GTK+ grab + * if any, otherwise it's left NULL and events are emitted + * from the toplevel (or topmost parentless parent). + */ + if (grab_widget) + topmost_widget = grab_widget; + /* If the grab widget is an ancestor of the event widget * then we send the event to the original event widget. * This is the key to implementing modality. @@ -1626,14 +1635,16 @@ gtk_main_do_event (GdkEvent *event) case GDK_WINDOW_STATE: case GDK_GRAB_BROKEN: case GDK_DAMAGE: - gtk_widget_event (event_widget, event); + if (!_gtk_widget_captured_event (event_widget, event)) + gtk_widget_event (event_widget, event); break; case GDK_SCROLL: case GDK_BUTTON_PRESS: case GDK_2BUTTON_PRESS: case GDK_3BUTTON_PRESS: - gtk_propagate_event (grab_widget, event); + if (!_gtk_propagate_captured_event (grab_widget, event, topmost_widget)) + gtk_propagate_event (grab_widget, event); break; case GDK_KEY_PRESS: @@ -1682,7 +1693,8 @@ gtk_main_do_event (GdkEvent *event) case GDK_BUTTON_RELEASE: case GDK_PROXIMITY_IN: case GDK_PROXIMITY_OUT: - gtk_propagate_event (grab_widget, event); + if (!_gtk_propagate_captured_event (grab_widget, event, topmost_widget)) + gtk_propagate_event (grab_widget, event); break; case GDK_ENTER_NOTIFY: @@ -1691,7 +1703,8 @@ gtk_main_do_event (GdkEvent *event) _gtk_widget_set_device_window (event_widget, gdk_event_get_device (event), event->any.window); - if (gtk_widget_is_sensitive (grab_widget)) + if (gtk_widget_is_sensitive (grab_widget) && + !_gtk_propagate_captured_event (grab_widget, event, topmost_widget)) gtk_widget_event (grab_widget, event); break; @@ -1701,7 +1714,8 @@ gtk_main_do_event (GdkEvent *event) _gtk_widget_set_device_window (event_widget, gdk_event_get_device (event), NULL); - if (gtk_widget_is_sensitive (grab_widget)) + if (gtk_widget_is_sensitive (grab_widget) && + !_gtk_propagate_captured_event (grab_widget, event, topmost_widget)) gtk_widget_event (grab_widget, event); break; @@ -2331,6 +2345,135 @@ gtk_get_event_widget (GdkEvent *event) return widget; } +static gboolean +propagate_event_up (GtkWidget *widget, + GdkEvent *event, + GtkWidget *topmost) +{ + gboolean handled_event = FALSE; + + /* Propagate event up the widget tree so that + * parents can see the button and motion + * events of the children. + */ + while (TRUE) + { + GtkWidget *tmp; + + g_object_ref (widget); + + /* Scroll events are special cased here because it + * feels wrong when scrolling a GtkViewport, say, + * to have children of the viewport eat the scroll + * event + */ + if (!gtk_widget_is_sensitive (widget)) + handled_event = event->type != GDK_SCROLL; + else + handled_event = gtk_widget_event (widget, event); + + tmp = gtk_widget_get_parent (widget); + g_object_unref (widget); + + if (widget == topmost) + break; + + widget = tmp; + + if (handled_event || !widget) + break; + } + + return handled_event; +} + +static gboolean +propagate_event_down (GtkWidget *widget, + GdkEvent *event, + GtkWidget *topmost) +{ + gint handled_event = FALSE; + GList *widgets = NULL; + GList *l; + + widgets = g_list_prepend (widgets, g_object_ref (widget)); + while (widget && widget != topmost) + { + widget = gtk_widget_get_parent (widget); + if (!widget) + break; + + widgets = g_list_prepend (widgets, g_object_ref (widget)); + + if (widget == topmost) + break; + } + + for (l = widgets; l && !handled_event; l = g_list_next (l)) + { + widget = (GtkWidget *)l->data; + + if (!gtk_widget_is_sensitive (widget)) + handled_event = TRUE; + else + handled_event = _gtk_widget_captured_event (widget, event); + } + g_list_free_full (widgets, (GDestroyNotify)g_object_unref); + + return handled_event; +} + +static gboolean +propagate_event (GtkWidget *widget, + GdkEvent *event, + gboolean captured, + GtkWidget *topmost) +{ + gboolean handled_event = FALSE; + gboolean (* propagate_func) (GtkWidget *widget, GdkEvent *event); + + propagate_func = captured ? _gtk_widget_captured_event : gtk_widget_event; + + if (event->type == GDK_KEY_PRESS || event->type == GDK_KEY_RELEASE) + { + /* Only send key events within Window widgets to the Window + * The Window widget will in turn pass the + * key event on to the currently focused widget + * for that window. + */ + GtkWidget *window; + + window = gtk_widget_get_toplevel (widget); + if (GTK_IS_WINDOW (window)) + { + g_object_ref (widget); + /* If there is a grab within the window, give the grab widget + * a first crack at the key event + */ + if (widget != window && gtk_widget_has_grab (widget)) + handled_event = propagate_func (widget, event); + + if (!handled_event) + { + window = gtk_widget_get_toplevel (widget); + if (GTK_IS_WINDOW (window)) + { + if (gtk_widget_is_sensitive (window)) + handled_event = propagate_func (window, event); + } + } + + g_object_unref (widget); + return handled_event; + } + } + + /* Other events get propagated up/down the widget tree */ + return captured ? + propagate_event_down (widget, event, topmost) : + propagate_event_up (widget, event, topmost); +} + /** * gtk_propagate_event: * @widget: a #GtkWidget @@ -2359,79 +2502,16 @@ void gtk_propagate_event (GtkWidget *widget, GdkEvent *event) { - gint handled_event; - g_return_if_fail (GTK_IS_WIDGET (widget)); g_return_if_fail (event != NULL); - handled_event = FALSE; - - g_object_ref (widget); - - if ((event->type == GDK_KEY_PRESS) || - (event->type == GDK_KEY_RELEASE)) - { - /* Only send key events within Window widgets to the Window - * The Window widget will in turn pass the - * key event on to the currently focused widget - * for that window. - */ - GtkWidget *window; - - window = gtk_widget_get_toplevel (widget); - if (GTK_IS_WINDOW (window)) - { - /* If there is a grab within the window, give the grab widget - * a first crack at the key event - */ - if (widget != window && gtk_widget_has_grab (widget)) - handled_event = gtk_widget_event (widget, event); - - if (!handled_event) - { - window = gtk_widget_get_toplevel (widget); - if (GTK_IS_WINDOW (window)) - { - if (gtk_widget_is_sensitive (window)) - gtk_widget_event (window, event); - } - } - - handled_event = TRUE; /* don't send to widget */ - } - } - - /* Other events get propagated up the widget tree - * so that parents can see the button and motion - * events of the children. - */ - if (!handled_event) - { - while (TRUE) - { - GtkWidget *tmp; - - /* Scroll events are special cased here because it - * feels wrong when scrolling a GtkViewport, say, - * to have children of the viewport eat the scroll - * event - */ - if (!gtk_widget_is_sensitive (widget)) - handled_event = event->type != GDK_SCROLL; - else - handled_event = gtk_widget_event (widget, event); - - tmp = gtk_widget_get_parent (widget); - g_object_unref (widget); - - widget = tmp; - - if (!handled_event && widget) - g_object_ref (widget); - else - break; - } - } - else - g_object_unref (widget); + propagate_event (widget, event, FALSE, NULL); +} + +gboolean +_gtk_propagate_captured_event (GtkWidget *widget, + GdkEvent *event, + GtkWidget *topmost) +{ + return propagate_event (widget, event, TRUE, topmost); } diff --git a/gtk/gtkprivate.h b/gtk/gtkprivate.h index 63860a21a0..9114bd08b2 100644 --- a/gtk/gtkprivate.h +++ b/gtk/gtkprivate.h @@ -72,6 +72,10 @@ gboolean _gtk_translate_keyboard_accel_state (GdkKeymap *keymap, gint *level, GdkModifierType *consumed_modifiers); +gboolean _gtk_propagate_captured_event (GtkWidget *widget, + GdkEvent *event, + GtkWidget *topmost); + G_END_DECLS #endif /* __GTK_PRIVATE_H__ */ diff --git a/gtk/gtkwidget.c b/gtk/gtkwidget.c index c7406fc882..23ab0d8e62 100644 --- a/gtk/gtkwidget.c +++ b/gtk/gtkwidget.c @@ -706,6 +706,7 @@ static void gtk_widget_set_device_enabled_internal (GtkWidget *widget, GdkDevice *device, gboolean recurse, gboolean enabled); +static gboolean event_window_is_still_viewable (GdkEvent *event); /* --- variables --- */ static gpointer gtk_widget_parent_class = NULL; @@ -5951,6 +5952,59 @@ gtk_widget_event (GtkWidget *widget, return gtk_widget_event_internal (widget, event); } +void +_gtk_widget_set_captured_event_handler (GtkWidget *widget, + GtkCapturedEventHandler callback) +{ + g_object_set_data (G_OBJECT (widget), "captured-event-handler", callback); +} + +gboolean +_gtk_widget_captured_event (GtkWidget *widget, + GdkEvent *event) +{ + gboolean return_val = FALSE; + GtkCapturedEventHandler handler; + + g_return_val_if_fail (GTK_IS_WIDGET (widget), TRUE); + g_return_val_if_fail (WIDGET_REALIZED_FOR_EVENT (widget, event), TRUE); + + if (event->type == GDK_EXPOSE) + { + g_warning ("Events of type GDK_EXPOSE cannot be synthesized. To get " + "the same effect, call gdk_window_invalidate_rect/region(), " + "followed by gdk_window_process_updates()."); + return TRUE; + } + + if (!event_window_is_still_viewable (event)) + return TRUE; + + handler = g_object_get_data (G_OBJECT (widget), "captured-event-handler"); + if (!handler) + return FALSE; + + g_object_ref (widget); + + return_val = handler (widget, event); + return_val |= !WIDGET_REALIZED_FOR_EVENT (widget, event); + + /* The widget that was originally to receive the event + * handles motion hints, but the capturing widget might + * not, so ensure we get further motion events. + */ + if (return_val && + event->type == GDK_MOTION_NOTIFY && + event->motion.is_hint && + (gdk_window_get_events (event->any.window) & + GDK_POINTER_MOTION_HINT_MASK) != 0) + gdk_event_request_motions (&event->motion); + + g_object_unref (widget); + + return return_val; +} + /* Returns TRUE if a translation should be done */ gboolean _gtk_widget_get_translation_to_window (GtkWidget *widget, diff --git a/gtk/gtkwidget.h b/gtk/gtkwidget.h index 4fd7fce086..5a4cdbcbff 100644 --- a/gtk/gtkwidget.h +++ b/gtk/gtkwidget.h @@ -437,7 +437,6 @@ struct _GtkWidgetClass void (*_gtk_reserved5) (void); void (*_gtk_reserved6) (void); void (*_gtk_reserved7) (void); - void (*_gtk_reserved8) (void); }; struct _GtkWidgetAuxInfo diff --git a/gtk/gtkwidgetprivate.h b/gtk/gtkwidgetprivate.h index 58dd0e0861..9774c13fe8 100644 --- a/gtk/gtkwidgetprivate.h +++ b/gtk/gtkwidgetprivate.h @@ -165,6 +165,13 @@ GtkStyle * _gtk_widget_get_style (GtkWidget *widget); void _gtk_widget_set_style (GtkWidget *widget, GtkStyle *style); +typedef gboolean (*GtkCapturedEventHandler) (GtkWidget *widget, GdkEvent *event); + +void _gtk_widget_set_captured_event_handler (GtkWidget *widget, + GtkCapturedEventHandler handler); + +gboolean _gtk_widget_captured_event (GtkWidget *widget, + GdkEvent *event); G_END_DECLS