[gtk+] menus: Implement scrolling through event capture for touch devices



commit 47f9435e995b1d5cb4d3c67841e4e809bafaf730
Author: Carlos Garnacho <carlosg gnome org>
Date:   Mon Dec 12 18:11:57 2011 +0100

    menus: Implement scrolling through event capture for touch devices
    
    This makes overflown menus scrollable via direct manipulation.
    Once past the threshold, the item below the pointer is unselected
    and scrolling starts.

 gtk/gtkmenu.c        |  204 +++++++++++++++++++++++++++++++++++++++-----------
 gtk/gtkmenuprivate.h |    5 +
 2 files changed, 166 insertions(+), 43 deletions(-)
---
diff --git a/gtk/gtkmenu.c b/gtk/gtkmenu.c
index faa6896..f1571d9 100644
--- a/gtk/gtkmenu.c
+++ b/gtk/gtkmenu.c
@@ -108,8 +108,10 @@
 #include "gtksettings.h"
 #include "gtkprivate.h"
 #include "gtkwidgetprivate.h"
+#include "gtkdnd.h"
 #include "gtkintl.h"
 #include "gtktypebuiltins.h"
+#include "gtkwidgetprivate.h"
 
 #include "deprecated/gtktearoffmenuitem.h"
 
@@ -225,6 +227,9 @@ static void     gtk_menu_scroll_to         (GtkMenu          *menu,
                                             gint              offset);
 static void     gtk_menu_grab_notify       (GtkWidget        *widget,
                                             gboolean          was_grabbed);
+static gboolean gtk_menu_captured_event    (GtkWidget        *widget,
+                                            GdkEvent         *event);
+
 
 static void     gtk_menu_stop_scrolling         (GtkMenu  *menu);
 static void     gtk_menu_remove_scroll_timeout  (GtkMenu  *menu);
@@ -1064,9 +1069,12 @@ gtk_menu_init (GtkMenu *menu)
   priv->needs_destruction_ref = TRUE;
 
   priv->monitor_num = -1;
+  priv->drag_start_y = -1;
 
   context = gtk_widget_get_style_context (GTK_WIDGET (menu));
   gtk_style_context_add_class (context, GTK_STYLE_CLASS_MENU);
+
+  _gtk_widget_set_captured_event_handler (GTK_WIDGET (menu), gtk_menu_captured_event);
 }
 
 static void
@@ -3323,34 +3331,6 @@ gtk_menu_get_preferred_height_for_width (GtkWidget *widget,
   g_free (nat_heights);
 }
 
