gtk: Add a way to do event capture
This patch adds a capture phase to GTK+'s event propagation model. Events are first propagated from the toplevel (or the grab widget, if a grab is in place) down to the target widget and then back up. The second phase is using the existing ::event signal, the new capture phase is using a private API instead of a public signal for now. This mechanism can be used in many places where we currently have to prevent child widgets from getting events by putting an input-only window over them. It will also be used to implement kinetic scrolling in subsequent patches. http://bugzilla.gnome.org/show_bug.cgi?id=641836 We automatically request more motion events in behalf of the original widget if it listens to motion hints. So the capturing widget doesn't need to handle such implementation details. We are not making event capture part of the public API for 3.4, which is why there is no ::captured-event signal.
This commit is contained in:
parent
6c257040a5
commit
9f4bfff1b0
234
gtk/gtkmain.c
234
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);
|
||||
}
|
||||
|
@ -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__ */
|
||||
|
@ -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,
|
||||
|
@ -437,7 +437,6 @@ struct _GtkWidgetClass
|
||||
void (*_gtk_reserved5) (void);
|
||||
void (*_gtk_reserved6) (void);
|
||||
void (*_gtk_reserved7) (void);
|
||||
void (*_gtk_reserved8) (void);
|
||||
};
|
||||
|
||||
struct _GtkWidgetAuxInfo
|
||||
|
@ -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
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user