gtkmenu*: Simplify event handling

Instead of delegating on the parent shell of a menu item/shell on a variety
of situations, Simplify event handling so:
1) Menu item selection is handled entirely on GtkMenuItem through crossing
   events.
2) The deepmost menu shell handles clicks inside and outside of it.

This avoids the rather hard to follow gtk_widget_event() calls going on all
throughout the handling of crossing and button events, and makes menus work
again.
This commit is contained in:
Carlos Garnacho 2017-05-02 17:53:14 +02:00
parent 33c5f3c193
commit 40ab7e1c95
3 changed files with 141 additions and 565 deletions

View File

@ -279,14 +279,10 @@ static void gtk_menu_real_move_scroll (GtkMenu *menu,
GtkScrollType type);
static void gtk_menu_stop_navigating_submenu (GtkMenu *menu);
static gboolean gtk_menu_stop_navigating_submenu_cb (gpointer user_data);
static gboolean gtk_menu_navigating_submenu (GtkMenu *menu,
gint event_x,
gint event_y);
static void gtk_menu_set_submenu_navigation_region (GtkMenu *menu,
GtkMenuItem *menu_item,
GdkEventCrossing *event);
static void gtk_menu_deactivate (GtkMenuShell *menu_shell);
static void gtk_menu_position (GtkMenu *menu,
gboolean set_scroll_offset);
@ -1131,10 +1127,6 @@ gtk_menu_window_event (GtkWidget *window,
switch (event->type)
{
case GDK_KEY_PRESS:
case GDK_KEY_RELEASE:
handled = gtk_widget_event (menu, event);
break;
case GDK_WINDOW_STATE:
/* Window for the menu has been closed by the display server or by GDK.
* Update the internal state as if the user had clicked outside the
@ -3342,8 +3334,6 @@ gtk_menu_motion_notify (GtkWidget *widget,
GtkWidget *parent;
GdkDevice *source_device;
gboolean need_enter;
source_device = gdk_event_get_source_device ((GdkEvent *) event);
if (GTK_IS_MENU (widget) &&
@ -3379,8 +3369,6 @@ gtk_menu_motion_notify (GtkWidget *widget,
if (definitely_within_item (menu_item, event->x, event->y))
menu_shell->priv->activate_time = 0;
need_enter = (gtk_menu_has_navigation_triangle (menu) || menu_shell->priv->ignore_enter);
/* Check to see if we are within an active submenu's navigation region
*/
if (gtk_menu_navigating_submenu (menu, event->x_root, event->y_root))
@ -3398,42 +3386,6 @@ gtk_menu_motion_notify (GtkWidget *widget,
return FALSE;
}
if (need_enter)
{
/* The menu is now sensitive to enter events on its items, but
* was previously sensitive. So we fake an enter event.
*/
menu_shell->priv->ignore_enter = FALSE;
if (event->x >= 0 && event->x < gdk_window_get_width (event->window) &&
event->y >= 0 && event->y < gdk_window_get_height (event->window))
{
GdkEvent *send_event = gdk_event_new (GDK_ENTER_NOTIFY);
gboolean result;
send_event->crossing.window = g_object_ref (event->window);
send_event->crossing.time = event->time;
send_event->crossing.send_event = TRUE;
send_event->crossing.x_root = event->x_root;
send_event->crossing.y_root = event->y_root;
send_event->crossing.x = event->x;
send_event->crossing.y = event->y;
send_event->crossing.state = event->state;
gdk_event_set_device (send_event, gdk_event_get_device ((GdkEvent *) event));
/* We send the event to 'widget', the currently active menu,
* instead of 'menu', the menu that the pointer is in. This
* will ensure that the event will be ignored unless the
* menuitem is a child of the active menu or some parent
* menu of the active menu.
*/
result = gtk_widget_event (widget, send_event);
gdk_event_free (send_event);
return result;
}
}
return FALSE;
}
@ -3733,8 +3685,6 @@ static gboolean
gtk_menu_enter_notify (GtkWidget *widget,
GdkEventCrossing *event)
{
GtkWidget *menu_item;
GtkWidget *parent;
GdkDevice *source_device;
if (event->mode == GDK_CROSSING_GTK_GRAB ||
@ -3743,10 +3693,8 @@ gtk_menu_enter_notify (GtkWidget *widget,
return TRUE;
source_device = gdk_event_get_source_device ((GdkEvent *) event);
menu_item = gtk_get_event_widget ((GdkEvent*) event);
if (GTK_IS_MENU (widget) &&
gdk_device_get_source (source_device) != GDK_SOURCE_TOUCHSCREEN)
if (gdk_device_get_source (source_device) != GDK_SOURCE_TOUCHSCREEN)
{
GtkMenuShell *menu_shell = GTK_MENU_SHELL (widget);
@ -3755,64 +3703,14 @@ gtk_menu_enter_notify (GtkWidget *widget,
event->x_root, event->y_root, TRUE, TRUE);
}
if (gdk_device_get_source (source_device) != GDK_SOURCE_TOUCHSCREEN &&
GTK_IS_MENU_ITEM (menu_item))
{
GtkWidget *menu = gtk_widget_get_parent (menu_item);
if (GTK_IS_MENU (menu))
{
GtkMenuPrivate *priv = (GTK_MENU (menu))->priv;
GtkMenuShell *menu_shell = GTK_MENU_SHELL (menu);
if (priv->seen_item_enter)
{
/* This is the second enter we see for an item
* on this menu. This means a release should always
* mean activate.
*/
menu_shell->priv->activate_time = 0;
}
else if ((event->detail != GDK_NOTIFY_NONLINEAR &&
event->detail != GDK_NOTIFY_NONLINEAR_VIRTUAL))
{
if (definitely_within_item (menu_item, event->x, event->y))
{
/* This is an actual user-enter (ie. not a pop-under)
* In this case, the user must either have entered
* sufficiently far enough into the item, or he must move
* far enough away from the enter point. (see
* gtk_menu_motion_notify())
*/
menu_shell->priv->activate_time = 0;
}
}
priv->seen_item_enter = TRUE;
}
}
/* If this is a faked enter (see gtk_menu_motion_notify), 'widget'
* will not correspond to the event widget's parent. Check to see
* if we are in the parent's navigation region.
*/
parent = gtk_widget_get_parent (menu_item);
if (GTK_IS_MENU_ITEM (menu_item) && GTK_IS_MENU (parent) &&
gtk_menu_navigating_submenu (GTK_MENU (parent),
event->x_root, event->y_root))
return TRUE;
return GTK_WIDGET_CLASS (gtk_menu_parent_class)->enter_notify_event (widget, event);
return GDK_EVENT_STOP;
}
static gboolean
gtk_menu_leave_notify (GtkWidget *widget,
GdkEventCrossing *event)
{
GtkMenuShell *menu_shell;
GtkMenu *menu;
GtkMenuItem *menu_item;
GtkWidget *event_widget;
GdkDevice *source_device;
if (event->mode == GDK_CROSSING_GTK_GRAB ||
@ -3821,7 +3719,6 @@ gtk_menu_leave_notify (GtkWidget *widget,
return TRUE;
menu = GTK_MENU (widget);
menu_shell = GTK_MENU_SHELL (widget);
if (gtk_menu_navigating_submenu (menu, event->x_root, event->y_root))
return TRUE;
@ -3831,37 +3728,7 @@ gtk_menu_leave_notify (GtkWidget *widget,
if (gdk_device_get_source (source_device) != GDK_SOURCE_TOUCHSCREEN)
gtk_menu_handle_scrolling (menu, event->x_root, event->y_root, FALSE, TRUE);
event_widget = gtk_get_event_widget ((GdkEvent*) event);
if (!GTK_IS_MENU_ITEM (event_widget))
return TRUE;
menu_item = GTK_MENU_ITEM (event_widget);
/* Here we check to see if we're leaving an active menu item
* with a submenu, in which case we enter submenu navigation mode.
*/
if (menu_shell->priv->active_menu_item != NULL
&& menu_item->priv->submenu != NULL
&& menu_item->priv->submenu_placement == GTK_LEFT_RIGHT)
{
if (GTK_MENU_SHELL (menu_item->priv->submenu)->priv->active)
{
gtk_menu_set_submenu_navigation_region (menu, menu_item, event);
return TRUE;
}
else if (menu_item == GTK_MENU_ITEM (menu_shell->priv->active_menu_item))
{
/* We are leaving an active menu item with nonactive submenu.
* Deselect it so we don't surprise the user with by popping
* up a submenu _after_ he left the item.
*/
gtk_menu_shell_deselect (menu_shell);
return TRUE;
}
}
return GTK_WIDGET_CLASS (gtk_menu_parent_class)->leave_notify_event (widget, event);
return GDK_EVENT_STOP;
}
static gboolean
@ -4019,43 +3886,6 @@ gtk_menu_stop_navigating_submenu (GtkMenu *menu)
}
}
/* When the timeout is elapsed, the navigation region is destroyed
* and the menuitem under the pointer (if any) is selected.
*/
static gboolean
gtk_menu_stop_navigating_submenu_cb (gpointer user_data)
{
GtkMenuPopdownData *popdown_data = user_data;
GtkMenu *menu = popdown_data->menu;
GtkMenuPrivate *priv = menu->priv;
GdkWindow *child_window;
gtk_menu_stop_navigating_submenu (menu);
if (gtk_widget_get_realized (GTK_WIDGET (menu)))
{
child_window = gdk_window_get_device_position (priv->bin_window,
popdown_data->device,
NULL, NULL, NULL);
if (child_window)
{
GdkEvent *send_event = gdk_event_new (GDK_ENTER_NOTIFY);
send_event->crossing.window = g_object_ref (child_window);
send_event->crossing.time = GDK_CURRENT_TIME; /* Bogus */
send_event->crossing.send_event = TRUE;
gdk_event_set_device (send_event, popdown_data->device);
GTK_WIDGET_CLASS (gtk_menu_parent_class)->enter_notify_event (GTK_WIDGET (menu), (GdkEventCrossing *)send_event);
gdk_event_free (send_event);
}
}
return FALSE;
}
static gboolean
gtk_menu_navigating_submenu (GtkMenu *menu,
gint event_x,
@ -4103,87 +3933,6 @@ gtk_menu_navigating_submenu (GtkMenu *menu,
}
}
static void
gtk_menu_set_submenu_navigation_region (GtkMenu *menu,
GtkMenuItem *menu_item,
GdkEventCrossing *event)
{
GtkMenuPrivate *priv = menu->priv;
gint submenu_left = 0;
gint submenu_right = 0;
gint submenu_top = 0;
gint submenu_bottom = 0;
gint width = 0;
GtkWidget *event_widget;
GtkMenuPopdownData *popdown_data;
GdkWindow *window;
g_return_if_fail (menu_item->priv->submenu != NULL);
g_return_if_fail (event != NULL);
event_widget = gtk_get_event_widget ((GdkEvent*) event);
window = gtk_widget_get_window (menu_item->priv->submenu);
gdk_window_get_origin (window, &submenu_left, &submenu_top);
submenu_right = submenu_left + gdk_window_get_width (window);
submenu_bottom = submenu_top + gdk_window_get_height (window);
width = gdk_window_get_width (gtk_widget_get_window (event_widget));
if (event->x >= 0 && event->x < width)
{
gtk_menu_stop_navigating_submenu (menu);
/* The navigation region is the triangle closest to the x/y
* location of the rectangle. This is why the width or height
* can be negative.
*/
if (menu_item->priv->submenu_direction == GTK_DIRECTION_RIGHT)
{
/* right */
priv->navigation_x = submenu_left;
priv->navigation_width = event->x_root - submenu_left;
}
else
{
/* left */
priv->navigation_x = submenu_right;
priv->navigation_width = event->x_root - submenu_right;
}
if (event->y < 0)
{
/* top */
priv->navigation_y = event->y_root;
priv->navigation_height = submenu_top - event->y_root - NAVIGATION_REGION_OVERSHOOT;
if (priv->navigation_height >= 0)
return;
}
else
{
/* bottom */
priv->navigation_y = event->y_root;
priv->navigation_height = submenu_bottom - event->y_root + NAVIGATION_REGION_OVERSHOOT;
if (priv->navigation_height <= 0)
return;
}
popdown_data = g_new (GtkMenuPopdownData, 1);
popdown_data->menu = menu;
popdown_data->device = gdk_event_get_device ((GdkEvent *) event);
priv->navigation_timeout = gdk_threads_add_timeout_full (G_PRIORITY_DEFAULT,
MENU_POPDOWN_DELAY,
gtk_menu_stop_navigating_submenu_cb,
popdown_data,
(GDestroyNotify) g_free);
g_source_set_name_by_id (priv->navigation_timeout, "[gtk+] gtk_menu_stop_navigating_submenu_cb");
}
}
static void
gtk_menu_deactivate (GtkMenuShell *menu_shell)
{

View File

@ -1213,18 +1213,37 @@ static gboolean
gtk_menu_item_enter (GtkWidget *widget,
GdkEventCrossing *event)
{
g_return_val_if_fail (event != NULL, FALSE);
GtkWidget *menu_shell;
return gtk_widget_event (gtk_widget_get_parent (widget), (GdkEvent *) event);
if (event->mode == GDK_CROSSING_GTK_GRAB ||
event->mode == GDK_CROSSING_GTK_UNGRAB ||
event->mode == GDK_CROSSING_STATE_CHANGED)
return GDK_EVENT_STOP;
if (gdk_event_get_device ((GdkEvent*) event) ==
gdk_event_get_source_device ((GdkEvent*) event))
return GDK_EVENT_STOP;
menu_shell = gtk_widget_get_parent (widget);
if (GTK_IS_MENU_SHELL (menu_shell) && GTK_IS_MENU_ITEM (widget) &&
GTK_MENU_SHELL (menu_shell)->priv->active)
gtk_menu_shell_select_item (GTK_MENU_SHELL (menu_shell), widget);
return GDK_EVENT_STOP;
}
static gboolean
gtk_menu_item_leave (GtkWidget *widget,
GdkEventCrossing *event)
{
g_return_val_if_fail (event != NULL, FALSE);
GtkMenuItem *menu_item = GTK_MENU_ITEM (widget);
GtkWidget *menu_shell = gtk_widget_get_parent (widget);
return gtk_widget_event (gtk_widget_get_parent (widget), (GdkEvent*) event);
if (GTK_IS_MENU_SHELL (menu_shell) && !menu_item->priv->submenu)
gtk_menu_shell_deselect (GTK_MENU_SHELL (menu_shell));
return GDK_EVENT_STOP;
}
static void
@ -1567,14 +1586,7 @@ gtk_menu_item_popup_timeout (gpointer data)
parent = gtk_widget_get_parent (GTK_WIDGET (menu_item));
if (GTK_IS_MENU_SHELL (parent) && GTK_MENU_SHELL (parent)->priv->active)
{
gtk_menu_item_real_popup_submenu (GTK_WIDGET (menu_item), info->trigger_event, TRUE);
if (info->trigger_event &&
info->trigger_event->type != GDK_BUTTON_PRESS &&
info->trigger_event->type != GDK_ENTER_NOTIFY &&
priv->submenu)
GTK_MENU_SHELL (priv->submenu)->priv->ignore_enter = TRUE;
}
gtk_menu_item_real_popup_submenu (GTK_WIDGET (menu_item), info->trigger_event, TRUE);
priv->timer = 0;

View File

@ -123,10 +123,6 @@ static gint gtk_menu_shell_button_release (GtkWidget *widget,
GdkEventButton *event);
static gint gtk_menu_shell_key_press (GtkWidget *widget,
GdkEventKey *event);
static gint gtk_menu_shell_enter_notify (GtkWidget *widget,
GdkEventCrossing *event);
static gint gtk_menu_shell_leave_notify (GtkWidget *widget,
GdkEventCrossing *event);
static void gtk_menu_shell_screen_changed (GtkWidget *widget,
GdkScreen *previous_screen);
static gboolean gtk_menu_shell_grab_broken (GtkWidget *widget,
@ -142,10 +138,6 @@ static void gtk_menu_shell_real_insert (GtkMenuShell *menu_shell,
GtkWidget *child,
gint position);
static void gtk_real_menu_shell_deactivate (GtkMenuShell *menu_shell);
static gint gtk_menu_shell_is_item (GtkMenuShell *menu_shell,
GtkWidget *child);
static GtkWidget *gtk_menu_shell_get_item (GtkMenuShell *menu_shell,
GdkEvent *event);
static GType gtk_menu_shell_child_type (GtkContainer *container);
static void gtk_menu_shell_real_select_item (GtkMenuShell *menu_shell,
GtkWidget *menu_item);
@ -191,8 +183,6 @@ gtk_menu_shell_class_init (GtkMenuShellClass *klass)
widget_class->button_release_event = gtk_menu_shell_button_release;
widget_class->grab_broken_event = gtk_menu_shell_grab_broken;
widget_class->key_press_event = gtk_menu_shell_key_press;
widget_class->enter_notify_event = gtk_menu_shell_enter_notify;
widget_class->leave_notify_event = gtk_menu_shell_leave_notify;
widget_class->screen_changed = gtk_menu_shell_screen_changed;
container_class->add = gtk_menu_shell_add;
@ -598,6 +588,22 @@ gtk_menu_shell_activate (GtkMenuShell *menu_shell)
}
}
static void
gtk_menu_shell_deactivate_and_emit_done (GtkMenuShell *menu_shell)
{
gtk_menu_shell_deactivate (menu_shell);
g_signal_emit (menu_shell, menu_shell_signals[SELECTION_DONE], 0);
}
static GtkMenuShell *
gtk_menu_shell_get_toplevel_shell (GtkMenuShell *menu_shell)
{
while (menu_shell->priv->parent_menu_shell)
menu_shell = GTK_MENU_SHELL (menu_shell->priv->parent_menu_shell);
return menu_shell;
}
static gint
gtk_menu_shell_button_press (GtkWidget *widget,
GdkEventButton *event)
@ -605,24 +611,19 @@ gtk_menu_shell_button_press (GtkWidget *widget,
GtkMenuShell *menu_shell;
GtkMenuShellPrivate *priv;
GtkWidget *menu_item;
GtkWidget *parent;
if (event->type != GDK_BUTTON_PRESS)
return FALSE;
menu_shell = GTK_MENU_SHELL (widget);
priv = menu_shell->priv;
if (priv->parent_menu_shell)
return gtk_widget_event (priv->parent_menu_shell, (GdkEvent*) event);
menu_shell = GTK_MENU_SHELL (widget);
priv = menu_shell->priv;
menu_item = gtk_get_event_target_with_type ((GdkEvent *) event, GTK_TYPE_MENU_ITEM);
menu_item = gtk_menu_shell_get_item (menu_shell, (GdkEvent *)event);
if (menu_item && _gtk_menu_item_is_selectable (menu_item))
if (menu_item &&
_gtk_menu_item_is_selectable (menu_item) &&
gtk_widget_get_parent (menu_item) == widget)
{
parent = gtk_widget_get_parent (menu_item);
if (menu_item != GTK_MENU_SHELL (parent)->priv->active_menu_item)
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
@ -631,8 +632,15 @@ gtk_menu_shell_button_press (GtkWidget *widget,
* 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 (parent)->submenu_placement != GTK_TOP_BOTTOM)
gtk_menu_shell_select_item (GTK_MENU_SHELL (parent), menu_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;
}
}
@ -649,7 +657,6 @@ gtk_menu_shell_button_press (GtkWidget *widget,
menu_item != priv->active_menu_item)
{
gtk_menu_shell_activate (menu_shell);
priv->button = event->button;
if (GTK_MENU_SHELL_GET_CLASS (menu_shell)->submenu_placement == GTK_TOP_BOTTOM)
{
@ -658,34 +665,13 @@ gtk_menu_shell_button_press (GtkWidget *widget,
}
}
}
else
{
if (!initially_active)
{
gtk_menu_shell_deactivate (menu_shell);
return FALSE;
}
}
}
else
{
widget = gtk_get_event_widget ((GdkEvent*) event);
if (widget == GTK_WIDGET (menu_shell))
else if (!initially_active)
{
gtk_menu_shell_deactivate (menu_shell);
g_signal_emit (menu_shell, menu_shell_signals[SELECTION_DONE], 0);
return FALSE;
}
}
if (menu_item &&
_gtk_menu_item_is_selectable (menu_item) &&
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;
}
return TRUE;
}
@ -700,8 +686,7 @@ gtk_menu_shell_grab_broken (GtkWidget *widget,
{
/* Unset the active menu item so gtk_menu_popdown() doesn't see it. */
gtk_menu_shell_deselect (menu_shell);
gtk_menu_shell_deactivate (menu_shell);
g_signal_emit (menu_shell, menu_shell_signals[SELECTION_DONE], 0);
gtk_menu_shell_deactivate_and_emit_done (menu_shell);
}
return TRUE;
@ -713,6 +698,18 @@ gtk_menu_shell_button_release (GtkWidget *widget,
{
GtkMenuShell *menu_shell = GTK_MENU_SHELL (widget);
GtkMenuShellPrivate *priv = menu_shell->priv;
GtkMenuShell *parent_shell = GTK_MENU_SHELL (priv->parent_menu_shell);
gboolean activated_submenu = FALSE;
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 &&
(event->time - GTK_MENU_SHELL (priv->parent_menu_shell)->priv->activate_time) < MENU_SHELL_TIMEOUT)
@ -723,101 +720,23 @@ gtk_menu_shell_button_release (GtkWidget *widget,
* https://bugzilla.gnome.org/show_bug.cgi?id=703069
*/
GTK_MENU_SHELL (priv->parent_menu_shell)->priv->activate_time = 0;
return TRUE;
return GDK_EVENT_STOP;
}
if (priv->active)
{
GtkWidget *menu_item;
gboolean deactivate = TRUE;
if (priv->button && (event->button != priv->button))
{
priv->button = 0;
if (priv->parent_menu_shell)
return gtk_widget_event (priv->parent_menu_shell, (GdkEvent*) event);
}
gint button = priv->button;
priv->button = 0;
menu_item = gtk_menu_shell_get_item (menu_shell, (GdkEvent*) event);
if ((event->time - priv->activate_time) > MENU_SHELL_TIMEOUT)
if (button && (event->button != button) && priv->parent_menu_shell)
{
if (menu_item && (priv->active_menu_item == menu_item) &&
_gtk_menu_item_is_selectable (menu_item))
{
GtkWidget *submenu = GTK_MENU_ITEM (menu_item)->priv->submenu;
if (submenu == NULL)
{
gtk_menu_shell_activate_item (menu_shell, menu_item, TRUE);
deactivate = FALSE;
}
else if (GTK_MENU_SHELL_GET_CLASS (menu_shell)->submenu_placement != GTK_TOP_BOTTOM ||
priv->activated_submenu)
{
GTimeVal *popup_time;
gint64 usec_since_popup = 0;
popup_time = g_object_get_data (G_OBJECT (submenu),
"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 (submenu),
"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 (!priv->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));
}
deactivate = FALSE;
}
}
else if (menu_item &&
!_gtk_menu_item_is_selectable (menu_item) &&
GTK_MENU_SHELL_GET_CLASS (menu_shell)->submenu_placement != GTK_TOP_BOTTOM)
{
deactivate = FALSE;
}
else if (priv->parent_menu_shell)
{
priv->active = TRUE;
gtk_widget_event (priv->parent_menu_shell, (GdkEvent*) event);
deactivate = FALSE;
}
/* If we ended up on an item with a submenu, leave the menu up. */
if (menu_item &&
(priv->active_menu_item == menu_item) &&
GTK_MENU_SHELL_GET_CLASS (menu_shell)->submenu_placement != GTK_TOP_BOTTOM)
{
deactivate = FALSE;
}
gtk_menu_shell_deactivate_and_emit_done (gtk_menu_shell_get_toplevel_shell (menu_shell));
return GDK_EVENT_STOP;
}
else /* a very fast press-release */
if ((event->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
@ -827,19 +746,72 @@ gtk_menu_shell_button_release (GtkWidget *widget,
* serious harm if we lose.
*/
priv->activate_time = 0;
deactivate = FALSE;
return GDK_EVENT_STOP;
}
if (deactivate)
menu_item = gtk_get_event_target_with_type ((GdkEvent *) event, GTK_TYPE_MENU_ITEM);
if (menu_item)
{
gtk_menu_shell_deactivate (menu_shell);
g_signal_emit (menu_shell, menu_shell_signals[SELECTION_DONE], 0);
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)
{
gtk_menu_shell_activate_item (menu_shell, GTK_WIDGET (menu_item), TRUE);
return GDK_EVENT_STOP;
}
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;
}
}
priv->activated_submenu = FALSE;
gtk_menu_shell_deactivate_and_emit_done (gtk_menu_shell_get_toplevel_shell (menu_shell));
}
return TRUE;
return GDK_EVENT_STOP;
}
void
@ -929,126 +901,6 @@ gtk_menu_shell_key_press (GtkWidget *widget,
return gtk_menu_shell_activate_mnemonic (menu_shell, event);
}
static gint
gtk_menu_shell_enter_notify (GtkWidget *widget,
GdkEventCrossing *event)
{
GtkMenuShell *menu_shell = GTK_MENU_SHELL (widget);
GtkMenuShellPrivate *priv = menu_shell->priv;
if (event->mode == GDK_CROSSING_GTK_GRAB ||
event->mode == GDK_CROSSING_GTK_UNGRAB ||
event->mode == GDK_CROSSING_STATE_CHANGED)
return TRUE;
if (priv->active)
{
GtkWidget *menu_item;
GtkWidget *parent;
menu_item = gtk_get_event_widget ((GdkEvent*) event);
if (!menu_item)
return TRUE;
if (GTK_IS_MENU_ITEM (menu_item) &&
!_gtk_menu_item_is_selectable (menu_item))
{
priv->in_unselectable_item = TRUE;
return TRUE;
}
parent = gtk_widget_get_parent (menu_item);
if (parent == widget &&
GTK_IS_MENU_ITEM (menu_item))
{
if (priv->ignore_enter)
return TRUE;
if (event->detail != GDK_NOTIFY_INFERIOR)
{
if ((gtk_widget_get_state_flags (menu_item) & GTK_STATE_FLAG_PRELIGHT) == 0)
gtk_menu_shell_select_item (menu_shell, menu_item);
/* If any mouse button is down, and there is a submenu
* that is not yet visible, activate it. It's sufficient
* to check for any button's mask (not only the one
* matching menu_shell->button), because there is no
* situation a mouse button could be pressed while
* entering a menu item where we wouldn't want to show
* its submenu.
*/
if ((event->state & (GDK_BUTTON1_MASK|GDK_BUTTON2_MASK|GDK_BUTTON3_MASK)) &&
GTK_MENU_ITEM (menu_item)->priv->submenu != NULL)
{
GTK_MENU_SHELL (parent)->priv->activated_submenu = TRUE;
if (!gtk_widget_get_visible (GTK_MENU_ITEM (menu_item)->priv->submenu))
{
GdkDevice *source_device;
source_device = gdk_event_get_source_device ((GdkEvent *) event);
if (gdk_device_get_source (source_device) == GDK_SOURCE_TOUCHSCREEN)
_gtk_menu_item_popup_submenu (menu_item, TRUE);
}
}
}
}
else if (priv->parent_menu_shell)
{
gtk_widget_event (priv->parent_menu_shell, (GdkEvent*) event);
}
}
return TRUE;
}
static gint
gtk_menu_shell_leave_notify (GtkWidget *widget,
GdkEventCrossing *event)
{
if (event->mode == GDK_CROSSING_GTK_GRAB ||
event->mode == GDK_CROSSING_GTK_UNGRAB ||
event->mode == GDK_CROSSING_STATE_CHANGED)
return TRUE;
if (gtk_widget_get_visible (widget))
{
GtkMenuShell *menu_shell = GTK_MENU_SHELL (widget);
GtkMenuShellPrivate *priv = menu_shell->priv;
GtkWidget *event_widget = gtk_get_event_widget ((GdkEvent*) event);
GtkMenuItem *menu_item;
if (!event_widget || !GTK_IS_MENU_ITEM (event_widget))
return TRUE;
menu_item = GTK_MENU_ITEM (event_widget);
if (!_gtk_menu_item_is_selectable (event_widget))
{
priv->in_unselectable_item = TRUE;
return TRUE;
}
if ((priv->active_menu_item == event_widget) &&
(menu_item->priv->submenu == NULL))
{
if ((event->detail != GDK_NOTIFY_INFERIOR) &&
(gtk_widget_get_state_flags (GTK_WIDGET (menu_item)) & GTK_STATE_FLAG_PRELIGHT) != 0)
{
gtk_menu_shell_deselect (menu_shell);
}
}
else if (priv->parent_menu_shell)
{
gtk_widget_event (priv->parent_menu_shell, (GdkEvent*) event);
}
}
return TRUE;
}
static void
gtk_menu_shell_screen_changed (GtkWidget *widget,
GdkScreen *previous_screen)
@ -1144,43 +996,6 @@ gtk_real_menu_shell_deactivate (GtkMenuShell *menu_shell)
}
}
static gint
gtk_menu_shell_is_item (GtkMenuShell *menu_shell,
GtkWidget *child)
{
GtkWidget *parent;
g_return_val_if_fail (GTK_IS_MENU_SHELL (menu_shell), FALSE);
g_return_val_if_fail (child != NULL, FALSE);
parent = gtk_widget_get_parent (child);
while (GTK_IS_MENU_SHELL (parent))
{
if (parent == (GtkWidget*) menu_shell)
return TRUE;
parent = GTK_MENU_SHELL (parent)->priv->parent_menu_shell;
}
return FALSE;
}
static GtkWidget*
gtk_menu_shell_get_item (GtkMenuShell *menu_shell,
GdkEvent *event)
{
GtkWidget *menu_item;
menu_item = gtk_get_event_widget ((GdkEvent*) event);
while (menu_item && !GTK_IS_MENU_ITEM (menu_item))
menu_item = gtk_widget_get_parent (menu_item);
if (menu_item && gtk_menu_shell_is_item (menu_shell, menu_item))
return menu_item;
else
return NULL;
}
/* Handlers for action signals */
/**