-
-
-static gboolean
-gtk_menu_button_scroll (GtkMenu        *menu,
-                        GdkEventButton *event)
-{
-  GtkMenuPrivate *priv = menu->priv;
-
-  if (priv->upper_arrow_prelight || priv->lower_arrow_prelight)
-    {
-      gboolean touchscreen_mode;
-
-      g_object_get (gtk_widget_get_settings (GTK_WIDGET (menu)),
-                    "gtk-touchscreen-mode", &touchscreen_mode,
-                    NULL);
-
-      if (touchscreen_mode)
-        gtk_menu_handle_scrolling (menu,
-                                   event->x_root, event->y_root,
-                                   event->type == GDK_BUTTON_PRESS,
-                                   FALSE);
-
-      return TRUE;
-    }
-
-  return FALSE;
-}
-
 static gboolean
 pointer_in_menu_window (GtkWidget *widget,
                         gdouble    x_root,
@@ -3390,11 +3370,6 @@ gtk_menu_button_press (GtkWidget      *widget,
   if (event->type != GDK_BUTTON_PRESS)
     return FALSE;
 
-  /* Don't pass down to menu shell for presses over scroll arrows
-   */
-  if (gtk_menu_button_scroll (GTK_MENU (widget), event))
-    return TRUE;
-
   /*  Don't pass down to menu shell if a non-menuitem part of the menu
    *  was clicked. The check for the event_widget being a GtkMenuShell
    *  works because we have the pointer grabbed on menu_shell->window
@@ -3424,11 +3399,6 @@ gtk_menu_button_release (GtkWidget      *widget,
   if (event->type != GDK_BUTTON_RELEASE)
     return FALSE;
 
-  /* Don't pass down to menu shell for releases over scroll arrows
-   */
-  if (gtk_menu_button_scroll (GTK_MENU (widget), event))
-    return TRUE;
-
   /*  Don't pass down to menu shell if a non-menuitem part of the menu
    *  was clicked (see comment in button_press()).
    */
@@ -3672,10 +3642,14 @@ gtk_menu_motion_notify (GtkWidget      *widget,
   GtkMenu *menu;
   GtkMenuShell *menu_shell;
   GtkWidget *parent;
+  GdkDevice *source_device;
 
   gboolean need_enter;
 
-  if (GTK_IS_MENU (widget))
+  source_device = gdk_event_get_source_device ((GdkEvent *) event);
+
+  if (GTK_IS_MENU (widget) &&
+      gdk_device_get_source (source_device) != GDK_SOURCE_TOUCHSCREEN)
     {
       GtkMenuPrivate *priv = GTK_MENU(widget)->priv;
 
@@ -4293,10 +4267,11 @@ gtk_menu_enter_notify (GtkWidget        *widget,
       event->mode == GDK_CROSSING_STATE_CHANGED)
     return TRUE;
 
-  source_device = gdk_event_get_source_device (event);
+  source_device = gdk_event_get_source_device ((GdkEvent *) event);
   menu_item = gtk_get_event_widget ((GdkEvent*) event);
 
-  if (GTK_IS_MENU (widget))
+  if (GTK_IS_MENU (widget) &&
+      gdk_device_get_source (source_device) != GDK_SOURCE_TOUCHSCREEN)
     {
       GtkMenuShell *menu_shell = GTK_MENU_SHELL (widget);
 
@@ -4363,6 +4338,7 @@ gtk_menu_leave_notify (GtkWidget        *widget,
   GtkMenu *menu;
   GtkMenuItem *menu_item;
   GtkWidget *event_widget;
+  GdkDevice *source_device;
 
   if (event->mode == GDK_CROSSING_GTK_GRAB ||
       event->mode == GDK_CROSSING_GTK_UNGRAB ||
@@ -4375,7 +4351,10 @@ gtk_menu_leave_notify (GtkWidget        *widget,
   if (gtk_menu_navigating_submenu (menu, event->x_root, event->y_root))
     return TRUE;
 
-  gtk_menu_handle_scrolling (menu, event->x_root, event->y_root, FALSE, TRUE);
+  source_device = gdk_event_get_source_device ((GdkEvent *) event);
+
+  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);
 
@@ -4410,6 +4389,142 @@ gtk_menu_leave_notify (GtkWidget        *widget,
   return GTK_WIDGET_CLASS (gtk_menu_parent_class)->leave_notify_event (widget, event);
 }
 
+static gboolean
+pointer_on_menu_widget (GtkMenu *menu,
+                        gdouble  x_root,
+                        gdouble  y_root)
+{
+  GtkMenuPrivate *priv = menu->priv;
+  GtkAllocation allocation;
+  gint window_x, window_y;
+
+  gtk_widget_get_allocation (GTK_WIDGET (menu), &allocation);
+  gdk_window_get_position (gtk_widget_get_window (priv->toplevel),
+                           &window_x, &window_y);
+
+  if (x_root >= window_x && x_root < window_x + allocation.width &&
+      y_root >= window_y && y_root < window_y + allocation.height)
+    return TRUE;
+
+  return FALSE;
+}
+
+static gboolean
+gtk_menu_captured_event (GtkWidget *widget,
+                         GdkEvent  *event)
+{
+  GdkDevice *source_device;
+  gboolean retval = FALSE;
+  GtkMenuPrivate *priv;
+  GtkMenu *menu;
+  gdouble x_root, y_root;
+  guint button;
+  GdkModifierType state;
+
+  menu = GTK_MENU (widget);
+  priv = menu->priv;
+
+  if (!priv->upper_arrow_visible && !priv->lower_arrow_visible)
+    return retval;
+
+  source_device = gdk_event_get_source_device (event);
+  gdk_event_get_root_coords (event, &x_root, &y_root);
+
+  switch (event->type)
+    {
+    case GDK_TOUCH_BEGIN:
+    case GDK_BUTTON_PRESS:
+      if ((!gdk_event_get_button (event, &button) || button == 1) &&
+          gdk_device_get_source (source_device) == GDK_SOURCE_TOUCHSCREEN &&
+          pointer_on_menu_widget (menu, x_root, y_root))
+        {
+          priv->drag_start_y = event->button.y_root;
+          priv->initial_drag_offset = priv->scroll_offset;
+          priv->drag_scroll_started = FALSE;
+        }
+      else
+        priv->drag_start_y = -1;
+
+      priv->drag_already_pressed = TRUE;
+      break;
+    case GDK_TOUCH_END:
+    case GDK_BUTTON_RELEASE:
+      if (priv->drag_scroll_started)
+        {
+          priv->drag_scroll_started = FALSE;
+          priv->drag_start_y = -1;
+          priv->drag_already_pressed = FALSE;
+          retval = TRUE;
+        }
+      break;
+    case GDK_TOUCH_UPDATE:
+    case GDK_MOTION_NOTIFY:
+      if ((!gdk_event_get_state (event, &state) || (state & GDK_BUTTON1_MASK) 
+!= 0) &&
+          gdk_device_get_source (source_device) == GDK_SOURCE_TOUCHSCREEN)
+        {
+          if (!priv->drag_already_pressed)
+            {
+              if (pointer_on_menu_widget (menu, x_root, y_root))
+                {
+                  priv->drag_start_y = y_root;
+                  priv->initial_drag_offset = priv->scroll_offset;
+                  priv->drag_scroll_started = FALSE;
+                }
+              else
+                priv->drag_start_y = -1;
+
+              priv->drag_already_pressed = TRUE;
+            }
+
+          if (priv->drag_start_y < 0 && !priv->drag_scroll_started)
+            break;
+
+          if (priv->drag_scroll_started)
+            {
+              gint offset, view_height;
+              GtkBorder arrow_border;
+              gdouble y_diff;
+
+              y_diff = y_root - priv->drag_start_y;
+              offset = priv->initial_drag_offset - y_diff;
+
+              view_height = gdk_window_get_height (gtk_widget_get_window (widget));
+              get_arrows_border (menu, &arrow_border);
+
+              if (priv->upper_arrow_visible)
+                view_height -= arrow_border.top;
+
+              if (priv->lower_arrow_visible)
+                view_height -= arrow_border.bottom;
+
+              offset = CLAMP (offset, 0, priv->requested_height - view_height);
+              gtk_menu_scroll_to (menu, offset);
+
+              retval = TRUE;
+            }
+          else if (gtk_drag_check_threshold (widget,
+                                             0, priv->drag_start_y,
+                                             0, y_root))
+            {
+              priv->drag_scroll_started = TRUE;
+              gtk_menu_shell_deselect (GTK_MENU_SHELL (menu));
+              retval = TRUE;
+            }
+        }
+      break;
+    case GDK_ENTER_NOTIFY:
+    case GDK_LEAVE_NOTIFY:
+      if (priv->drag_scroll_started)
+        retval = TRUE;
+      break;
+    default:
+      break;
+    }
+
+  return retval;
+}
+
 static void
 gtk_menu_stop_navigating_submenu (GtkMenu *menu)
 {
@@ -5670,7 +5785,6 @@ gtk_menu_real_move_scroll (GtkMenu       *menu,
     }
 }
 
-
 /**
  * gtk_menu_set_monitor:
  * @menu: a #GtkMenu
@@ -5747,11 +5861,13 @@ static void
 gtk_menu_grab_notify (GtkWidget *widget,
                       gboolean   was_grabbed)
 {
+  GtkMenu *menu;
   GtkWidget *toplevel;
   GtkWindowGroup *group;
   GtkWidget *grab;
   GdkDevice *pointer;
 
+  menu = GTK_MENU (widget);
   pointer = _gtk_menu_shell_get_grab_device (GTK_MENU_SHELL (widget));
 
   if (!pointer ||
@@ -5768,6 +5884,8 @@ gtk_menu_grab_notify (GtkWidget *widget,
 
   if (GTK_MENU_SHELL (widget)->priv->active && !GTK_IS_MENU_SHELL (grab))
     gtk_menu_shell_cancel (GTK_MENU_SHELL (widget));
+
+  menu->priv->drag_scroll_started = FALSE;
 }
 
 /**
diff --git a/gtk/gtkmenuprivate.h b/gtk/gtkmenuprivate.h
index 37617c7..f458358 100644
--- a/gtk/gtkmenuprivate.h
+++ b/gtk/gtkmenuprivate.h
@@ -99,6 +99,8 @@ struct _GtkMenuPrivate
   guint seen_item_enter       : 1;
   guint ignore_button_release : 1;
   guint no_toggle_size        : 1;
+  guint drag_already_pressed  : 1;
+  guint drag_scroll_started   : 1;
 
   /* info used for the table */
   guint *heights;
@@ -125,6 +127,9 @@ struct _GtkMenuPrivate
   gint navigation_height;
 
   guint navigation_timeout;
+
+  gdouble drag_start_y;
+  gint initial_drag_offset;
 };
 
 G_END_DECLS



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]