[gtk+/multitouch: 58/121] gtk, menu: Implement scrolling through ::captured-event for touch devices



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

    gtk,menu: Implement scrolling through ::captured-event 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        |  199 +++++++++++++++++++++++++++++++++++++++-----------
 gtk/gtkmenuprivate.h |    5 +
 2 files changed, 161 insertions(+), 43 deletions(-)
---
diff --git a/gtk/gtkmenu.c b/gtk/gtkmenu.c
index 96f42a6..995eda3 100644
--- a/gtk/gtkmenu.c
+++ b/gtk/gtkmenu.c
@@ -110,6 +110,7 @@
 #include "gtksettings.h"
 #include "gtkprivate.h"
 #include "gtkwidgetprivate.h"
+#include "gtkdnd.h"
 #include "gtkintl.h"
 #include "gtktypebuiltins.h"
 
@@ -227,6 +228,10 @@ static void     gtk_menu_scroll_to         (GtkMenu          *menu,
                                             gint              offset);
 static void     gtk_menu_grab_notify       (GtkWidget        *widget,
                                             gboolean          was_grabbed);
+static GtkCapturedEventFlags
+                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);
@@ -510,6 +515,7 @@ gtk_menu_class_init (GtkMenuClass *class)
   widget_class->get_preferred_width = gtk_menu_get_preferred_width;
   widget_class->get_preferred_height = gtk_menu_get_preferred_height;
   widget_class->get_preferred_height_for_width = gtk_menu_get_preferred_height_for_width;
+  widget_class->captured_event = gtk_menu_captured_event;
 
   container_class->remove = gtk_menu_remove;
   container_class->get_child_property = gtk_menu_get_child_property;
@@ -1057,6 +1063,7 @@ 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);
@@ -3313,34 +3320,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,
@@ -3380,11 +3359,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
@@ -3414,11 +3388,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()).
    */
@@ -3662,10 +3631,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_TOUCH)
     {
       GtkMenuPrivate *priv = GTK_MENU(widget)->priv;
 
@@ -4283,10 +4256,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_TOUCH)
     {
       GtkMenuShell *menu_shell = GTK_MENU_SHELL (widget);
 
@@ -4353,6 +4327,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 ||
@@ -4365,7 +4340,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_TOUCH)
+    gtk_menu_handle_scrolling (menu, event->x_root, event->y_root, FALSE, TRUE);
 
   event_widget = gtk_get_event_widget ((GdkEvent*) event);
 
@@ -4400,6 +4378,138 @@ 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 GtkCapturedEventFlags
+gtk_menu_captured_event (GtkWidget *widget,
+                         GdkEvent  *event)
+{
+  GdkDevice *source_device;
+  GtkCapturedEventFlags flags;
+  GtkMenuPrivate *priv;
+  GtkMenu *menu;
+
+  menu = GTK_MENU (widget);
+  priv = menu->priv;
+  flags = GTK_CAPTURED_EVENT_NONE;
+
+  if (!priv->upper_arrow_visible && !priv->lower_arrow_visible)
+    return flags;
+
+  source_device = gdk_event_get_source_device (event);
+
+  switch (event->type)
+    {
+    case GDK_BUTTON_PRESS:
+      if (event->button.button == 1 &&
+          gdk_device_get_source (source_device) == GDK_SOURCE_TOUCH &&
+          pointer_on_menu_widget (menu, event->button.x_root, event->button.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_BUTTON_RELEASE:
+      if (priv->drag_scroll_started)
+        {
+          flags = GTK_CAPTURED_EVENT_HANDLED;
+          priv->drag_scroll_started = FALSE;
+          priv->drag_start_y = -1;
+          priv->drag_already_pressed = FALSE;
+        }
+      break;
+    case GDK_MOTION_NOTIFY:
+      if (event->motion.state & GDK_BUTTON1_MASK &&
+          gdk_device_get_source (source_device) == GDK_SOURCE_TOUCH)
+        {
+          if (!priv->drag_already_pressed)
+            {
+              if (pointer_on_menu_widget (menu,
+                                          event->motion.x_root,
+                                          event->motion.y_root))
+                {
+                  priv->drag_start_y = event->motion.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 = event->motion.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);
+
+              flags = GTK_CAPTURED_EVENT_HANDLED;
+            }
+          else if (gtk_drag_check_threshold (widget,
+                                             0, priv->drag_start_y,
+                                             0, event->motion.y_root))
+            {
+              priv->drag_scroll_started = TRUE;
+              flags = GTK_CAPTURED_EVENT_HANDLED;
+              gtk_menu_shell_deselect (GTK_MENU_SHELL (menu));
+            }
+        }
+      break;
+    case GDK_ENTER_NOTIFY:
+    case GDK_LEAVE_NOTIFY:
+      if (priv->drag_scroll_started)
+        flags = GTK_CAPTURED_EVENT_HANDLED;
+      break;
+    default:
+      break;
+    }
+
+  return flags;
+}
+
 static void
 gtk_menu_stop_navigating_submenu (GtkMenu *menu)
 {
@@ -5660,7 +5770,6 @@ gtk_menu_real_move_scroll (GtkMenu       *menu,
     }
 }
 
-
 /**
  * gtk_menu_set_monitor:
  * @menu: a #GtkMenu
@@ -5737,11 +5846,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 ||
@@ -5758,6 +5869,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 cbac9fd..9c88dfb 100644
--- a/gtk/gtkmenuprivate.h
+++ b/gtk/gtkmenuprivate.h
@@ -100,6 +100,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;
@@ -126,6 +128,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]