diff --git a/gtk/gtkmarshalers.list b/gtk/gtkmarshalers.list
index fa60a7c..b192d57 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
 BOOLEAN:OBJECT
 BOOLEAN:OBJECT,UINT,FLAGS
 BOOLEAN:OBJECT,INT,INT,UINT
diff --git a/gtk/gtksettings.c b/gtk/gtksettings.c
index 44fe5c6..addaf63 100644
--- a/gtk/gtksettings.c
+++ b/gtk/gtksettings.c
@@ -196,7 +196,8 @@ enum {
   PROP_LABEL_SELECT_ON_FOCUS,
   PROP_COLOR_PALETTE,
   PROP_IM_PREEDIT_STYLE,
-  PROP_IM_STATUS_STYLE
+  PROP_IM_STATUS_STYLE,
+  PROP_PRESS_AND_HOLD_TIMEOUT
 };
 
 /* --- prototypes --- */
@@ -1296,6 +1297,24 @@ gtk_settings_class_init (GtkSettingsClass *class)
                                              gtk_rc_property_parse_enum);
   g_assert (result == PROP_IM_STATUS_STYLE);
 
+  /**
+   * 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: PRESSANDHOLD
+   */
+  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,
+                                                               1000,
+                                                               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 b2e3572..4fa2b5c 100644
--- a/gtk/gtkwidget.c
+++ b/gtk/gtkwidget.c
@@ -27,6 +27,7 @@
 #include "config.h"
 
 #include <stdarg.h>
+#include <stdlib.h>
 #include <string.h>
 #include <locale.h>
 
@@ -65,6 +66,8 @@
 #include "gtkdebug.h"
 #include "gtkplug.h"
 #include "gtktypebuiltins.h"
+#include "gtkdnd.h"
+#include "gtkicontheme.h"
 
 
 /**
@@ -457,6 +460,7 @@ enum {
   QUERY_TOOLTIP,
   DRAG_FAILED,
   STYLE_UPDATED,
+  PRESS_AND_HOLD,
   LAST_SIGNAL
 };
 
@@ -514,6 +518,26 @@ struct _GtkStateData
   guint		use_forall : 1;
 };
 
+typedef struct
+{
+  /* timeouts */
+  guint press_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;
+} PressAndHoldData;
+
 /* --- prototypes --- */
 static void	gtk_widget_class_init		(GtkWidgetClass     *klass);
 static void	gtk_widget_base_class_finalize	(GtkWidgetClass     *klass);
@@ -616,6 +640,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,
+                                                                      gpointer           user_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);
@@ -897,6 +927,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;
 
@@ -2961,6 +2992,68 @@ 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
+   * @keyboard_mode: %TRUE if the press-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 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 and @keyboard_mode is undefined.
+   * 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, 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: PRESSANDHOLD
+   */
+  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_BOOLEAN,
+		  G_TYPE_BOOLEAN, 4,
+		  GTK_TYPE_PRESS_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_KEY_F10, GDK_SHIFT_MASK,
                                 "popup-menu", 0);
@@ -3138,6 +3231,22 @@ gtk_widget_class_init (GtkWidgetClass *klass)
                                                              P_("The length of vertical scroll arrows"),
                                                              1, G_MAXINT, 16,
                                                              GTK_PARAM_READABLE));
+  /**
+   * GtkWidget:press-and-hold-cursor:
+   *
+   * The "press-and-hold-cursor" specifies the cursor to use between the
+   * query and trigger actions of the GtkWidget::press-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: PRESSANDHOLD
+   */
+  gtk_widget_class_install_style_property (klass,
+                                           g_param_spec_string ("press-and-hold-cursor",
+                                                                P_("Press and hold cursor"),
+                                                                P_("Cursor used as press and hold indicator"),
+                                                                "watch",
+                                                                GTK_PARAM_READABLE));
 
   g_type_class_add_private (klass, sizeof (GtkWidgetPrivate));
 }
@@ -3519,6 +3628,16 @@ gtk_widget_init (GtkWidget *widget)
 
   priv->style = gtk_widget_get_default_style ();
   g_object_ref (priv->style);
