From 7bfc3a5c74742e541dc0451180056b6d647471f0 Mon Sep 17 00:00:00 2001 From: Carlos Garnacho Date: Thu, 7 Jun 2018 15:32:47 +0200 Subject: [PATCH] gtkmenushell: Port to GtkGesture We still need to poke the current event at places, but this is better than the ::event vfunc. --- gtk/gtkmenushell.c | 356 +++++++++++++++++++++++++-------------------- 1 file changed, 199 insertions(+), 157 deletions(-) diff --git a/gtk/gtkmenushell.c b/gtk/gtkmenushell.c index 8873345ffa..f9c497b8e5 100644 --- a/gtk/gtkmenushell.c +++ b/gtk/gtkmenushell.c @@ -76,6 +76,7 @@ #include "gtkwindow.h" #include "gtkwindowprivate.h" #include "gtkeventcontrollerkey.h" +#include "gtkgesturemultipress.h" #include "a11y/gtkmenushellaccessible.h" @@ -124,8 +125,20 @@ static gboolean gtk_menu_shell_key_press (GtkEventControllerKey *key, GtkWidget *widget); static void gtk_menu_shell_display_changed (GtkWidget *widget, GdkDisplay *previous_display); -static gboolean gtk_menu_shell_event (GtkWidget *widget, - GdkEvent *event); +static void multi_press_pressed (GtkGestureMultiPress *gesture, + gint n_press, + gdouble x, + gdouble y, + GtkMenuShell *menu_shell); +static void multi_press_released (GtkGestureMultiPress *gesture, + gint n_press, + gdouble x, + gdouble y, + GtkMenuShell *menu_shell); +static void multi_press_stopped (GtkGestureMultiPress *gesture, + GtkMenuShell *menu_shell); + + static void gtk_menu_shell_add (GtkContainer *container, GtkWidget *widget); static void gtk_menu_shell_remove (GtkContainer *container, @@ -180,7 +193,6 @@ gtk_menu_shell_class_init (GtkMenuShellClass *klass) object_class->finalize = gtk_menu_shell_finalize; object_class->dispose = gtk_menu_shell_dispose; - widget_class->event = gtk_menu_shell_event; widget_class->display_changed = gtk_menu_shell_display_changed; container_class->add = gtk_menu_shell_add; @@ -422,6 +434,15 @@ gtk_menu_shell_init (GtkMenuShell *menu_shell) gtk_widget_add_controller (widget, controller); gtk_widget_set_has_surface (widget, FALSE); + + controller = GTK_EVENT_CONTROLLER (gtk_gesture_multi_press_new ()); + g_signal_connect (controller, "pressed", + G_CALLBACK (multi_press_pressed), menu_shell); + g_signal_connect (controller, "released", + G_CALLBACK (multi_press_released), menu_shell); + g_signal_connect (controller, "stopped", + G_CALLBACK (multi_press_stopped), menu_shell); + gtk_widget_add_controller (widget, controller); } static void @@ -605,217 +626,238 @@ gtk_menu_shell_get_toplevel_shell (GtkMenuShell *menu_shell) return menu_shell; } -static gboolean -gtk_menu_shell_event (GtkWidget *widget, - GdkEvent *event) +static void +multi_press_stopped (GtkGestureMultiPress *gesture, + GtkMenuShell *menu_shell) { - GtkMenuShell *menu_shell = GTK_MENU_SHELL (widget); GtkMenuShellPrivate *priv = menu_shell->priv; GdkSurface *surface; + GdkEvent *event; - if (gdk_event_get_event_type (event) == GDK_GRAB_BROKEN) - { - gdk_event_get_grab_surface (event, &surface); + event = gtk_get_current_event (); + if (!event) + return; + if (gdk_event_get_grab_surface (event, &surface)) + { if (priv->have_xgrab && surface == NULL) { /* Unset the active menu item so gtk_menu_popdown() doesn't see it. */ gtk_menu_shell_deselect (menu_shell); gtk_menu_shell_deactivate_and_emit_done (menu_shell); } - - return GDK_EVENT_STOP; } - else if (gdk_event_get_event_type (event) == GDK_BUTTON_PRESS) - { - GtkWidget *menu_item; - menu_item = gtk_get_event_target_with_type (event, GTK_TYPE_MENU_ITEM); + g_object_unref (event); +} - if (menu_item && - _gtk_menu_item_is_selectable (menu_item) && - gtk_widget_get_parent (menu_item) == widget) - { - if (menu_item != menu_shell->priv->active_menu_item) - { - /* select the menu item *before* activating the shell, so submenus - * which might be open are closed the friendly way. If we activate - * (and thus grab) this menu shell first, we might get grab_broken - * events which will close the entire menu hierarchy. Selecting the - * menu item also fixes up the state as if enter_notify() would - * have run before (which normally selects the item). - */ - if (GTK_MENU_SHELL_GET_CLASS (menu_shell)->submenu_placement != GTK_TOP_BOTTOM) - gtk_menu_shell_select_item (menu_shell, menu_item); - } +static void +multi_press_pressed (GtkGestureMultiPress *gesture, + gint n_press, + gdouble x, + gdouble y, + GtkMenuShell *menu_shell) +{ + GtkMenuShellPrivate *priv = menu_shell->priv; + GtkWidget *menu_item; + GdkEvent *event; - if (GTK_MENU_ITEM (menu_item)->priv->submenu != NULL && - !gtk_widget_get_visible (GTK_MENU_ITEM (menu_item)->priv->submenu)) - { - _gtk_menu_item_popup_submenu (menu_item, FALSE); - priv->activated_submenu = TRUE; - } + event = gtk_get_current_event (); + menu_item = gtk_get_event_target_with_type (event, GTK_TYPE_MENU_ITEM); + + if (menu_item && + _gtk_menu_item_is_selectable (menu_item) && + gtk_widget_get_parent (menu_item) == GTK_WIDGET (menu_shell)) + { + if (menu_item != menu_shell->priv->active_menu_item) + { + /* select the menu item *before* activating the shell, so submenus + * which might be open are closed the friendly way. If we activate + * (and thus grab) this menu shell first, we might get grab_broken + * events which will close the entire menu hierarchy. Selecting the + * menu item also fixes up the state as if enter_notify() would + * have run before (which normally selects the item). + */ + if (GTK_MENU_SHELL_GET_CLASS (menu_shell)->submenu_placement != GTK_TOP_BOTTOM) + gtk_menu_shell_select_item (menu_shell, menu_item); } - if (!priv->active || !priv->button) + if (GTK_MENU_ITEM (menu_item)->priv->submenu != NULL && + !gtk_widget_get_visible (GTK_MENU_ITEM (menu_item)->priv->submenu)) { - gboolean initially_active = priv->active; - guint button; - guint32 time; + _gtk_menu_item_popup_submenu (menu_item, FALSE); + priv->activated_submenu = TRUE; + } + } + + if (!priv->active || !priv->button) + { + gboolean initially_active = priv->active; + guint button; + guint32 time; - gdk_event_get_button (event, &button); - time = gdk_event_get_time (event); + button = gtk_gesture_single_get_current_button (GTK_GESTURE_SINGLE (gesture)); + time = gdk_event_get_time (event); - priv->button = button; + priv->button = button; - if (menu_item) + if (menu_item) + { + if (_gtk_menu_item_is_selectable (menu_item) && + gtk_widget_get_parent (menu_item) == GTK_WIDGET (menu_shell) && + menu_item != priv->active_menu_item) { - if (_gtk_menu_item_is_selectable (menu_item) && - gtk_widget_get_parent (menu_item) == widget && - menu_item != priv->active_menu_item) - { - gtk_menu_shell_activate (menu_shell); + gtk_menu_shell_activate (menu_shell); - if (GTK_MENU_SHELL_GET_CLASS (menu_shell)->submenu_placement == GTK_TOP_BOTTOM) - { - priv->activate_time = time; - gtk_menu_shell_select_item (menu_shell, menu_item); - } + if (GTK_MENU_SHELL_GET_CLASS (menu_shell)->submenu_placement == GTK_TOP_BOTTOM) + { + priv->activate_time = time; + gtk_menu_shell_select_item (menu_shell, menu_item); } } - else if (!initially_active) - { - gtk_menu_shell_deactivate (menu_shell); - return GDK_EVENT_STOP; - } } + else if (!initially_active) + { + gtk_menu_shell_deactivate (menu_shell); + gtk_gesture_set_state (GTK_GESTURE (gesture), + GTK_EVENT_SEQUENCE_CLAIMED); + } + } + + g_object_unref (event); +} + +static void +multi_press_released (GtkGestureMultiPress *gesture, + gint n_press, + gdouble x, + gdouble y, + GtkMenuShell *menu_shell) +{ + GtkMenuShellPrivate *priv = menu_shell->priv; + GtkMenuShell *parent_shell = GTK_MENU_SHELL (priv->parent_menu_shell); + gboolean activated_submenu = FALSE; + guint new_button; + guint32 time; + GdkEvent *event; + + event = gtk_get_current_event (); + new_button = gtk_gesture_single_get_current_button (GTK_GESTURE_SINGLE (gesture)); + time = gdk_event_get_time (event); - return GDK_EVENT_PROPAGATE; + gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED); + + if (parent_shell) + { + /* If a submenu was just activated, it is its shell which is receiving + * the button release event. In this case, we must check the parent + * shell to know about the submenu state. + */ + activated_submenu = parent_shell->priv->activated_submenu; + parent_shell->priv->activated_submenu = FALSE; } - else if (gdk_event_get_event_type (event) == GDK_BUTTON_RELEASE) + + if (priv->parent_menu_shell && + (time - GTK_MENU_SHELL (priv->parent_menu_shell)->priv->activate_time) < MENU_SHELL_TIMEOUT) { - GtkMenuShell *parent_shell = GTK_MENU_SHELL (priv->parent_menu_shell); - gboolean activated_submenu = FALSE; - guint new_button; - guint32 time; + /* The button-press originated in the parent menu bar and we are + * a pop-up menu. It was a quick press-and-release so we don't want + * to activate an item but we leave the popup in place instead. + * https://bugzilla.gnome.org/show_bug.cgi?id=703069 + */ + GTK_MENU_SHELL (priv->parent_menu_shell)->priv->activate_time = 0; + return; + } - gdk_event_get_button (event, &new_button); - time = gdk_event_get_time (event); + if (priv->active) + { + GtkWidget *menu_item; + guint button = priv->button; - if (parent_shell) + priv->button = 0; + + if (button && (new_button != button) && priv->parent_menu_shell) { - /* If a submenu was just activated, it is its shell which is receiving - * the button release event. In this case, we must check the parent - * shell to know about the submenu state. - */ - activated_submenu = parent_shell->priv->activated_submenu; - parent_shell->priv->activated_submenu = FALSE; + gtk_menu_shell_deactivate_and_emit_done (gtk_menu_shell_get_toplevel_shell (menu_shell)); + return; } - if (priv->parent_menu_shell && - (time - GTK_MENU_SHELL (priv->parent_menu_shell)->priv->activate_time) < MENU_SHELL_TIMEOUT) + if ((time - priv->activate_time) <= MENU_SHELL_TIMEOUT) { - /* The button-press originated in the parent menu bar and we are - * a pop-up menu. It was a quick press-and-release so we don't want - * to activate an item but we leave the popup in place instead. - * https://bugzilla.gnome.org/show_bug.cgi?id=703069 + /* We only ever want to prevent deactivation on the first + * press/release. Setting the time to zero is a bit of a + * hack, since we could be being triggered in the first + * few fractions of a second after a server time wraparound. + * the chances of that happening are ~1/10^6, without + * serious harm if we lose. */ - GTK_MENU_SHELL (priv->parent_menu_shell)->priv->activate_time = 0; - return GDK_EVENT_STOP; + priv->activate_time = 0; + return; } - if (priv->active) + menu_item = gtk_get_event_target_with_type (event, GTK_TYPE_MENU_ITEM); + + if (menu_item) { - GtkWidget *menu_item; - guint button = priv->button; + GtkWidget *submenu = GTK_MENU_ITEM (menu_item)->priv->submenu; + GtkWidget *parent_menu_item_shell = gtk_widget_get_parent (menu_item); - priv->button = 0; + if (!_gtk_menu_item_is_selectable (GTK_WIDGET (menu_item))) + return; - if (button && (new_button != button) && priv->parent_menu_shell) + if (submenu == NULL) { - gtk_menu_shell_deactivate_and_emit_done (gtk_menu_shell_get_toplevel_shell (menu_shell)); - return GDK_EVENT_STOP; + gtk_menu_shell_activate_item (menu_shell, GTK_WIDGET (menu_item), TRUE); + return; } - - if ((time - priv->activate_time) <= MENU_SHELL_TIMEOUT) + else if (GTK_MENU_SHELL (parent_menu_item_shell)->priv->parent_menu_shell && + (activated_submenu || + GTK_MENU_SHELL_GET_CLASS (menu_shell)->submenu_placement != GTK_TOP_BOTTOM)) { - /* We only ever want to prevent deactivation on the first - * press/release. Setting the time to zero is a bit of a - * hack, since we could be being triggered in the first - * few fractions of a second after a server time wraparound. - * the chances of that happening are ~1/10^6, without - * serious harm if we lose. - */ - priv->activate_time = 0; - return GDK_EVENT_STOP; - } + GTimeVal *popup_time; + gint64 usec_since_popup = 0; - menu_item = gtk_get_event_target_with_type (event, GTK_TYPE_MENU_ITEM); + popup_time = g_object_get_data (G_OBJECT (menu_shell), + "gtk-menu-exact-popup-time"); + if (popup_time) + { + GTimeVal current_time; - if (menu_item) - { - GtkWidget *submenu = GTK_MENU_ITEM (menu_item)->priv->submenu; - GtkWidget *parent_menu_item_shell = gtk_widget_get_parent (menu_item); + g_get_current_time (¤t_time); - if (!_gtk_menu_item_is_selectable (GTK_WIDGET (menu_item))) - return GDK_EVENT_STOP; + usec_since_popup = ((gint64) current_time.tv_sec * 1000 * 1000 + + (gint64) current_time.tv_usec - + (gint64) popup_time->tv_sec * 1000 * 1000 - + (gint64) popup_time->tv_usec); + + g_object_set_data (G_OBJECT (menu_shell), + "gtk-menu-exact-popup-time", NULL); + } - if (submenu == NULL) + /* Only close the submenu on click if we opened the + * menu explicitly (usec_since_popup == 0) or + * enough time has passed since it was opened by + * GtkMenuItem's timeout (usec_since_popup > delay). + */ + if (!activated_submenu && + (usec_since_popup == 0 || + usec_since_popup > MENU_POPDOWN_DELAY * 1000)) { - gtk_menu_shell_activate_item (menu_shell, GTK_WIDGET (menu_item), TRUE); - return GDK_EVENT_STOP; + _gtk_menu_item_popdown_submenu (menu_item); } - else if (GTK_MENU_SHELL (parent_menu_item_shell)->priv->parent_menu_shell && - (activated_submenu || - GTK_MENU_SHELL_GET_CLASS (menu_shell)->submenu_placement != GTK_TOP_BOTTOM)) + else { - GTimeVal *popup_time; - gint64 usec_since_popup = 0; - - popup_time = g_object_get_data (G_OBJECT (menu_shell), - "gtk-menu-exact-popup-time"); - if (popup_time) - { - GTimeVal current_time; - - g_get_current_time (¤t_time); - - usec_since_popup = ((gint64) current_time.tv_sec * 1000 * 1000 + - (gint64) current_time.tv_usec - - (gint64) popup_time->tv_sec * 1000 * 1000 - - (gint64) popup_time->tv_usec); - - g_object_set_data (G_OBJECT (menu_shell), - "gtk-menu-exact-popup-time", NULL); - } - - /* Only close the submenu on click if we opened the - * menu explicitly (usec_since_popup == 0) or - * enough time has passed since it was opened by - * GtkMenuItem's timeout (usec_since_popup > delay). - */ - if (!activated_submenu && - (usec_since_popup == 0 || - usec_since_popup > MENU_POPDOWN_DELAY * 1000)) - { - _gtk_menu_item_popdown_submenu (menu_item); - } - else - { - gtk_menu_item_select (GTK_MENU_ITEM (menu_item)); - } - - return GDK_EVENT_STOP; + gtk_menu_item_select (GTK_MENU_ITEM (menu_item)); } - } - gtk_menu_shell_deactivate_and_emit_done (gtk_menu_shell_get_toplevel_shell (menu_shell)); + return; + } } - return GDK_EVENT_STOP; + gtk_menu_shell_deactivate_and_emit_done (gtk_menu_shell_get_toplevel_shell (menu_shell)); } - return GDK_EVENT_PROPAGATE; + g_object_unref (event); } void -- 2.30.2