Index: gtk/gtkwidget.h
===================================================================
--- gtk/gtkwidget.h	(revision 17895)
+++ gtk/gtkwidget.h	(working copy)
@@ -84,6 +84,13 @@ typedef enum
   GTK_WIDGET_HELP_WHATS_THIS
 } GtkWidgetHelpType;
 
+typedef enum
+{
+  GTK_TAP_AND_HOLD_QUERY,
+  GTK_TAP_AND_HOLD_TRIGGER,
+  GTK_TAP_AND_HOLD_CANCEL
+} GtkTapAndHoldAction;
+
 /* Macro for casting a pointer to a GtkWidget or GtkWidgetClass pointer.
  * Macros for testing whether `widget' or `klass' are of type GTK_TYPE_WIDGET.
  */
@@ -417,8 +424,13 @@ struct _GtkWidgetClass
 				       gboolean    keyboard_tooltip,
 				       GtkTooltip *tooltip);
 
+  gboolean     (* tap_and_hold) (GtkWidget           *widget,
+				 GtkTapAndHoldAction  action,
+				 gint                 x,
+				 gint                 y,
+				 gboolean             keyboard_triggered);
+
   /* Padding for future expansion */
-  void (*_gtk_reserved5) (void);
   void (*_gtk_reserved6) (void);
   void (*_gtk_reserved7) (void);
 };
Index: gtk/gtkwidget.c
===================================================================
--- gtk/gtkwidget.c	(revision 17896)
+++ gtk/gtkwidget.c	(working copy)
@@ -26,6 +26,7 @@
 
 #include <config.h>
 #include <stdarg.h>
+#include <stdlib.h>
 #include <string.h>
 #include <locale.h>
 #include "gtkcontainer.h"
@@ -53,6 +54,8 @@
 #include "gtktooltip.h"
 #include "gtkinvisible.h"
 #include "gtkalias.h"
+#include "gtkdnd.h"
+#include "gtkicontheme.h"
 
 #define WIDGET_CLASS(w)	 GTK_WIDGET_GET_CLASS (w)
 #define	INIT_PATH_SIZE	(512)
@@ -125,6 +128,7 @@ enum {
   QUERY_TOOLTIP,
   KEYNAV_FAILED,
   DRAG_FAILED,
+  TAP_AND_HOLD,
   LAST_SIGNAL
 };
 
@@ -162,6 +166,26 @@ struct _GtkStateData
   guint		use_forall : 1;
 };
 
+typedef struct
+{
+  /* timeouts */
+  guint tap_and_hold_id;
+  guint animation_id;
+
+  guint motion_id;
+
+  GdkWindow *window;
+  gint start_x;
+  gint start_y;
+  gint current_x;
+  gint current_y;
+
+  GdkPixbufAnimation *animation;
+  GdkPixbufAnimationIter *iter;
+
+  GdkCursor *cursor;
+} TapAndHoldData;
+
 /* --- prototypes --- */
 static void	gtk_widget_class_init		(GtkWidgetClass     *klass);
 static void	gtk_widget_base_class_finalize	(GtkWidgetClass     *klass);
@@ -242,6 +266,13 @@ static gboolean         gtk_widget_real_
 static void             gtk_widget_set_has_tooltip              (GtkWidget *widget,
 								 gboolean   has_tooltip,
 								 gboolean   force);
