[gtk+/multitouch: 10/123] Add GtkWidget::press-and-hold signal



commit 2c21dc976792c62eff13237bffb31735bda16d52
Author: Carlos Garcia Campos <cgarcia igalia com>
Date:   Mon Feb 14 12:44:17 2011 +0100

    Add GtkWidget::press-and-hold signal
    
    Press-and-hold signal is emitted when the mouse button is pressed for a
    given amount of time, specified in the new "press-and-hold-timeout"
    GtkSetting. It's commonly used in mobile platforms to emulate a right
    click to show a context menu. This patch is based on previous patches by
    Kristian Rietveld and Danielle Madeley.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=315645

 gtk/gtkmarshalers.list   |    1 +
 gtk/gtksettings.c        |   21 ++-
 gtk/gtkwidget.c          |  444 ++++++++++++++++++++++++++++++++++++++++++++++
 gtk/gtkwidget.h          |   12 ++-
 tests/Makefile.am        |    6 +
 tests/testpressandhold.c |  192 ++++++++++++++++++++
 6 files changed, 674 insertions(+), 2 deletions(-)
---
diff --git a/gtk/gtkmarshalers.list b/gtk/gtkmarshalers.list
index e104d31..9b8195b 100644
--- a/gtk/gtkmarshalers.list
+++ b/gtk/gtkmarshalers.list
@@ -27,6 +27,7 @@ BOOLEAN:ENUM
 BOOLEAN:ENUM,BOOLEAN
 BOOLEAN:ENUM,DOUBLE
 BOOLEAN:ENUM,INT
+BOOLEAN:ENUM,INT,INT
 BOOLEAN:OBJECT
 BOOLEAN:OBJECT,UINT,FLAGS
 BOOLEAN:OBJECT,INT,INT,UINT
diff --git a/gtk/gtksettings.c b/gtk/gtksettings.c
index 48579aa..85f2ed8 100644
--- a/gtk/gtksettings.c
+++ b/gtk/gtksettings.c
@@ -210,7 +210,8 @@ enum {
   PROP_IM_PREEDIT_STYLE,
   PROP_IM_STATUS_STYLE,
   PROP_SHELL_SHOWS_APP_MENU,
-  PROP_SHELL_SHOWS_MENUBAR
+  PROP_SHELL_SHOWS_MENUBAR,
+  PROP_PRESS_AND_HOLD_TIMEOUT
 };
 
 /* --- prototypes --- */
@@ -1353,6 +1354,24 @@ gtk_settings_class_init (GtkSettingsClass *class)
                                              NULL);
   g_assert (result == PROP_SHELL_SHOWS_MENUBAR);
 
+  /**
+   * GtkSettings:gtk-press-and-hold-timeout:
+   *
+   * The amount of time, in milliseconds, a button has to be pressed
+   * before the press-and-hold signal with the trigger action is emitted.
+   *
+   * Since: 3.2
+   */
+  result = settings_install_property_parser (class,
+                                             g_param_spec_int ("gtk-press-and-hold-timeout",
+                                                               P_("Press And Hold Timeout"),
+                                                               P_("Timeout before press-and-hold action activates"),
+                                                               0, G_MAXINT,
+                                                               DEFAULT_TIMEOUT_PRESS_AND_HOLD,
+                                                               GTK_PARAM_READWRITE),
+                                             NULL);
+  g_assert (result == PROP_PRESS_AND_HOLD_TIMEOUT);
+
   g_type_class_add_private (class, sizeof (GtkSettingsPrivate));
 }
 
diff --git a/gtk/gtkwidget.c b/gtk/gtkwidget.c
index 7171791..f72503c 100644
--- a/gtk/gtkwidget.c
+++ b/gtk/gtkwidget.c
@@ -485,6 +485,7 @@ enum {
   DRAG_FAILED,
   STYLE_UPDATED,
   CAPTURED_EVENT,
+  PRESS_AND_HOLD,
   LAST_SIGNAL
 };
 
@@ -540,6 +541,26 @@ struct _GtkStateData
   guint         operation : 2;
 };
 