+
+  /* FIXME: Enable press and hold */
+  g_signal_connect (widget, "button-press-event",
+                    G_CALLBACK (gtk_widget_press_and_hold_button_press_event), NULL);
+  g_signal_connect (widget, "button-release-event",
+                    G_CALLBACK (gtk_widget_press_and_hold_cancel), NULL);
+  g_signal_connect (widget, "leave-notify-event",
+                    G_CALLBACK (gtk_widget_press_and_hold_cancel), NULL);
+  g_signal_connect (widget, "drag-begin",
+                    G_CALLBACK (gtk_widget_press_and_hold_cancel), NULL);
 }
 
 
@@ -6618,6 +6737,369 @@ 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_data (G_OBJECT (widget), "gtk-widget-press-and-hold-data");
+}
+
+static void
+press_and_hold_data_free (gpointer press_and_hold_data)
+{
+  PressAndHoldData *data = press_and_hold_data;
+
+  if (data->window)
+    {
+      gdk_window_set_cursor (data->window, NULL);
+      g_object_unref (data->window);
+    }
+
+  if (data->press_and_hold_id)
+    g_source_remove (data->press_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)
+    g_object_unref (data->cursor);
+
+  g_free (data);
+}
+
+static inline void
+gtk_widget_set_press_and_hold_data (GtkWidget        *widget,
+				    PressAndHoldData *data)
+{
+  g_object_set_data_full (G_OBJECT (widget), "gtk-widget-press-and-hold-data",
+			  data, 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_new0 (PressAndHoldData, 1);
+      gtk_widget_set_press_and_hold_data (widget, data);
+    }
+
+  return data;
+}
+
+static inline void
+gtk_widget_press_and_hold_remove_timer (GtkWidget *widget)
+{
+  PressAndHoldData *data;
+
+  data = gtk_widget_peek_press_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->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_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);
+  PressAndHoldData *data = gtk_widget_peek_press_and_hold_data (widget);
+
+  if (!data || !GDK_IS_WINDOW (widget->priv->window))
+    {
+      gtk_widget_press_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->priv->window, cursor);
+  g_object_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_press_and_hold_animation_timeout,
+                               widget);
+
+  return FALSE;
+}
+
+static gboolean
+gtk_widget_press_and_hold_motion_notify (GtkWidget      *widget,
+                                         GdkEventMotion *event,
+                                         gpointer        user_data)
+{
+  PressAndHoldData *data = gtk_widget_peek_press_and_hold_data (widget);
+
+  if (!data || !GDK_IS_WINDOW (widget->priv->window))
+    {
+      gtk_widget_press_and_hold_remove_timer (widget);
+
+      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, NULL);
+
+      return FALSE;
+    }
+
+  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_remove_timer (widget);
+  _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,
+                 FALSE,
+                 &return_value);
+
+  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);
+  gchar *cursor_name;
+  GEnumValue *cursor_value;
+  GEnumClass *enum_class;
+
+  gtk_widget_style_get (GTK_WIDGET (widget),
+                       "press-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
+   * press-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)
+        g_object_unref (data->cursor);
+
+      data->cursor =
+          gdk_cursor_new_for_display (gtk_widget_get_display (widget),
+                                      cursor_value->value);
+      gdk_window_set_cursor (widget->priv->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 press-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_press_and_hold_animation_timeout,
+                                       widget);
+        }
+    }
+
+  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,
+                 FALSE,
+                 &return_value);
+
+  return return_value;
+}
+
+static gboolean
+gtk_widget_press_and_hold_cancel (GtkWidget      *widget,
+                                  gpointer        unused,
+                                  gpointer        user_data)
+{
+  gboolean return_value;
+  PressAndHoldData *data;
+
+  data = gtk_widget_peek_press_and_hold_data (widget);
+  if (!data || !data->press_and_hold_id)
+    return FALSE;
+
+  gtk_widget_press_and_hold_remove_timer (widget);
+
+  g_signal_emit (widget, widget_signals[PRESS_AND_HOLD],
+                 0,
+                 GTK_PRESS_AND_HOLD_CANCEL,
+                 -1, -1,
+                 FALSE,
+                 &return_value);
+
+  return FALSE;
+}
+
+static gboolean
+gtk_widget_press_and_hold_button_press_event (GtkWidget      *widget,
+                                              GdkEventButton *event,
+                                              gpointer        user_data)
+{
+  PressAndHoldData *data;
+
+  if (event->type == GDK_2BUTTON_PRESS)
+    return FALSE;
+
+  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;
+
+      _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;
+
+      data->window = g_object_ref (widget->priv->window);
+
+      g_object_get (gtk_widget_get_settings (GTK_WIDGET (widget)),
+                    "gtk-press-and-hold-timeout", &timeout,
+                    "gtk-timeout-initial", &begin_ani_timeout,
+                    NULL);
+
+      /* 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);
+
+      /* Make sure we catch all motion events for widget */
+      if (gtk_widget_get_has_window (widget))
+        gdk_window_set_events (widget->priv->window,
+                               gdk_window_get_events (widget->priv->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_press_and_hold_motion_notify),
+                            NULL);
+
+      data->press_and_hold_id =
+          gdk_threads_add_timeout (timeout,
+                                   gtk_widget_press_and_hold_timeout,
+                                   widget);
+    }
+
+  return FALSE;
+}
+
 /**
  * gtk_widget_is_focus:
  * @widget: a #GtkWidget
diff --git a/gtk/gtkwidget.h b/gtk/gtkwidget.h
index c391b22..11c5c2e 100644
--- a/gtk/gtkwidget.h
+++ b/gtk/gtkwidget.h
@@ -50,6 +50,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.
  */