+
+static gboolean         gtk_widget_tap_and_hold_cancel             (GtkWidget      *widget,
+								    gpointer        unused,
+								    gpointer        user_data);
+static gboolean         gtk_widget_tap_and_hold_button_press_event (GtkWidget      *widget,
+								    GdkEventButton *button,
+								    gpointer        user_data);
      
 static void gtk_widget_set_usize_internal (GtkWidget *widget,
 					   gint       width,
@@ -426,6 +457,7 @@ gtk_widget_class_init (GtkWidgetClass *k
   klass->can_activate_accel = gtk_widget_real_can_activate_accel;
   klass->grab_broken_event = NULL;
   klass->query_tooltip = gtk_widget_real_query_tooltip;
+  klass->tap_and_hold = NULL;
 
   klass->show_help = gtk_widget_real_show_help;
   
@@ -1616,6 +1648,67 @@ gtk_widget_class_init (GtkWidgetClass *k
 		  _gtk_marshal_BOOLEAN__UINT,
                   G_TYPE_BOOLEAN, 1, G_TYPE_UINT);
 
+  /* GtkWidget::tap-and-hold:
+   * @widget: the object which received the signal
+   * @action: a #GtkTapAndHoldAction specifying the action
+   * @x: if the action is not %GTK_TAP_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_TAP_AND_HOLD_CANCEL, the y coordinate of the
+   *     cursor position where the request has been emitted, relative to
+   *     widget->window, otherwise undefined
+   * @keyboard_mode: %TRUE if the tap-and-hold action was initiated by
+   *                 the keyboard.
+   *
+   * Connect to this signal and correctly handle all of its actions if you
+   * want your widget to support the tap-n-hold operation.  The tap-and-hold
+   * operation is defined as keeping a mouse button pressed for a given amount
+   * of time (specified in the "tap-and-hold-timeout" GtkSetting); during
+   * this time the mouse is only allowed to move a little bit (not past the
+   * drag threshold), else the tap-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 tap-and-hold operation has been terminated
+   * before the trigger action has been emitted.
+   *
+   * For query, @action will be set to %GTK_TAP_AND_HOLD_QUERY, @x and @y
+   * will be set to the cursor position and @keyboard_mode is undefined.
+   * A return value of %FALSE means no tap-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_TAP_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_TAP_AND_HOLD_CANCEL, @x and @y are both
+   * undefined, as is @keyboard_mode.  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: 2.12
+   */
+  widget_signals[TAP_AND_HOLD] =
+    g_signal_new (I_("tap_and_hold"),
+		  G_TYPE_FROM_CLASS (gobject_class),
+		  G_SIGNAL_RUN_LAST,
+		  G_STRUCT_OFFSET (GtkWidgetClass, tap_and_hold),
+		  _gtk_boolean_handled_accumulator, NULL,
+		  _gtk_marshal_BOOLEAN__ENUM_INT_INT_BOOLEAN,
+		  G_TYPE_BOOLEAN, 4,
+		  GTK_TYPE_TAP_AND_HOLD_ACTION,
+		  G_TYPE_INT,
+		  G_TYPE_INT,
+		  G_TYPE_BOOLEAN);
+
   binding_set = gtk_binding_set_by_class (klass);
   gtk_binding_entry_add_signal (binding_set, GDK_F10, GDK_SHIFT_MASK,
                                 "popup_menu", 0);
@@ -1801,6 +1894,23 @@ gtk_widget_class_init (GtkWidgetClass *k
                                                              P_("The length of vertical scroll arrows"),
                                                              1, G_MAXINT, 16,
                                                              GTK_PARAM_READABLE));
+
+/**
+ * GtkWidget:tap-and-hold-cursor:
+ *
+ * The "tap-and-hold-cursor" specifies the cursor to use between the
+ * query and trigger actions of the GtkWidget::tap-and-hold signal.  The
+ * value can either be a nickname of a #GdkCursor or a path to an
+ * animated image file.  By default the "watch" cursor is used.
+ *
+ * Since: 2.12
+ */
+  gtk_widget_class_install_style_property (klass,
+					   g_param_spec_string ("tap-and-hold-cursor",
+								P_("Tap and hold cursor"),
+								P_("Cursor used as tap and hold indicator"),
+								"watch",
+								GTK_PARAM_READABLE));
 }
 
 static void
@@ -2056,6 +2166,16 @@ gtk_widget_init (GtkWidget *widget)
 
   widget->style = gtk_widget_get_default_style ();
   g_object_ref (widget->style);
+
+  /* FIXME: Enable tap and hold */
+  g_signal_connect (widget, "button-press-event",
+		    G_CALLBACK (gtk_widget_tap_and_hold_button_press_event), NULL);
+  g_signal_connect (widget, "button-release-event",
+		    G_CALLBACK (gtk_widget_tap_and_hold_cancel), NULL);
+  g_signal_connect (widget, "leave-notify-event",
+		    G_CALLBACK (gtk_widget_tap_and_hold_cancel), NULL);
+  g_signal_connect (widget, "drag-begin",
+		    G_CALLBACK (gtk_widget_tap_and_hold_cancel), NULL);
 }
 
 
@@ -4605,6 +4725,409 @@ gtk_widget_real_keynav_failed (GtkWidget
   return TRUE;
 }
 