+typedef struct
+{
+  /* timeout */
+  guint press_and_hold_id;
+
+  /* animation */
+  GtkWidget *popup;
+  guint animation_id;
+
+  /* signal handlers */
+  guint motion_id;
+  guint button_release_id;
+  guint drag_begin_id;
+
+  gint start_x;
+  gint start_y;
+  gint current_x;
+  gint current_y;
+} PressAndHoldData;
+
 /* --- prototypes --- */
 static void	gtk_widget_base_class_init	(gpointer            g_class);
 static void	gtk_widget_class_init		(GtkWidgetClass     *klass);
@@ -647,6 +668,12 @@ static gboolean         gtk_widget_real_can_activate_accel      (GtkWidget *widg
 static void             gtk_widget_real_set_has_tooltip         (GtkWidget *widget,
 								 gboolean   has_tooltip,
 								 gboolean   force);
+static gboolean         gtk_widget_press_and_hold_cancel        (GtkWidget         *widget,
+                                                                 gpointer           unused,
+                                                                 PressAndHoldData  *data);
+static gboolean         gtk_widget_press_and_hold_button_press_event (GtkWidget      *widget,
+                                                                      GdkEventButton *button,
+                                                                      gpointer        user_data);
 static void             gtk_widget_buildable_interface_init     (GtkBuildableIface *iface);
 static void             gtk_widget_buildable_set_name           (GtkBuildable     *buildable,
                                                                  const gchar      *name);
@@ -735,6 +762,7 @@ static GQuark		quark_visual = 0;
 static GQuark           quark_modifier_style = 0;
 static GQuark           quark_enabled_devices = 0;
 static GQuark           quark_size_groups = 0;
+static GQuark           quark_press_and_hold = 0;
 GParamSpecPool         *_gtk_widget_child_property_pool = NULL;
 GObjectNotifyContext   *_gtk_widget_child_property_notify_context = NULL;
 
@@ -859,6 +887,7 @@ gtk_widget_class_init (GtkWidgetClass *klass)
   quark_modifier_style = g_quark_from_static_string ("gtk-widget-modifier-style");
   quark_enabled_devices = g_quark_from_static_string ("gtk-widget-enabled-devices");
   quark_size_groups = g_quark_from_static_string ("gtk-widget-size-groups");
+  quark_press_and_hold = g_quark_from_static_string ("gtk-widget-press-and-hold");
 
   style_property_spec_pool = g_param_spec_pool_new (FALSE);
   _gtk_widget_child_property_pool = g_param_spec_pool_new (TRUE);
@@ -937,6 +966,7 @@ gtk_widget_class_init (GtkWidgetClass *klass)
   klass->grab_broken_event = NULL;
   klass->query_tooltip = gtk_widget_real_query_tooltip;
   klass->style_updated = gtk_widget_real_style_updated;
+  klass->press_and_hold = NULL;
 
   klass->show_help = gtk_widget_real_show_help;
 
@@ -3037,6 +3067,66 @@ gtk_widget_class_init (GtkWidgetClass *klass)
 		  _gtk_marshal_BOOLEAN__UINT,
                   G_TYPE_BOOLEAN, 1, G_TYPE_UINT);
 
