[gtk+/multitouch: 10/129] Add GtkWidget::press-and-hold signal
- From: Carlos Garnacho <carlosg src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gtk+/multitouch: 10/129] Add GtkWidget::press-and-hold signal
- Date: Sat, 18 Feb 2012 17:07:09 +0000 (UTC)
commit 7a59b443eefac87354802c4e7117f9e61dbeee9d
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 4679a49..297046b 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 bcacac8..97da061 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -78,6 +78,7 @@ noinst_PROGRAMS = $(TEST_PROGS) \
testorientable \
testoverlay \
testprint \
+ testpressandhold \
testrecentchooser \
testrecentchoosermenu \
testrichtext \
@@ -198,6 +199,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)
@@ -298,6 +300,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]