After an evaluation, GNOME has moved from Bugzilla to GitLab. Learn more about GitLab.
No new issues can be reported in GNOME Bugzilla anymore.
To report an issue in a GNOME project, go to GNOME GitLab.
Do not go to GNOME Gitlab for: Bluefish, Doxygen, GnuCash, GStreamer, java-gnome, LDTP, NetworkManager, Tomboy.
Bug 423761 - GtkMenu shouldn't close on clicks on its border
GtkMenu shouldn't close on clicks on its border
Status: RESOLVED FIXED
Product: gtk+
Classification: Platform
Component: Widget: GtkMenu
unspecified
Other All
: Normal normal
: ---
Assigned To: gtk-bugs
gtk-bugs
Depends on:
Blocks:
 
 
Reported: 2007-03-28 14:46 UTC by Michael Natterer
Modified: 2007-03-29 16:07 UTC
See Also:
GNOME target: ---
GNOME version: ---


Attachments
Patch fixing the above (2.43 KB, patch)
2007-03-28 14:47 UTC, Michael Natterer
none Details | Review
Updated patch (2.50 KB, patch)
2007-03-29 09:39 UTC, Michael Natterer
none Details | Review

Description Michael Natterer 2007-03-28 14:46:23 UTC
When clicking on an open menu's border, the menu closes. It does
however not close for clicks on other inactive parts such as
separators or inactive menu items. Fixing this is important because
the menu border is now themeable and not just one pixel any longer.

Patch from maemo-gtk (which does use a large menu border) follows.
Comment 1 Michael Natterer 2007-03-28 14:47:19 UTC
Created attachment 85456 [details] [review]
Patch fixing the above
Comment 2 Tim Janik 2007-03-28 16:03:21 UTC
Comment on attachment 85456 [details] [review]
Patch fixing the above

>+static GtkWidget *
>+gtk_menu_find_item (GdkEventButton *event)
>+{
>+  GtkWidget *menu_item;
>+
>+  menu_item = gtk_get_event_widget ((GdkEvent*) event);
>+
>+  while (menu_item && !GTK_IS_MENU_ITEM (menu_item))
>+    menu_item = menu_item->parent;

this code checking for menu items in combination with the code calling it below restricts clickable widgets in menus to menu items. i'm not sure that this is generally a good idea (e.g. check buttons could conceivably be used in menus at some point).

>+
>+  return menu_item;
>+}
>+
>+static gboolean
>+pointer_in_menu_tree (GtkWidget *widget)
>+{
>+  GtkMenuShell *menu_shell;
>+  gint x, y, width, height;
>+
>+  menu_shell = GTK_MENU_SHELL (widget);
>+
>+  gdk_window_get_pointer (widget->window, &x, &y, NULL);
>+  gdk_drawable_get_size (widget->window, &width, &height);
>+
>+  if ((x >= 0) && (x < width) && (y >= 0) && (y < height))
>+    return TRUE;

this code looks ok, except that the parenthesis around the comparisons are bogus, && and || were introduced to have a much lower precedence than comparisons (they are not broken wrg precedence like & and | are).

>+
>+  if (GTK_IS_MENU (menu_shell->parent_menu_shell))
>+    return pointer_in_menu_tree (menu_shell->parent_menu_shell);
>+
>+  return FALSE;
>+}
>+
> static gboolean
> gtk_menu_button_scroll (GtkMenu        *menu,
>                         GdkEventButton *event)
>@@ -2586,11 +2640,20 @@ gtk_menu_button_press (GtkWidget      *w
>   if (event->type != GDK_BUTTON_PRESS)
>     return FALSE;
> 
>-  /* Don't pop down the menu for presses over scroll arrows
>+  /* Don't pass down to menu shell for presses over scroll arrows
>    */
>   if (gtk_menu_button_scroll (GTK_MENU (widget), event))
>     return TRUE;
> 
>+  if (!gtk_menu_find_item (event))

hm, like i said above i don't like this approach very much.
this code is pro-actively searching for a set of widgets it considers worth processing events. and that's all done just to ignore button presses on window borders? i'd much rather have the code process events as normal and once not other widget processed the event, check whether the click was outside or inside a menu window.
do you think you can reform the patch towards that?

>+    {
>+      /*  Don't pass down to menu shell if a non-menuitem part of the menu
>+       *  was clicked.
>+       */
>+      if (pointer_in_menu_tree (widget))
>+        return TRUE;
>+    }
>+
>   return GTK_WIDGET_CLASS (gtk_menu_parent_class)->button_press_event (widget, event);
> }
Comment 3 Michael Natterer 2007-03-29 08:09:23 UTC
Right, checking for menu items only is wrong. I'll also fix the useless
parenthesis.

However chaining up to evil code like gtk_menu_shell_button_press,release()
first and then checking if we clicked the border is something I'd rather
like to avoid.