+  /**
+   * GtkWidget::press-and-hold:
+   * @widget: the object which received the signal
+   * @action: a #GtkPressAndHoldAction specifying the action
+   * @x: if the action is not %GTK_PRESS_AND_HOLD_CANCEL, the x coordinate
+   *     of the cursor position where the request has been emitted, relative
+   *     to widget->window, otherwise undefined
+   * @y: if the action is not %GTK_PRESS_AND_HOLD_CANCEL, the y coordinate
+   *     of the cursor position where the request has been emitted, relative
+   *     to widget->window, otherwise undefined
+   *
+   * Connect to this signal and correctly handle all of its actions if you
+   * want your widget to support the press-n-hold operation.  The
+   * press-and-hold operation is defined as keeping a mouse button pressed
+   * for a given amount of time (specified in the "press-and-hold-timeout"
+   * GtkSetting); during this time the mouse is only allowed to move a little
+   * bit (not past the drag threshold), else the press-and-hold operation will
+   * be terminated.
+   *
+   * From the above passage we can distill three actions for which this
+   * signal will be emitted: query, emitted when the mouse button goes
+   * down; trigger, emitted if the mouse button has been kept down for the
+   * specified amount of time and movements did not pass the drag threshold;
+   * and cancel, emitted when the press-and-hold operation has been terminated
+   * before the trigger action has been emitted.
+   *
+   * For query, @action will be set to %GTK_PRESS_AND_HOLD_QUERY, @x and @y
+   * will be set to the cursor position.
+   * A return value of %FALSE means no press-and-hold action should occur
+   * for these coordinates on the given widget, when %TRUE is returned
+   * a trigger action may be emitted later on.
+   *
+   * The trigger action is emitted by setting @action to be
+   * %GTK_PRESS_AND_HOLD_TRIGGER, the @x and @y coordinates are set to the
+   * cursor's current location (this includes any movements made between
+   * the original query and this trigger) and @keyboard_mode is set to
+   * %TRUE if the trigger was initiated by a keyboard action, %FALSE
+   * otherwise.  In this case the return value is ignored.
+   *
+   * When @action is %GTK_WIDGET_PRESS_AND_HOLD_CANCEL, @x and @y are both
+   * undefined.  The return value is ignored too as
+   * this action is only there for informational purposes.
+   *
+   * Returns: a boolean indicating how to proceed based on the value of
+   *          @action, as described above.
+   *
+   * Since: 3.2
+   */
+  widget_signals[PRESS_AND_HOLD] =
+    g_signal_new (I_("press-and-hold"),
+		  G_TYPE_FROM_CLASS (gobject_class),
+		  G_SIGNAL_RUN_LAST,
+		  G_STRUCT_OFFSET (GtkWidgetClass, press_and_hold),
+		  _gtk_boolean_handled_accumulator, NULL,
+		  _gtk_marshal_BOOLEAN__ENUM_INT_INT,
+		  G_TYPE_BOOLEAN, 3,
+		  GTK_TYPE_PRESS_AND_HOLD_ACTION,
+		  G_TYPE_INT,
+		  G_TYPE_INT);
+
   binding_set = gtk_binding_set_by_class (klass);
   gtk_binding_entry_add_signal (binding_set, GDK_KEY_F10, GDK_SHIFT_MASK,
                                 "popup-menu", 0);
@@ -4461,6 +4551,12 @@ gtk_widget_realize (GtkWidget *widget)
       _gtk_widget_enable_device_events (widget);
       gtk_widget_update_devices_mask (widget, TRUE);
 
+      /* Enable button motion events for press and hold */
+      if (!gtk_widget_get_has_window (widget))
+        gdk_window_set_events (priv->window,
+                               gdk_window_get_events (priv->window) | GDK_BUTTON_MOTION_MASK);
+      gtk_widget_add_events (widget, GDK_BUTTON_MOTION_MASK);
+
       gtk_widget_pop_verify_invariants (widget);
     }
 }
@@ -5924,6 +6020,10 @@ _gtk_widget_captured_event (GtkWidget *widget,
 
   g_object_ref (widget);
 
+  /* Make sure we always handle press and hold */
+  if (event->type == GDK_BUTTON_PRESS)
+    gtk_widget_press_and_hold_button_press_event (widget, (GdkEventButton *)event, NULL);
+
   g_signal_emit (widget, widget_signals[CAPTURED_EVENT], 0, event, &return_val);
   return_val |= !WIDGET_REALIZED_FOR_EVENT (widget, event);
 
@@ -6783,6 +6883,350 @@ gtk_widget_has_focus (GtkWidget *widget)
   return widget->priv->has_focus;
 }
 