+
+/* --- Tap and hold --- */
+
+static inline TapAndHoldData *
+gtk_widget_peek_tap_and_hold_data (GtkWidget *widget)
+{
+  return g_object_get_data (G_OBJECT (widget), "gtk-widget-tap-and-hold-data");
+}
+
+static void
+tap_and_hold_data_free (gpointer tap_and_hold_data)
+{
+  TapAndHoldData *data = tap_and_hold_data;
+
+  if (data->window)
+    {
+      gdk_window_set_cursor (data->window, NULL);
+      g_object_unref (data->window);
+    }
+
+  if (data->tap_and_hold_id)
+    g_source_remove (data->tap_and_hold_id);
+
+  if (data->animation_id)
+    g_source_remove (data->animation_id);
+
+  if (data->iter)
+    g_object_unref (data->iter);
+
+  if (data->animation)
+    g_object_unref (data->animation);
+
+  if (data->cursor)
+    gdk_cursor_unref (data->cursor);
+
+  g_free (data);
+}
+
+static inline void
+gtk_widget_set_tap_and_hold_data (GtkWidget      *widget,
+				  TapAndHoldData *data)
+{
+  g_object_set_data_full (G_OBJECT (widget), "gtk-widget-tap-and-hold-data",
+			  data, tap_and_hold_data_free);
+}
+
+static inline TapAndHoldData *
+gtk_widget_get_tap_and_hold_data (GtkWidget *widget)
+{
+  TapAndHoldData *data;
+
+  data = gtk_widget_peek_tap_and_hold_data (widget);
+  if (!data)
+    {
+      data = g_new0 (TapAndHoldData, 1);
+      gtk_widget_set_tap_and_hold_data (widget, data);
+    }
+
+  return data;
+}
+
+static inline void
+gtk_widget_tap_and_hold_remove_timer (GtkWidget *widget)
+{
+  TapAndHoldData *data;
+
+  data = gtk_widget_peek_tap_and_hold_data (widget);
+  if (!data)
+    return;
+
+  if (data->window)
+    {
+      gdk_window_set_cursor (data->window, NULL);
+      g_object_unref (data->window);
+      data->window = NULL;
+    }
+
+  if (data->motion_id)
+    g_signal_handler_disconnect (widget, data->motion_id);
+  data->motion_id = 0;
+
+  if (data->tap_and_hold_id)
+    g_source_remove (data->tap_and_hold_id);
+  data->tap_and_hold_id = 0;
+
+  if (data->animation_id)
+    g_source_remove (data->animation_id);
+  data->animation_id = 0;
+}
+
+static gboolean
+gtk_widget_tap_and_hold_animation_timeout (gpointer user_data)
+{
+  gint timeout;
+  GTimeVal time;
+  GdkScreen *screen;
+  GdkPixbuf *pixbuf;
+  GdkCursor *cursor;
+  const gchar *x_hot, *y_hot;
+  gint x, y;
+
+  GtkWidget *widget = GTK_WIDGET (user_data);
+  TapAndHoldData *data = gtk_widget_peek_tap_and_hold_data (widget);
+
+  if (!data || !GDK_IS_WINDOW (widget->window))
+    {
+      gtk_widget_tap_and_hold_remove_timer (widget);
+
+      return FALSE;
+    }
+
+  if (!data->animation)
+    {
+      data->animation_id = 0;
+      return FALSE;
+    }
+
+  g_get_current_time (&time);
+  screen = gdk_screen_get_default ();
+
+  pixbuf = gdk_pixbuf_animation_iter_get_pixbuf (data->iter);
+  if (!GDK_IS_PIXBUF (pixbuf))
+    return TRUE;
+
+  x_hot = gdk_pixbuf_get_option (pixbuf, "x_hot");
+  x = x_hot ? atoi (x_hot) : gdk_pixbuf_get_width (pixbuf) / 2;
+
+  y_hot = gdk_pixbuf_get_option (pixbuf, "y_hot");
+  y = y_hot ? atoi (y_hot) : gdk_pixbuf_get_height (pixbuf) / 2;
+
+  cursor = gdk_cursor_new_from_pixbuf (gdk_display_get_default (),
+				       pixbuf, x, y);
+  if (!cursor)
+    return TRUE;
+
+  gdk_window_set_cursor (widget->window, cursor);
+  gdk_cursor_unref (cursor);
+
+  gdk_pixbuf_animation_iter_advance (data->iter, &time);
+  timeout = gdk_pixbuf_animation_iter_get_delay_time (data->iter);
+  data->animation_id =
+    gdk_threads_add_timeout (timeout,
+			     gtk_widget_tap_and_hold_animation_timeout,
+			     widget);
+
+  return FALSE;
+}
+
+static void
+to_widget_allocation (GdkWindow   *window,
+		      gint         src_x,
+		      gint         src_y,
+		      gint        *x,
+		      gint        *y)
+{
+  GtkWidget *widget;
+
+  gdk_window_get_user_data (window, (void **)&widget);
+
+  while (window && window != widget->window)
+    {
+      gint px, py;
+
+      gdk_window_get_position (window, &px, &py);
+      src_x += px;
+      src_y += py;
+
+      window = gdk_window_get_parent (window);
+    }
+
+  if (GTK_WIDGET_NO_WINDOW (widget))
+    {
+      src_x += widget->allocation.x;
+      src_y += widget->allocation.y;
+    }
+
+  /* Failing to find widget->window can happen for e.g. a detached handle box;
+   * chaining ::tap-and-hold up to its parent probably makes little sense,
+   * and users better implement tap-and-hold on handle_box->child.
+   * so we simply ignore the event for tap-and-hold here.
+   */
+  if (!window)
+    return;
+
+  /* Translate from window relative to allocation relative */
+  if (!GTK_WIDGET_NO_WINDOW (widget) && widget->parent)
+    {
+      gint wx, wy;
+      gdk_window_get_position (widget->window, &wx, &wy);
+
+      src_x += wx - widget->allocation.x;
+      src_y += wy - widget->allocation.y;
+    }
+  else
+    {
+      src_x -= widget->allocation.x;
+      src_y -= widget->allocation.y;
+    }
+
+  if (x)
+    *x = src_x;
+  if (y)
+    *y = src_y;
+}
+
+static gboolean
+gtk_widget_tap_and_hold_motion_notify (GtkWidget      *widget,
+				       GdkEventMotion *event,
+				       gpointer        user_data)
+{
+  TapAndHoldData *data = gtk_widget_peek_tap_and_hold_data (widget);
+
+  if (!data || !GDK_IS_WINDOW (widget->window))
+    {
+      gtk_widget_tap_and_hold_remove_timer (widget);
+
+      return FALSE;
+    }
+
+  to_widget_allocation (event->window, event->x, event->y,
+			&data->current_x, &data->current_y);
+
+  /* Stop tap-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_tap_and_hold_cancel (widget, NULL, NULL);
+
+      return FALSE;
+    }
+
+  return FALSE;
+}
+
+static gboolean
+gtk_widget_tap_and_hold_timeout (gpointer user_data)
+{
+  gboolean return_value;
+  GtkWidget *widget = GTK_WIDGET (user_data);
+  TapAndHoldData *data = gtk_widget_peek_tap_and_hold_data (widget);
+
+  /* Done, clean up and emit the trigger signal */
+  gtk_widget_tap_and_hold_remove_timer (widget);
+  _gtk_widget_grab_notify (widget, FALSE);
+
+  g_signal_emit (widget, widget_signals[TAP_AND_HOLD],
+		 0,
+		 GTK_TAP_AND_HOLD_TRIGGER,
+		 data->current_x, data->current_y,
+		 FALSE,
+		 &return_value);
+
+  return FALSE;
+}
+
+static gboolean
+gtk_widget_tap_and_hold_query (GtkWidget *widget,
+			       gint       x,
+			       gint       y)
+{
+  gboolean return_value = FALSE;
+
+  g_signal_emit (widget, widget_signals[TAP_AND_HOLD],
+		 0,
+		 GTK_TAP_AND_HOLD_QUERY,
+		 x, y,
+		 FALSE,
+		 &return_value);
+
+  return return_value;
+}
+
+static gboolean
+gtk_widget_tap_and_hold_cancel (GtkWidget      *widget,
+				gpointer        unused,
+				gpointer        user_data)
+{
+  gboolean return_value;
+  TapAndHoldData *data;
+
+  data = gtk_widget_peek_tap_and_hold_data (widget);
+  if (!data || !data->tap_and_hold_id)
+    return FALSE;
+
+  gtk_widget_tap_and_hold_remove_timer (widget);
+
+  g_signal_emit (widget, widget_signals[TAP_AND_HOLD],
+		 0,
+		 GTK_TAP_AND_HOLD_CANCEL,
+		 -1, -1,
+		 FALSE,
+		 &return_value);
+
+  return FALSE;
+}
+
+static gboolean
+gtk_widget_tap_and_hold_button_press_event (GtkWidget      *widget,
+					    GdkEventButton *event,
+					    gpointer        user_data)
+{
+  TapAndHoldData *data;
+
+  if (event->type == GDK_2BUTTON_PRESS)
+    return FALSE;
+
+  data = gtk_widget_get_tap_and_hold_data (widget);
+  if (!data)
+    return FALSE;
+
+  if (gtk_widget_tap_and_hold_query (widget, event->x, event->y)
+      && !data->tap_and_hold_id)
+    {
+      gint timeout;
+      gchar *cursor_name;
+      GEnumValue *cursor_value;
+      GEnumClass *enum_class;
+
+      to_widget_allocation (event->window, event->x, event->y,
+			    &data->start_x, &data->start_y);
+
+      data->current_x = data->start_x;
+      data->current_y = data->start_y;
+
+      data->window = g_object_ref (widget->window);
+
+      g_object_get (gtk_widget_get_settings (GTK_WIDGET (widget)),
+		    "gtk-tap-and-hold-timeout", &timeout,
+		    NULL);
+
+      gtk_widget_style_get (GTK_WIDGET (widget),
+			    "tap-and-hold-cursor", &cursor_name,
+			    NULL);
+
+      enum_class = g_type_class_ref (GDK_TYPE_CURSOR_TYPE);
+      cursor_value = g_enum_get_value_by_nick (enum_class, cursor_name);
+      g_type_class_unref (enum_class);
+
+      /* We try loading a mouse cursor with the string in the
+       * tap-and-hold-cursor style property first, if that fails,
+       * we try using it as a path to load an animation.
+       */
+      if (cursor_value)
+        {
+	  if (data->cursor)
+	    gdk_cursor_unref (data->cursor);
+
+	  data->cursor =
+	    gdk_cursor_new_for_display (gtk_widget_get_display (widget),
+					cursor_value->value);
+	  gdk_window_set_cursor (widget->window, data->cursor);
+	}
+      else
+        {
+	  gint ani_timeout;
+	  GError *error = NULL;
+
+	  if (data->animation)
+	    g_object_unref (data->animation);
+
+	  data->animation = gdk_pixbuf_animation_new_from_file (cursor_name,
+								&error);
+	  if (!data->animation)
+	    {
+	      g_warning ("Couldn't load tap-and-hold animation: %s\n",
+		         error->message);
+	      g_error_free (error);
+	    }
+	  else
+	    {
+	      if (data->iter)
+		g_object_unref (data->iter);
+	      data->iter = gdk_pixbuf_animation_get_iter (data->animation, NULL);
+
+	      ani_timeout = gdk_pixbuf_animation_iter_get_delay_time (data->iter);
+	      data->animation_id =
+		gdk_threads_add_timeout (ani_timeout,
+					 gtk_widget_tap_and_hold_animation_timeout,
+					 widget);
+	    }
+	}
+
+      /* Make sure we catch all motion events for widget */
+      if (GTK_WIDGET_NO_WINDOW (widget))
+	gdk_window_set_events (widget->window,
+			       gdk_window_get_events (widget->window)
+			       | GDK_POINTER_MOTION_MASK);
+      gtk_widget_add_events (widget, GDK_POINTER_MOTION_MASK);
+
+      data->motion_id =
+	g_signal_connect (widget, "motion-notify-event",
+			  G_CALLBACK (gtk_widget_tap_and_hold_motion_notify),
+			  NULL);
+
+      data->tap_and_hold_id =
+	gdk_threads_add_timeout (timeout,
+				 gtk_widget_tap_and_hold_timeout,
+				 widget);
+    }
+
+  return FALSE;
+}
+
 /**
  * gtk_widget_is_focus:
  * @widget: a #GtkWidget
Index: gtk/gtkmarshalers.list
===================================================================
--- gtk/gtkmarshalers.list	(revision 17895)
+++ gtk/gtkmarshalers.list	(working copy)
@@ -27,6 +27,7 @@ BOOLEAN:ENUM
 BOOLEAN:ENUM,BOOLEAN
 BOOLEAN:ENUM,DOUBLE
 BOOLEAN:ENUM,INT
+BOOLEAN:ENUM,INT,INT,BOOLEAN
 BOOLEAN:OBJECT
 BOOLEAN:OBJECT,UINT,FLAGS
 BOOLEAN:OBJECT,INT,INT,UINT
Index: gtk/gtksettings.c
===================================================================
--- gtk/gtksettings.c	(revision 17895)
+++ gtk/gtksettings.c	(working copy)
@@ -105,7 +105,8 @@ enum {
   PROP_PRINT_BACKENDS,
   PROP_PRINT_PREVIEW_COMMAND,
   PROP_ENABLE_MNEMONICS,
-  PROP_ENABLE_ACCELS
+  PROP_ENABLE_ACCELS,
+  PROP_TAP_AND_HOLD_TIMEOUT
 };
 
 
@@ -785,6 +786,24 @@ gtk_settings_class_init (GtkSettingsClas
                                                                    GTK_PARAM_READWRITE),
                                              NULL);
   g_assert (result == PROP_ENABLE_ACCELS);
+
+  /**
+   * GtkSettings:gtk-tap-and-hold-timeout:
+   *
+   * The amount of time, in milliseconds, a button has to be pressed
+   * before the tap-and-hold signal with the trigger action is emitted.
+   *
+   * Since: 2.12
+   */
+  result = settings_install_property_parser (class,
+                                             g_param_spec_int ("gtk-tap-and-hold-timeout",
+                                                               P_("Tap And Hold Timeout"),
+                                                               P_("Timeout before tap-and-hold action activates"),
+                                                               0, G_MAXINT,
+                                                               1000,
+                                                               GTK_PARAM_READWRITE),
+                                             NULL);
+  g_assert (result == PROP_TAP_AND_HOLD_TIMEOUT);
 }
 
 static void
