Submenu hysteresis patch
- From: Mrcooger <mrcooger cyberverse com>
- To: gtk-devel-list redhat com
- Subject: Submenu hysteresis patch
- Date: Wed, 5 Jul 2000 15:41:54 -0700 (PDT)
I've taken Nils Barth's excellent submenu navigation patch and performed
some manipulations in an attempt to finish/clean it up.
* Created a padding area for the navigation region, so that the region is
much easier to get into when navigating into a submenu that is not to the
right and below the current menu item.
* Added a timeout of about 1/3rd of a second. Easily changable with a
#define.
* Moved all the code into gtkmenu.c (and it works there quite naturally, I
think). This involved adding enter/leave_notify handlers (I tried
gtk_grab_Add() but could not get it to work).
Please let me know what you think and what changes have to be made.
Thanks,
David :)
Index: gtkmenu.c
===================================================================
RCS file: /cvs/gnome/gtk+/gtk/gtkmenu.c,v
retrieving revision 1.46
diff -u -r1.46 gtkmenu.c
--- gtkmenu.c 2000/06/20 21:04:38 1.46
+++ gtkmenu.c 2000/07/05 22:20:47
@@ -34,10 +34,12 @@
#include "gtksignal.h"
#include "gtkwindow.h"
-
#define MENU_ITEM_CLASS(w) GTK_MENU_ITEM_GET_CLASS (w)
#define MENU_NEEDS_RESIZE(m) GTK_MENU_SHELL (m)->menu_flag
+#define SUBMENU_NAV_REGION_PADDING 2
+#define SUBMENU_NAV_HYSTERESIS_TIMEOUT 333
+
typedef struct _GtkMenuAttachData GtkMenuAttachData;
struct _GtkMenuAttachData
@@ -64,6 +66,16 @@
GdkEventKey *event);
static gint gtk_menu_motion_notify (GtkWidget *widget,
GdkEventMotion *event);
+static gint gtk_menu_enter_notify (GtkWidget *widget,
+ GdkEventCrossing *event);
+static gint gtk_menu_leave_notify (GtkWidget *widget,
+ GdkEventCrossing *event);
+static void gtk_menu_stop_navigating_submenu (void);
+static gint gtk_menu_stop_navigating_submenu_cb (gpointer user_data);
+static gboolean gtk_menu_navigating_submenu (gint event_x,
+ gint event_y);
+static void gtk_menu_set_submenu_navigation_region (GtkMenuItem *menu_item,
+ GdkEventCrossing *event);
static void gtk_menu_deactivate (GtkMenuShell *menu_shell);
static void gtk_menu_show_all (GtkWidget *widget);
static void gtk_menu_hide_all (GtkWidget *widget);
@@ -75,8 +87,8 @@
static GtkMenuShellClass *parent_class = NULL;
static const gchar *attach_data_key = "gtk-menu-attach-data";
static GQuark quark_uline_accel_group = 0;
+static GdkRegion *navigation_region = NULL;
-
GtkType
gtk_menu_get_type (void)
{
@@ -129,6 +141,8 @@
widget_class->motion_notify_event = gtk_menu_motion_notify;
widget_class->show_all = gtk_menu_show_all;
widget_class->hide_all = gtk_menu_hide_all;
+ widget_class->enter_notify_event = gtk_menu_enter_notify;
+ widget_class->leave_notify_event = gtk_menu_leave_notify;
menu_shell_class->submenu_placement = GTK_LEFT_RIGHT;
menu_shell_class->deactivate = gtk_menu_deactivate;
@@ -1029,6 +1043,8 @@
menu_shell = GTK_MENU_SHELL (widget);
+ gtk_menu_stop_navigating_submenu ();
+
if (GTK_WIDGET_CLASS (parent_class)->key_press_event (widget, event))
return TRUE;
@@ -1106,11 +1122,21 @@
gtk_menu_motion_notify (GtkWidget *widget,
GdkEventMotion *event)
{
+ GtkMenu *menu;
+ GtkMenuShell *menu_shell;
+
g_return_val_if_fail (widget != NULL, FALSE);
g_return_val_if_fail (GTK_IS_MENU (widget), FALSE);
- if (GTK_MENU_SHELL (widget)->ignore_enter)
- GTK_MENU_SHELL (widget)->ignore_enter = FALSE;
+ menu = GTK_MENU (widget);
+
+ if (gtk_menu_navigating_submenu (event->x_root, event->y_root))
+ return TRUE;
+
+ menu_shell = GTK_MENU_SHELL (widget);
+
+ if (menu_shell->ignore_enter)
+ menu_shell->ignore_enter = FALSE;
else
{
gint width, height;
@@ -1131,6 +1157,225 @@
}
return FALSE;
+}
+
+static gint
+gtk_menu_enter_notify (GtkWidget *widget,
+ GdkEventCrossing *event)
+{
+ GtkMenu *menu;
+ GtkWidget *event_widget;
+
+ g_return_val_if_fail (widget != NULL, FALSE);
+ g_return_val_if_fail (GTK_IS_MENU_SHELL (widget), FALSE);
+ g_return_val_if_fail (event != NULL, FALSE);
+
+ menu = GTK_MENU (widget);
+
+ if (GTK_MENU_SHELL (menu)->active && !GTK_MENU_SHELL (menu)->ignore_enter)
+ {
+ event_widget = gtk_get_event_widget ((GdkEvent*) event);
+
+ if (!event_widget || !GTK_WIDGET_IS_SENSITIVE (event_widget))
+ return TRUE;
+
+ if (gtk_menu_navigating_submenu (event->x_root, event->y_root))
+ return TRUE; /* Don't do anything -- we're navigating */
+
+ if ((event_widget->parent == widget) &&
+ (GTK_MENU_SHELL (menu)->active_menu_item != event_widget) &&
+ GTK_IS_MENU_ITEM (event_widget))
+ {
+ if ((event->detail != GDK_NOTIFY_INFERIOR) &&
+ (GTK_WIDGET_STATE (event_widget) != GTK_STATE_PRELIGHT))
+ {
+ gtk_menu_shell_select_item (GTK_MENU_SHELL (menu), event_widget);
+ }
+ }
+ else if (GTK_MENU_SHELL (menu)->parent_menu_shell)
+ {
+ gtk_widget_event (GTK_MENU_SHELL (menu)->parent_menu_shell, (GdkEvent*) event);
+ }
+ }
+
+ return TRUE;
+}
+
+static gint
+gtk_menu_leave_notify (GtkWidget *widget,
+ GdkEventCrossing *event)
+{
+ GtkMenu *menu;
+ GtkMenuItem *menu_item;
+ GtkWidget *event_widget;
+
+ g_return_val_if_fail (widget != NULL, FALSE);
+ g_return_val_if_fail (GTK_IS_MENU_SHELL (widget), FALSE);
+ g_return_val_if_fail (event != NULL, FALSE);
+
+ if (GTK_WIDGET_VISIBLE (widget))
+ {
+ menu = GTK_MENU (widget);
+ event_widget = gtk_get_event_widget ((GdkEvent*) event);
+
+ if (!event_widget || !GTK_IS_MENU_ITEM (event_widget))
+ return TRUE;
+
+ menu_item = GTK_MENU_ITEM (event_widget);
+
+ if (GTK_MENU_SHELL (menu)->ignore_leave)
+ {
+ GTK_MENU_SHELL (menu)->ignore_leave = FALSE;
+ return TRUE;
+ }
+
+ if (!GTK_WIDGET_IS_SENSITIVE (menu_item))
+ return TRUE;
+
+ if (gtk_menu_navigating_submenu (event->x_root, event->y_root))
+ return TRUE; /* Don't do anything -- we're navigating */
+
+ /* 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 (GTK_MENU_SHELL (menu)->active_menu_item != NULL
+ && menu_item->submenu != NULL
+ && menu_item->submenu_placement == GTK_LEFT_RIGHT)
+ {
+ if (menu_item->submenu->window != NULL)
+ {
+ gtk_menu_set_submenu_navigation_region (menu_item, event);
+ return TRUE;
+ }
+ }
+
+ if ((GTK_MENU_SHELL (menu)->active_menu_item == event_widget) &&
+ (menu_item->submenu == NULL))
+ {
+ if ((event->detail != GDK_NOTIFY_INFERIOR) &&
+ (GTK_WIDGET_STATE (menu_item) != GTK_STATE_NORMAL))
+ {
+ gtk_menu_shell_deselect (GTK_MENU_SHELL (menu));
+ }
+ }
+ else if (GTK_MENU_SHELL (menu)->parent_menu_shell)
+ {
+ gtk_widget_event (GTK_MENU_SHELL (menu)->parent_menu_shell, (GdkEvent*) event);
+ }
+ }
+
+ return TRUE;
+}
+
+static void
+gtk_menu_stop_navigating_submenu (void)
+{
+ if (navigation_region)
+ {
+ gdk_region_destroy (navigation_region);
+ navigation_region = NULL;
+ }
+}
+
+static gint
+gtk_menu_stop_navigating_submenu_cb (gpointer user_data)
+{
+ GdkEvent send_event;
+ GdkWindow *window_at_pointer;
+
+ gtk_menu_stop_navigating_submenu ();
+
+ /* Here we get the mouse pointer and send an enter event
+ to the window currently under the mouse. That way, if the
+ user is navigating a submenu, and the timeout runs out,
+ the menu item under his mouse is selected without him having
+ to move the mouse one more time for a new motion_notify event. */
+ window_at_pointer = gdk_window_at_pointer (NULL, NULL);
+
+ send_event.crossing.type = GDK_ENTER_NOTIFY;
+ send_event.crossing.window = window_at_pointer;
+ send_event.crossing.time = GDK_CURRENT_TIME;
+ send_event.crossing.send_event = TRUE;
+
+ gtk_widget_event (GTK_WIDGET (user_data), &send_event);
+
+ return FALSE;
+}
+
+static gboolean
+gtk_menu_navigating_submenu (gint event_x, gint event_y)
+{
+ if (navigation_region)
+ {
+ if (gdk_region_point_in (navigation_region, event_x, event_y))
+ return TRUE;
+ else
+ {
+ gtk_menu_stop_navigating_submenu ();
+ return FALSE;
+ }
+ }
+ return FALSE;
+}
+
+static void
+gtk_menu_set_submenu_navigation_region (GtkMenuItem *menu_item,
+ GdkEventCrossing *event)
+{
+ gint submenu_left = 0, submenu_right = 0;
+ gint submenu_top = 0, submenu_bottom = 0;
+ gint width = 0, height = 0;
+ GdkPoint point[3];
+ GtkWidget *event_widget;
+
+ g_return_if_fail (menu_item->submenu != NULL);
+ g_return_if_fail (event != NULL);
+
+ event_widget = gtk_get_event_widget ((GdkEvent*) event);
+
+ gdk_window_get_root_origin (menu_item->submenu->window,
+ &submenu_left, &submenu_top);
+ gdk_window_get_size (menu_item->submenu->window, &width, &height);
+ submenu_right = submenu_left + width;
+ submenu_bottom = submenu_top + height;
+ gdk_window_get_size (event_widget->window, &width, &height);
+
+ if ((event->x >= 0) && (event->x <= width)
+ && (((event->y < 0) && (event->y_root >= submenu_top))
+ || ((event->y >= 0) && (event->y_root <= submenu_bottom))))
+ {
+ /* Set navigation region */
+ /* We need to give a little padding, because sometimes the pt-in-region function
+ won't count one of the polygon vertices (which the mouse pointer will be on
+ immediately) as being IN the region. */
+ if (menu_item->submenu_direction == GTK_DIRECTION_RIGHT)
+ point[0].x = event->x_root - SUBMENU_NAV_REGION_PADDING;
+ else
+ point[0].x = event->x_root + SUBMENU_NAV_REGION_PADDING;
+
+ if (event->y < 0)
+ point[0].y = event->y_root + SUBMENU_NAV_REGION_PADDING;
+ else
+ point[0].y = event->y_root - SUBMENU_NAV_REGION_PADDING;
+
+ if (event->y < 0) /* Exiting the top */
+ point[1].y = submenu_top;
+ else /* Exiting the bottom */
+ point[1].y = submenu_bottom;
+
+ if (menu_item->submenu_direction == GTK_DIRECTION_RIGHT)
+ point[1].x = submenu_left; /* submenu is on the right */
+ else /* submenu is on the left */
+ point[1].x = submenu_right;
+
+ point[2].x = point[1].x;
+ point[2].y = point[0].y;
+
+ navigation_region = gdk_region_polygon (point, 3, GDK_WINDING_RULE);
+
+ gtk_timeout_add (SUBMENU_NAV_HYSTERESIS_TIMEOUT,
+ gtk_menu_stop_navigating_submenu_cb, menu_item);
+ }
}
static void
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]