+/* --- Press and hold --- */
+
+static inline PressAndHoldData *
+gtk_widget_peek_press_and_hold_data (GtkWidget *widget)
+{
+  return g_object_get_qdata (G_OBJECT (widget), quark_press_and_hold);
+}
+
+static void
+press_and_hold_data_free (PressAndHoldData *data)
+{
+  if (data->popup)
+    gtk_widget_destroy (data->popup);
+
+  if (data->press_and_hold_id)
+    g_source_remove (data->press_and_hold_id);
+
+  if (data->animation_id)
+    g_source_remove (data->animation_id);
+
+  g_slice_free (PressAndHoldData, data);
+}
+
+static inline void
+gtk_widget_set_press_and_hold_data (GtkWidget        *widget,
+				    PressAndHoldData *data)
+{
+  g_object_set_qdata_full (G_OBJECT (widget),
+                           quark_press_and_hold,
+                           data,
+                           (GDestroyNotify) press_and_hold_data_free);
+}
+
+static inline PressAndHoldData *
+gtk_widget_get_press_and_hold_data (GtkWidget *widget)
+{
+  PressAndHoldData *data;
+
+  data = gtk_widget_peek_press_and_hold_data (widget);
+  if (!data)
+    {
+      data = g_slice_new0 (PressAndHoldData);
+      gtk_widget_set_press_and_hold_data (widget, data);
+    }
+
+  return data;
+}
+
+static inline void
+gtk_widget_press_and_hold_finish (GtkWidget        *widget,
+                                  PressAndHoldData *data)
+{
+  if (data->popup)
+    gtk_widget_destroy (data->popup);
+  data->popup = NULL;
+
+  if (data->motion_id)
+    g_signal_handler_disconnect (widget, data->motion_id);
+  data->motion_id = 0;
+
+  if (data->button_release_id)
+    g_signal_handler_disconnect (widget, data->button_release_id);
+  data->button_release_id = 0;
+
+  if (data->drag_begin_id)
+    g_signal_handler_disconnect (widget, data->drag_begin_id);
+  data->drag_begin_id = 0;
+
+  if (data->press_and_hold_id)
+    g_source_remove (data->press_and_hold_id);
+  data->press_and_hold_id = 0;
+
+  if (data->animation_id)
+    g_source_remove (data->animation_id);
+  data->animation_id = 0;
+}
+
+static gboolean
+gtk_widget_press_and_hold_button_release (GtkWidget        *widget,
+                                          GdkEvent         *_event,
+                                          PressAndHoldData *data)
+{
+  if (_event->type != GDK_BUTTON_RELEASE)
+    return FALSE;
+
+  gtk_widget_press_and_hold_cancel (widget, NULL, data);
+
+  return FALSE;
+}
+
+static gboolean
+gtk_widget_press_and_hold_motion_notify (GtkWidget        *widget,
+                                         GdkEvent         *_event,
+                                         PressAndHoldData *data)
+{
+  GdkEventMotion *event;
+
+  if (_event->type != GDK_MOTION_NOTIFY)
+    return FALSE;
+
+  event = (GdkEventMotion *)_event;
+
+  if (!data->press_and_hold_id)
+    {
+      gtk_widget_press_and_hold_finish (widget, data);
+
+      return FALSE;
+    }
+
+  _gtk_widget_find_at_coords (event->window, event->x, event->y,
+                              &data->current_x, &data->current_y);
+
+  /* Stop press-and-hold if we dragged too far from the starting point */
+  if (gtk_drag_check_threshold (widget, data->start_x, data->start_y,
+                                data->current_x, data->current_y))
+    {
+      gtk_widget_press_and_hold_cancel (widget, NULL, data);
+
+      return FALSE;
+    }
+
+  if (data->popup)
+    {
+      gint x, y;
+      guint cursor_size;
+
+      gdk_window_get_pointer (gdk_screen_get_root_window (gtk_widget_get_screen (widget)),
+                              &x, &y, NULL);
+      cursor_size = gdk_display_get_default_cursor_size (gtk_widget_get_display (widget));
+      gtk_window_move (GTK_WINDOW (data->popup),
+                       x - cursor_size / 2,
+                       y - cursor_size / 2);
+    }
+
+  return FALSE;
+}
+
+static gboolean
+gtk_widget_press_and_hold_timeout (gpointer user_data)
+{
+  gboolean return_value;
+  GtkWidget *widget = GTK_WIDGET (user_data);
+  PressAndHoldData *data = gtk_widget_peek_press_and_hold_data (widget);
+
+  /* Done, clean up and emit the trigger signal */
+  gtk_widget_press_and_hold_finish (widget, data);
+  _gtk_widget_grab_notify (widget, FALSE);
+
+  g_signal_emit (widget, widget_signals[PRESS_AND_HOLD],
+                 0,
+                 GTK_PRESS_AND_HOLD_TRIGGER,
+                 data->current_x, data->current_y,
+                 &return_value);
+
+  return FALSE;
+}
+
+static gboolean
+press_and_hold_animation_draw (GtkWidget        *widget,
+                               cairo_t          *cr,
+                               PressAndHoldData *data)
+{
+  GtkStyleContext *context;
+  GtkStateFlags state;
+  gint width, height;
+
+  width = gtk_widget_get_allocated_width (widget);
+  height = gtk_widget_get_allocated_height (widget);
+
+  context = gtk_widget_get_style_context (widget);
+  state = gtk_widget_get_state_flags (widget);
+  gtk_style_context_set_state (context, state);
+
+  if (!gtk_widget_is_composited (widget))
+    {
+      cairo_t *mask_cr;
+      cairo_region_t *region;
+      cairo_surface_t *mask;
+
+      mask = cairo_image_surface_create (CAIRO_FORMAT_A1, width, height);
+
+      mask_cr = cairo_create (mask);
+      gtk_render_activity (context, mask_cr, 0, 0, width, height);
+      cairo_destroy (mask_cr);
+
+      region = gdk_cairo_region_create_from_surface (mask);
+      gdk_window_shape_combine_region (gtk_widget_get_window (widget), region, 0, 0);
+      cairo_region_destroy (region);
+
+      cairo_surface_destroy (mask);
+    }
+
+  cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR);
+  cairo_paint (cr);
+
+  gtk_render_activity (context, cr, 0, 0, width, height);
+
+  return FALSE;
+}
+
+static gboolean
+gtk_widget_press_and_hold_begin_animation_timeout (gpointer user_data)
+{
+  GtkWidget *widget = GTK_WIDGET (user_data);
+  PressAndHoldData *data = gtk_widget_peek_press_and_hold_data (widget);
+  gint x, y;
+  guint cursor_size;
+
+  if (data->popup)
+    {
+      gtk_widget_set_state_flags (GTK_WIDGET (data->popup),
+                                  GTK_STATE_FLAG_ACTIVE, FALSE);
+      gdk_window_get_pointer (gdk_screen_get_root_window (gtk_widget_get_screen (widget)),
+                              &x, &y, NULL);
+      cursor_size = gdk_display_get_default_cursor_size (gtk_widget_get_display (widget));
+      gtk_window_move (GTK_WINDOW (data->popup),
+                       x - cursor_size / 2,
+                       y - cursor_size / 2);
+      gtk_widget_show (data->popup);
+    }
+
+  return FALSE;
+}
+
+static gboolean
+gtk_widget_press_and_hold_query (GtkWidget *widget,
+                                 gint       x,
+                                 gint       y)
+{
+  gboolean return_value = FALSE;
+
+  g_signal_emit (widget, widget_signals[PRESS_AND_HOLD],
+                 0,
+                 GTK_PRESS_AND_HOLD_QUERY,
+                 x, y,
+                 &return_value);
+
+  return return_value;
+}
+
+static gboolean
+gtk_widget_press_and_hold_cancel (GtkWidget        *widget,
+                                  gpointer          unused,
+                                  PressAndHoldData *data)
+{
+  gboolean return_value;
+
+  if (!data->press_and_hold_id)
+    return FALSE;
+
+  gtk_widget_press_and_hold_finish (widget, data);
+
+  g_signal_emit (widget, widget_signals[PRESS_AND_HOLD],
+                 0,
+                 GTK_PRESS_AND_HOLD_CANCEL,
+                 -1, -1,
+                 &return_value);
+
+  return FALSE;
+}
+
+static gboolean
+gtk_widget_press_and_hold_button_press_event (GtkWidget      *widget,
+                                              GdkEventButton *event,
+                                              gpointer        user_data)
+{
+  PressAndHoldData *data = gtk_widget_get_press_and_hold_data (widget);
+
+  if (!data)
+    return FALSE;
+
+  if (gtk_widget_press_and_hold_query (widget, event->x, event->y)
+      && !data->press_and_hold_id)
+    {
+      gint timeout, begin_ani_timeout;
+      GdkScreen *screen;
+      GdkVisual *visual;
+      GtkStyleContext *context;
+      cairo_region_t *region;
+      guint cursor_size;
+
+      _gtk_widget_find_at_coords (event->window, event->x, event->y,
+                                  &data->start_x, &data->start_y);
+
+      data->current_x = data->start_x;
+      data->current_y = data->start_y;
+
+      g_object_get (gtk_widget_get_settings (GTK_WIDGET (widget)),
+                    "gtk-press-and-hold-timeout", &timeout,
+                    "gtk-timeout-initial", &begin_ani_timeout,
+                    NULL);
+
+      screen = gtk_widget_get_screen (widget);
+      visual = gdk_screen_get_rgba_visual (screen);
+
+      data->popup = gtk_window_new (GTK_WINDOW_POPUP);
+      gtk_window_set_screen (GTK_WINDOW (data->popup), screen);
+      if (visual)
+        gtk_widget_set_visual (data->popup, visual);
+      gtk_widget_set_app_paintable (data->popup, TRUE);
+      gtk_widget_realize (data->popup);
+
+      context = gtk_widget_get_style_context (data->popup);
+      gtk_style_context_add_class (context, GTK_STYLE_CLASS_PRESS_AND_HOLD);
+
+      g_signal_connect (data->popup, "draw",
+                        G_CALLBACK (press_and_hold_animation_draw),
+                        data);
+
+      cursor_size = gdk_display_get_default_cursor_size (gtk_widget_get_display (widget));
+      gtk_window_resize (GTK_WINDOW (data->popup), cursor_size, cursor_size);
+
+      region = cairo_region_create ();
+      gdk_window_input_shape_combine_region (gtk_widget_get_window (data->popup), region, 0, 0);
+      cairo_region_destroy (region);
+
+      /* delay loading the animation by the double click timeout */
+      data->animation_id =
+        gdk_threads_add_timeout (begin_ani_timeout,
+                                 gtk_widget_press_and_hold_begin_animation_timeout,
+                                 widget);
+
+      data->motion_id =
+              g_signal_connect (widget, "captured-event",
+                                G_CALLBACK (gtk_widget_press_and_hold_motion_notify),
+                                data);
+      data->button_release_id =
+              g_signal_connect (widget, "captured-event",
+                                G_CALLBACK (gtk_widget_press_and_hold_button_release),
+                                data);
+      data->drag_begin_id =
+              g_signal_connect (widget, "drag-begin",
+                                G_CALLBACK (gtk_widget_press_and_hold_cancel),
+                                data);
+
+      data->press_and_hold_id =
+              gdk_threads_add_timeout (timeout,
+                                       gtk_widget_press_and_hold_timeout,
+                                       widget);
+    }
+
+  return FALSE;
+}
+
 /**
  * gtk_widget_has_visible_focus:
  * @widget: a #GtkWidget
diff --git a/gtk/gtkwidget.h b/gtk/gtkwidget.h
index 1f7b15a..a214b38 100644
--- a/gtk/gtkwidget.h
+++ b/gtk/gtkwidget.h
@@ -49,6 +49,13 @@ typedef enum
   GTK_WIDGET_HELP_WHATS_THIS
 } GtkWidgetHelpType;
 
+typedef enum
+{
+  GTK_PRESS_AND_HOLD_QUERY,
+  GTK_PRESS_AND_HOLD_TRIGGER,
+  GTK_PRESS_AND_HOLD_CANCEL
+} GtkPressAndHoldAction;
+
 /* Macro for casting a pointer to a GtkWidget or GtkWidgetClass pointer.
  * Macros for testing whether `widget' or `klass' are of type GTK_TYPE_WIDGET.
  */
