Submenu hysteresis patch revised



Howdy everyone. Made a few more changes to this thing. The
functionality is the same, but it should be cleaner and leaner. The
changes from the last version of this patch are: 

* The gtk_menu_leave/enter_notify() functions now mostly use the
GtkMenuShell functions to do their work through GTK_WIDGET_CLASS
(parent_class)->enter/leave_notify_event (widget, event). 
* Some comments cleaned up. 

Also, the original intent of this patch (from Nils Barth's original
code) : 

* 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). 

Issues: 

* Add an API function to set the timeout? (Personally, I am against this
for now until we get some way to do this from gtkrc or whatever the global
configuration system ends up being (GTK+ todo list item: "Allow global
customization"). 
* Move the navigation_region variable from globalness to the GtkMenu
structure? I'm in favor of moving it, but if it makes the code simpler,
might be better to just leave it. 
* Fill In The Blank :) 

 - David :) 
Index: gtk/gtkmenu.c
===================================================================
RCS file: /cvs/gnome/gtk+/gtk/gtkmenu.c,v
retrieving revision 1.46
diff -u -r1.46 gtkmenu.c
--- gtk/gtkmenu.c	2000/06/20 21:04:38	1.46
+++ gtk/gtkmenu.c	2000/07/06 22:06:36
@@ -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,7 +87,7 @@
 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);
+  
+  menu = GTK_MENU (widget); 
+
+  if (gtk_menu_navigating_submenu (event->x_root, event->y_root))
+    return TRUE; 
+
+  menu_shell = GTK_MENU_SHELL (widget); 
   
-  if (GTK_MENU_SHELL (widget)->ignore_enter)
-    GTK_MENU_SHELL (widget)->ignore_enter = FALSE;
+  if (menu_shell->ignore_enter)
+    menu_shell->ignore_enter = FALSE; 
   else 
     {
       gint width, height;
@@ -1131,6 +1157,165 @@
     }
 
   return FALSE;
+}
+
+static gint
+gtk_menu_enter_notify (GtkWidget        *widget,
+		       GdkEventCrossing *event)
+{
+  if (gtk_menu_navigating_submenu (event->x_root, event->y_root))
+	return TRUE; 
+
+  return GTK_WIDGET_CLASS (parent_class)->enter_notify_event (widget, event); 
+}
+
+static gint
+gtk_menu_leave_notify (GtkWidget        *widget,
+		       GdkEventCrossing *event)
+{
+  GtkMenu *menu;
+  GtkMenuItem *menu_item;
+  GtkWidget *event_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_navigating_submenu (event->x_root, event->y_root))
+    return TRUE; 
+  
+  /* 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;
+	}
+    }
+  
+  return GTK_WIDGET_CLASS (parent_class)->leave_notify_event (widget, event); 
+}
+
+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; 
+      
+      /* Exiting the top or bottom? */ 
+      if (event->y < 0)
+	point[1].y = submenu_top;
+      else
+	point[1].y = submenu_bottom;
+      
+      /* Submenu is to the left or right? */ 
+      if (menu_item->submenu_direction == GTK_DIRECTION_RIGHT)
+	point[1].x = submenu_left; 
+      else
+	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]