@@ -423,6 +430,12 @@ struct _GtkWidgetClass
 
   void         (* style_updated)          (GtkWidget *widget);
 
+  gboolean     (* press_and_hold)         (GtkWidget             *widget,
+                                           GtkPressAndHoldAction  action,
+                                           gint                   x,
+                                           gint                   y,
+                                           gboolean               keyboard_triggered);
+
   /*< private >*/
 
   /* Padding for future expansion */
@@ -433,7 +446,6 @@ struct _GtkWidgetClass
   void (*_gtk_reserved5) (void);
   void (*_gtk_reserved6) (void);
   void (*_gtk_reserved7) (void);
-  void (*_gtk_reserved8) (void);
 };
 
 struct _GtkWidgetAuxInfo
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 318bbfe..2b3ab45 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -68,6 +68,7 @@ noinst_PROGRAMS =  $(TEST_PROGS)	\
 	testoffscreenwindow		\
 	testorientable			\
 	testprint			\
+	testpressandhold		\
 	testrecentchooser 		\
 	testrecentchoosermenu		\
 	testrichtext			\
@@ -168,6 +169,7 @@ testappchooser_DEPENDENCIES = $(TEST_DEPS)
 testappchooserbutton_DEPENDENCIES = $(TEST_DEPS)
 testorientable_DEPENDENCIES = $(TEST_DEPS)
 testprint_DEPENDENCIES = $(TEST_DEPS)
+testpressandhold_DEPENDENCIES = $(TEST_DEPS)
 testrecentchooser_DEPENDENCIES = $(TEST_DEPS)
 testrecentchoosermenu_DEPENDENCIES = $(TEST_DEPS)
 testrichtext_DEPENDENCIES = $(TEST_DEPS)
@@ -250,6 +252,7 @@ testappchooser_LDADD = $(LDADDS)
 testappchooserbutton_LDADD = $(LDADDS)
 testorientable_LDADD = $(LDADDS)
 testprint_LDADD = $(LDADDS)
+testpressandhold_LDADD = $(LDADDS)
 testrecentchooser_LDADD = $(LDADDS)
 testrecentchoosermenu_LDADD = $(LDADDS)
 testrichtext_LDADD = $(LDADDS)
@@ -328,6 +331,9 @@ testtoolbar_SOURCES =	\
 	testtoolbar.c	\
 	prop-editor.c
 
+testpressandhold_SOURCES =	\
+	testpressandhold.c
+
 testprint_SOURCES =    	\
 	testprint.c		\
 	testprintfileoperation.h \
diff --git a/tests/testpressandhold.c b/tests/testpressandhold.c
new file mode 100644
index 0000000..20ff51a
--- /dev/null
+++ b/tests/testpressandhold.c
@@ -0,0 +1,192 @@
+/* testpressandhold.c: Test application for GTK+ >= 2.16 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;
+}