@@ -427,6 +434,10 @@ struct _GtkWidgetClass
 
   void         (* captured_event)         (GtkWidget *widget,
                                            GdkEvent  *event);
+  gboolean     (* press_and_hold)         (GtkWidget             *widget,
+                                           GtkPressAndHoldAction  action,
+                                           gint                   x,
+                                           gint                   y);
   /*< private >*/
 
   GtkWidgetClassPrivate *priv;
@@ -437,7 +448,6 @@ struct _GtkWidgetClass
   void (*_gtk_reserved4) (void);
   void (*_gtk_reserved5) (void);
   void (*_gtk_reserved6) (void);
-  void (*_gtk_reserved7) (void);
 };
 
 struct _GtkWidgetAuxInfo
diff --git a/tests/Makefile.am b/tests/Makefile.am
index b9fb7f2..b6bdcac 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -77,6 +77,7 @@ noinst_PROGRAMS =  $(TEST_PROGS)	\
 	testorientable			\
 	testoverlay			\
 	testprint			\
+	testpressandhold		\
 	testrecentchooser 		\
 	testrecentchoosermenu		\
 	testrichtext			\
@@ -197,6 +198,7 @@ testappchooserbutton_DEPENDENCIES = $(TEST_DEPS)
 testorientable_DEPENDENCIES = $(TEST_DEPS)
 testoverlay_DEPENDENCIES = $(TEST_DEPS)
 testprint_DEPENDENCIES = $(TEST_DEPS)
