diff --git a/gtk/gtkmarshalers.list b/gtk/gtkmarshalers.list index e104d31263..9b8195b735 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 4679a49621..297046bbc3 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 71717917f9..f72503cba8 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 1f7b15ab2f..a214b3839d 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 bcacac80c2..97da0614ba 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 0000000000..0f0f2873c3 --- /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 + * + * 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 + +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; +}