Index: tests/Makefile.am
===================================================================
--- tests/Makefile.am	(revision 17844)
+++ tests/Makefile.am	(working copy)
@@ -85,7 +85,8 @@ noinst_PROGRAMS =			\
 	testmerge			\
 	testactions			\
 	testgrouping			\
-	testtooltips
+	testtooltips			\
+	testtapandhold
 
 autotestfilechooser_DEPENDENCIES = $(TEST_DEPS)
 simple_DEPENDENCIES = $(TEST_DEPS)
@@ -140,6 +141,7 @@ testmerge_DEPENDENCIES = $(TEST_DEPS)
 testactions_DEPENDENCIES = $(TEST_DEPS)
 testgrouping_DEPENDENCIES = $(TEST_DEPS)
 testtooltips_DEPENDENCIES = $(TEST_DEPS)
+testtapandhold_DEPENDENCIES = $(TEST_DEPS)
 
 autotestfilechooser_LDADD = $(LDADDS)
 simple_LDADD = $(LDADDS)
@@ -201,6 +203,7 @@ testmerge_LDADD = $(LDADDS)
 testactions_LDADD = $(LDADDS)
 testgrouping_LDADD = $(LDADDS)
 testtooltips_LDADD = $(LDADDS)
+testtapandhold_LDADD = $(LDADDS)
 
 autotestfilechooser_SOURCES =	\
 	autotestfilechooser.c