+testpressandhold_DEPENDENCIES = $(TEST_DEPS)
 testrecentchooser_DEPENDENCIES = $(TEST_DEPS)
 testrecentchoosermenu_DEPENDENCIES = $(TEST_DEPS)
 testrichtext_DEPENDENCIES = $(TEST_DEPS)
@@ -297,6 +299,7 @@ testappchooserbutton_LDADD = $(LDADDS)
 testorientable_LDADD = $(LDADDS)
 testoverlay_LDADD = $(LDADDS)
 testprint_LDADD = $(LDADDS)
+testpressandhold_LDADD = $(LDADDS)
 testrecentchooser_LDADD = $(LDADDS)
 testrecentchoosermenu_LDADD = $(LDADDS)
 testrichtext_LDADD = $(LDADDS)
@@ -411,6 +414,9 @@ testprint_SOURCES =    	\
 	testprintfileoperation.h \
 	testprintfileoperation.c
 
+testpressandhold_SOURCES =	\
+	testpressandhold.c
+
 testsocket_SOURCES =    	\
 	testsocket.c		\
 	testsocket_common.c
diff --git a/tests/testpressandhold.c b/tests/testpressandhold.c
new file mode 100644
index 0000000..0f0f287
--- /dev/null
+++ b/tests/testpressandhold.c
@@ -0,0 +1,192 @@
+/* testpressandhold.c: Test application for GTK+ >= 3.2 press-n-hold code
+ *
+ * Copyright (C) 2007,2008  Imendio AB
+ * Contact: Kristian Rietveld <kris imendio com>
+ *
+ * This work is provided "as is"; redistribution and modification
+ * in whole or in part, in any medium, physical or electronic is
+ * permitted without restriction.
+ *
+ * This work is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ * In no event shall the authors or contributors be liable for any
+ * direct, indirect, incidental, special, exemplary, or consequential
+ * damages (including, but not limited to, procurement of substitute
+ * goods or services; loss of use, data, or profits; or business
+ * interruption) however caused and on any theory of liability, whether
+ * in contract, strict liability, or tort (including negligence or
+ * otherwise) arising in any way out of the use of this software, even
+ * if advised of the possibility of such damage.
+ */
+
+#include <gtk/gtk.h>
+
+struct CoordData
+{
+  gint x;
+  gint y;
+  GtkWidget *widget;
+};
+
+static void
+popup_position_func (GtkMenu   *menu,
+                     gint      *x,
+                     gint      *y,
+                     gboolean  *push_in,
+                     gpointer   user_data)
+{
+  GtkRequisition req;
+  GdkScreen *screen;
+  struct CoordData *data = user_data;
+
+  screen = gtk_widget_get_screen (data->widget);
+  gtk_widget_get_preferred_size (GTK_WIDGET (menu), &req, NULL);
+
+  *x = data->x;
+  *y = data->y;
+
+  *x = CLAMP (*x, 0, MAX (0, gdk_screen_get_width (screen) - req.width));
+  *y = CLAMP (*y, 0, MAX (0, gdk_screen_get_height (screen) - req.height));
+}
+
+static void
+press_and_hold_show_menu (GtkWidget *widget,
+			  gint       x,
+			  gint       y)
+{
+  GtkWidget *menu;
+  GtkWidget *item;
+  struct CoordData data;
+
+  menu = gtk_menu_new ();
+
+  item = gtk_menu_item_new_with_label ("Test 1");
+  gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+  gtk_widget_show (item);
+
+  item = gtk_menu_item_new_with_label ("Test 2");
+  gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+  gtk_widget_show (item);
+
+  item = gtk_menu_item_new_with_label ("Test 3");
+  gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+  gtk_widget_show (item);
+
+  data.widget = widget;
+  gdk_window_get_origin (gtk_widget_get_window (widget), &data.x, &data.y);
+  data.x += x;
+  data.y += y;
+
+  gtk_menu_popup (GTK_MENU (menu), NULL, NULL,
+		  popup_position_func,
+		  &data,
+		  1,
+		  GDK_CURRENT_TIME);
+}
+
+static gboolean
+press_and_hold (GtkWidget             *widget,
+	        GtkPressAndHoldAction  action,
+	        gint                   x,
+	        gint                   y,
+	        gboolean               keyboard)
+{
+  switch (action)
+    {
+      case GTK_PRESS_AND_HOLD_QUERY:
+	g_print ("press-and-hold-query on %s\n", gtk_widget_get_name (widget));
+        return TRUE;
+
+      case GTK_PRESS_AND_HOLD_TRIGGER:
+	g_print ("press-and-hold-trigger on %s\n", gtk_widget_get_name (widget));
+        press_and_hold_show_menu (widget, x, y);
+        break;
+
+      case GTK_PRESS_AND_HOLD_CANCEL:
+	g_print ("press-and-hold-cancel on %s\n", gtk_widget_get_name (widget));
+        break;
+    }
+
+  return FALSE;
+}
+
+static GtkTreeModel *
+create_model (void)
+{
+  GtkTreeStore *store;
+  GtkTreeIter iter;
+
+  store = gtk_tree_store_new (1, G_TYPE_STRING);
+
+  /* A tree store with some random words ... */
+  gtk_tree_store_insert_with_values (store, &iter, NULL, 0,
+				     0, "File Manager", -1);
+  gtk_tree_store_insert_with_values (store, &iter, NULL, 0,
+				     0, "Gossip", -1);
+  gtk_tree_store_insert_with_values (store, &iter, NULL, 0,
+				     0, "System Settings", -1);
+  gtk_tree_store_insert_with_values (store, &iter, NULL, 0,
+				     0, "The GIMP", -1);
+  gtk_tree_store_insert_with_values (store, &iter, NULL, 0,
+				     0, "Terminal", -1);
+  gtk_tree_store_insert_with_values (store, &iter, NULL, 0,
+				     0, "Word Processor", -1);
+
+  return GTK_TREE_MODEL (store);
+}
+
+int
+main (int argc, char **argv)
+{
+  GtkWidget *window;
+  GtkWidget *box;
+  GtkWidget *label, *checkbutton, *tree_view;
+
+  gtk_init (&argc, &argv);
+
+  window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+  gtk_window_set_title (GTK_WINDOW (window), "Press and Hold test");
+  gtk_container_set_border_width (GTK_CONTAINER (window), 10);
+  g_signal_connect (window, "delete_event",
+		    G_CALLBACK (gtk_main_quit), NULL);
+
+  box = gtk_vbox_new (FALSE, 3);
+  gtk_container_add (GTK_CONTAINER (window), box);
+
+
+  label = gtk_button_new_with_label ("Press-n-hold me!");
+  g_signal_connect (label, "press-and-hold",
+		    G_CALLBACK (press_and_hold), NULL);
+  gtk_box_pack_start (GTK_BOX (box), label, FALSE, FALSE, 0);
+
+  label = gtk_button_new_with_label ("No press and hold");
+  gtk_box_pack_start (GTK_BOX (box), label, FALSE, FALSE, 0);
+
+  checkbutton = gtk_check_button_new_with_label ("Checkable check button");
+  g_signal_connect (checkbutton, "press-and-hold",
+		    G_CALLBACK (press_and_hold), NULL);
+  gtk_box_pack_start (GTK_BOX (box), checkbutton, FALSE, FALSE, 0);
+
+
+  tree_view = gtk_tree_view_new_with_model (create_model ());
+  gtk_widget_set_size_request (tree_view, 200, 240);
+
+  gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (tree_view),
+					       0, "Test",
+					       gtk_cell_renderer_text_new (),
+					       "text", 0,
+					       NULL);
+
+  g_signal_connect (tree_view, "press-and-hold",
+		    G_CALLBACK (press_and_hold), NULL);
+
+  gtk_box_pack_start (GTK_BOX (box), tree_view, FALSE, FALSE, 0);
+
+  gtk_widget_show_all (window);
+
+  gtk_main ();
+
+  return 0;
+}



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