What about checking if gtk_get_event_widget() returns a GtkMenuShell
(which would mean we either clicked outside any menu or on a menu border)?
That would simplify the code quite a bit and avoid dealing with the
return value of weird GtkMenuShell handlers.
Comment 4 Michael Natterer 2007-03-29 09:39:52 UTC
Created attachment 85505 [details] [review]
Updated patch

- doesn't walk the widget hierarchy.
- doesn't have any GtkMenuItem special casing.
- doesn't cause any server roundtrip
Comment 5 Michael Natterer 2007-03-29 11:04:37 UTC
Oops, one of the "window_x" in the last patch should be "window_y". Fixed
in my tree, I won't upload a new patch just because of this.
Comment 6 Tim Janik 2007-03-29 11:48:43 UTC
Comment on attachment 85505 [details] [review]
Updated patch

> static gboolean
>+pointer_in_menu_tree (GtkWidget *widget,

this should be named pointer_in_menu_window() now, since that's what the new code checks and it also is a way better check.

>+                      gdouble    x_root,
>+                      gdouble    y_root)
>+{
>+  GtkMenu *menu = GTK_MENU (widget);
>+
>+  if (GTK_WIDGET_MAPPED (menu->toplevel))
>+    {
>+      GtkMenuShell *menu_shell;
>+      gint          window_x, window_y;
>+
>+      gdk_window_get_position (menu->toplevel->window, &window_x, &window_y);
>+
>+      if (x_root >= window_x && x_root < window_x + widget->allocation.width &&
>+          y_root >= window_y && y_root < window_x + widget->allocation.height)

i see, heres the x/y you caught already.

>+        return TRUE;
>+
>+      menu_shell = GTK_MENU_SHELL (widget);
>+
>+      if (GTK_IS_MENU (menu_shell->parent_menu_shell))
>+        return pointer_in_menu_tree (menu_shell->parent_menu_shell,
>+                                     x_root, y_root);
>+    }
>+
>+  return FALSE;
>+}
>+
>+static gboolean
> gtk_menu_button_press (GtkWidget      *widget,
>                        GdkEventButton *event)
> {
>   if (event->type != GDK_BUTTON_PRESS)
>     return FALSE;
> 
>-  /* Don't pop down the menu for presses over scroll arrows
>+  /* Don't pass down to menu shell for presses over scroll arrows
>    */
>   if (gtk_menu_button_scroll (GTK_MENU (widget), event))
>     return TRUE;
> 
>+  if (GTK_IS_MENU_SHELL (gtk_get_event_widget ((GdkEvent *) event)))

i don't think this extra check should be here. pointer_in_menu_window() should work without it, and for some cases it'll wrongly supress pointer_in_menu_window() checks (e.g. when clicking on the area created by window->border_width = 20).

>+    {
>+      /*  Don't pass down to menu shell if a non-menuitem part of the menu
>+       *  was clicked.
>+       */
>+      if (pointer_in_menu_tree (widget, event->x_root, event->y_root))
>+        return TRUE;
>+    }
>+
>   return GTK_WIDGET_CLASS (gtk_menu_parent_class)->button_press_event (widget, event);
> }
Comment 7 Michael Natterer 2007-03-29 13:25:29 UTC
Ok, pointer_in_menu_window() is better i guess.

The check for GTK_IS_MENU_SHELL() is IMHO correct to catch any
click on a non-child area of the window or outside. I've tested
both setting the window's border_width to != 0 and to increase
the menu's padding, both work fine.
Comment 8 Tim Janik 2007-03-29 14:46:28 UTC
(In reply to comment #7)
> Ok, pointer_in_menu_window() is better i guess.
> 
> The check for GTK_IS_MENU_SHELL() is IMHO correct to catch any
> click on a non-child area of the window or outside. I've tested
> both setting the window's border_width to != 0 and to increase
> the menu's padding, both work fine.

ok, after a bit of IM discussion it seems you're right. the reason it works is that because gtkmenu.c grabs with grab_window=menu_shell->window and owner_events=TRUE, gtk_get_event_widget() will always either return a child widget of menu_shell, or the menu_shell itself (events outside will be reported menu_shell->window relative).
so thanks for hashing this out, since you've also done proper testing i think this can go in now (adding the explanation i just gave as a comment wouldn't hurt though).
Comment 9 Michael Natterer 2007-03-29 16:07:06 UTC
Fixed in trunk:

2007-03-29  Michael Natterer  <mitch@imendio.com>

	Don't close menus on clicks on their border area (bug #423761).
	(modified patch from maemo-gtk).

	* gtk/gtkmenu.c (gtk_menu_button_press)
	(gtk_menu_button_release): bail out early if the click was on the
	menu's border (not on any item and not outside the window).

	(pointer_in_menu_window): new utility function which checks if
	passed root coords are inside the menu_shell or one of its
	parent shells.