gtkmenushell: Port to GtkGesture

We still need to poke the current event at places, but this is
better than the ::event vfunc.
This commit is contained in:
Carlos Garnacho 2018-06-07 15:32:47 +02:00
parent a5414bc404
commit 7bfc3a5c74

View File

@ -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)
event = gtk_get_current_event ();
if (!event)
return;
if (gdk_event_get_grab_surface (event, &surface))
{
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)
g_object_unref (event);
}
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;
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 (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;
}
}
if (!priv->active || !priv->button)
{
gboolean initially_active = priv->active;
guint button;
guint32 time;
button = gtk_gesture_single_get_current_button (GTK_GESTURE_SINGLE (gesture));
time = gdk_event_get_time (event);
priv->button = button;
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)
{
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);
}
}
}
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);
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;
}
if (priv->parent_menu_shell &&
(time - GTK_MENU_SHELL (priv->parent_menu_shell)->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
*/
GTK_MENU_SHELL (priv->parent_menu_shell)->priv->activate_time = 0;
return;
}
if (priv->active)
{
GtkWidget *menu_item;
guint button = priv->button;
priv->button = 0;
if (button && (new_button != button) && priv->parent_menu_shell)
{
gtk_menu_shell_deactivate_and_emit_done (gtk_menu_shell_get_toplevel_shell (menu_shell));
return;
}
if ((time - priv->activate_time) <= MENU_SHELL_TIMEOUT)
{
/* 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;
}
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) == widget)
if (menu_item)
{
if (menu_item != menu_shell->priv->active_menu_item)
GtkWidget *submenu = GTK_MENU_ITEM (menu_item)->priv->submenu;
GtkWidget *parent_menu_item_shell = gtk_widget_get_parent (menu_item);
if (!_gtk_menu_item_is_selectable (GTK_WIDGET (menu_item)))
return;
if (submenu == NULL)
{
/* 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).
gtk_menu_shell_activate_item (menu_shell, GTK_WIDGET (menu_item), TRUE);
return;
}
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))
{
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 (&current_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 (GTK_MENU_SHELL_GET_CLASS (menu_shell)->submenu_placement != GTK_TOP_BOTTOM)
gtk_menu_shell_select_item (menu_shell, menu_item);
}
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;
}
}
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);
priv->button = button;
if (menu_item)
{
if (_gtk_menu_item_is_selectable (menu_item) &&
gtk_widget_get_parent (menu_item) == widget &&
menu_item != priv->active_menu_item)
if (!activated_submenu &&
(usec_since_popup == 0 ||
usec_since_popup > MENU_POPDOWN_DELAY * 1000))
{
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);
}
_gtk_menu_item_popdown_submenu (menu_item);
}
}
else if (!initially_active)
{
gtk_menu_shell_deactivate (menu_shell);
return GDK_EVENT_STOP;
}
}
return GDK_EVENT_PROPAGATE;
}
else if (gdk_event_get_event_type (event) == GDK_BUTTON_RELEASE)
{
GtkMenuShell *parent_shell = GTK_MENU_SHELL (priv->parent_menu_shell);
gboolean activated_submenu = FALSE;
guint new_button;
guint32 time;
gdk_event_get_button (event, &new_button);
time = gdk_event_get_time (event);
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;
}
if (priv->parent_menu_shell &&
(time - GTK_MENU_SHELL (priv->parent_menu_shell)->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
*/
GTK_MENU_SHELL (priv->parent_menu_shell)->priv->activate_time = 0;
return GDK_EVENT_STOP;
}
if (priv->active)
{
GtkWidget *menu_item;
guint button = priv->button;
priv->button = 0;
if (button && (new_button != button) && priv->parent_menu_shell)
{
gtk_menu_shell_deactivate_and_emit_done (gtk_menu_shell_get_toplevel_shell (menu_shell));
return GDK_EVENT_STOP;
}
if ((time - priv->activate_time) <= MENU_SHELL_TIMEOUT)
{
/* 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;
}
menu_item = gtk_get_event_target_with_type (event, GTK_TYPE_MENU_ITEM);
if (menu_item)
{
GtkWidget *submenu = GTK_MENU_ITEM (menu_item)->priv->submenu;
GtkWidget *parent_menu_item_shell = gtk_widget_get_parent (menu_item);
if (!_gtk_menu_item_is_selectable (GTK_WIDGET (menu_item)))
return GDK_EVENT_STOP;
if (submenu == NULL)
else
{
gtk_menu_shell_activate_item (menu_shell, GTK_WIDGET (menu_item), TRUE);
return GDK_EVENT_STOP;
gtk_menu_item_select (GTK_MENU_ITEM (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))
{
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 (&current_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;
}
return;
}
gtk_menu_shell_deactivate_and_emit_done (gtk_menu_shell_get_toplevel_shell (menu_shell));
}
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