@@ -282,6 +285,9 @@ testgrouping_SOURCES =		\
 testtooltips_SOURCES =		\
 	testtooltips.c
 
+testtapandhold_SOURCES =	\
+	testtapandhold.c
+
 testrecentchoosermenu_SOURCES =	\
 	testrecentchoosermenu.c
 
Index: tests/testtapandhold.c
===================================================================
--- tests/testtapandhold.c	(revision 0)
+++ tests/testtapandhold.c	(revision 0)
@@ -0,0 +1,190 @@
+/* testtapandhold.c: Test application for GTK+ >= 2.12 tap-n-hold code
+ *
+ * Copyright (C) 2007  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_size_request (GTK_WIDGET (menu), &req);
+
+  *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
+tap_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 (widget->window, &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
+tap_and_hold (GtkWidget           *widget,
+	      GtkTapAndHoldAction  action,
+	      gint                 x,
+	      gint                 y,
+	      gboolean             keyboard)
+{
+  switch (action)
+    {
+      case GTK_TAP_AND_HOLD_QUERY:
+	g_print ("tap-and-hold-query on %s\n", gtk_widget_get_name (widget));
+        return TRUE;
+
+      case GTK_TAP_AND_HOLD_TRIGGER:
+	g_print ("tap-and-hold-trigger on %s\n", gtk_widget_get_name (widget));
+        tap_and_hold_show_menu (widget, x, y);
+        break;
+
+      case GTK_TAP_AND_HOLD_CANCEL:
+	g_print ("tap-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), "Tap 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 ("Tap-n-hold me!");
+  g_signal_connect (label, "tap-and-hold",
+		    G_CALLBACK (tap_and_hold), NULL);
+  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, "tap-and-hold",
+		    G_CALLBACK (tap_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, "tap-and-hold",
+		    G_CALLBACK (tap_and_hold), NULL);
+
+  gtk_box_pack_start (GTK_BOX (box), tree_view, FALSE, FALSE, 0);
+
+  gtk_widget_show_all (window);
+
+  gtk_main ();
+
+  return 0;
+}
