From aa0c220f7d8fa50397dab3d13b1c515559bcff1e Mon Sep 17 00:00:00 2001 From: Carlos Garcia Campos Date: Wed, 23 Mar 2011 17:42:21 +0100 Subject: [PATCH] scrolledwindow: Allow selections and drag-and-drop when kinetic scrolling is enabled If the scrolling doesn't start after a long press, the scrolling is cancelled and events are handled by child widget normally. --- gtk/gtkscrolledwindow.c | 126 +++++++++++++++++++++++++++++++++-- tests/testkineticscrolling.c | 64 +++++++++++++++++- 2 files changed, 180 insertions(+), 10 deletions(-) diff --git a/gtk/gtkscrolledwindow.c b/gtk/gtkscrolledwindow.c index 7535ebfc24..be58926ec3 100644 --- a/gtk/gtkscrolledwindow.c +++ b/gtk/gtkscrolledwindow.c @@ -168,6 +168,7 @@ struct _GtkScrolledWindowPrivate /* Kinetic scrolling */ GdkWindow *event_window; + GdkEvent *button_press_event; guint kinetic_scrolling_enabled : 1; guint in_drag : 1; guint hmoving : 1; @@ -175,6 +176,7 @@ struct _GtkScrolledWindowPrivate guint button_press_id; guint motion_notify_id; guint button_release_id; + guint press_and_hold_id; MotionEventList motion_events; GtkTimeline *deceleration_timeline; gdouble dx; @@ -230,6 +232,10 @@ static gboolean gtk_scrolled_window_scroll_event (GtkWidget *widge GdkEventScroll *event); static gboolean gtk_scrolled_window_button_press_event (GtkWidget *widget, GdkEvent *event); +static gboolean gtk_scrolled_window_press_and_hold (GtkWidget *widget, + GtkPressAndHoldAction action, + gint x, + gint y); static gboolean gtk_scrolled_window_focus (GtkWidget *widget, GtkDirectionType direction); static void gtk_scrolled_window_add (GtkContainer *container, @@ -1125,9 +1131,14 @@ gtk_scrolled_window_set_kinetic_scrolling (GtkScrolledWindow *scrolled_window, gdk_window_show (priv->event_window); motion_event_list_init (&priv->motion_events, 3); priv->button_press_id = - g_signal_connect (scrolled_window, "captured_event", - G_CALLBACK (gtk_scrolled_window_button_press_event), - NULL); + g_signal_connect (scrolled_window, "captured-event", + G_CALLBACK (gtk_scrolled_window_button_press_event), + NULL); + priv->press_and_hold_id = + g_signal_connect (scrolled_window, "press-and-hold", + G_CALLBACK (gtk_scrolled_window_press_and_hold), + NULL); + /* Hide the scrollbars */ gtk_scrolled_window_auto_hide_scrollbars_start (scrolled_window, AUTO_HIDE_SCROLLBARS_TIMEOUT); @@ -1164,6 +1175,11 @@ gtk_scrolled_window_set_kinetic_scrolling (GtkScrolledWindow *scrolled_window, g_signal_handler_disconnect (scrolled_window, priv->button_release_id); priv->button_release_id = 0; } + if (priv->press_and_hold_id > 0) + { + g_signal_handler_disconnect (scrolled_window, priv->press_and_hold_id); + priv->press_and_hold_id = 0; + } motion_event_list_clear (&priv->motion_events); if (priv->event_window) gdk_window_hide (priv->event_window); @@ -1242,6 +1258,18 @@ gtk_scrolled_window_destroy (GtkWidget *widget) g_signal_handler_disconnect (widget, priv->button_release_id); priv->button_release_id = 0; } + if (priv->press_and_hold_id > 0) + { + g_signal_handler_disconnect (widget, priv->press_and_hold_id); + priv->press_and_hold_id = 0; + } + + if (priv->button_press_event) + { + gdk_event_free (priv->button_press_event); + priv->button_press_event = NULL; + } + motion_event_list_clear (&priv->motion_events); gtk_scrolled_window_auto_hide_scrollbars_stop (scrolled_window); @@ -2790,6 +2818,61 @@ gtk_scrolled_window_start_deceleration (GtkScrolledWindow *scrolled_window, duration + AUTO_HIDE_SCROLLBARS_TIMEOUT); } +static gboolean +gtk_scrolled_window_press_and_hold (GtkWidget *widget, + GtkPressAndHoldAction action, + gint x, + gint y) +{ + GtkScrolledWindow *scrolled_window = GTK_SCROLLED_WINDOW (widget); + GtkScrolledWindowPrivate *priv = scrolled_window->priv; + + switch (action) + { + case GTK_PRESS_AND_HOLD_QUERY: + return !priv->in_drag; + case GTK_PRESS_AND_HOLD_TRIGGER: + /* Cancel the scrolling and send the button press + * event to the child widget + */ + + gdk_device_ungrab (gdk_event_get_device (priv->button_press_event), GDK_CURRENT_TIME); + + if (priv->motion_notify_id > 0) + { + g_signal_handler_disconnect (scrolled_window, priv->motion_notify_id); + priv->motion_notify_id = 0; + } + if (priv->button_release_id > 0) + { + g_signal_handler_disconnect (scrolled_window, priv->button_release_id); + priv->button_release_id = 0; + } + + /* We are going to synthesize the button press event so that + * it can be handled by child widget, but we don't want to + * handle it, so block both button-press and and press-and-hold + * during this button press + */ + g_signal_handler_block (scrolled_window, priv->button_press_id); + g_signal_handler_block (scrolled_window, priv->press_and_hold_id); + + gtk_main_do_event (priv->button_press_event); + + g_signal_handler_unblock (scrolled_window, priv->button_press_id); + g_signal_handler_unblock (scrolled_window, priv->press_and_hold_id); + gdk_event_free (priv->button_press_event); + priv->button_press_event = NULL; + break; + case GTK_PRESS_AND_HOLD_CANCEL: + default: + break; + } + + /* Doesn't really matter in this case */ + return FALSE; +} + static gboolean gtk_scrolled_window_button_release_event (GtkWidget *widget, GdkEvent *_event) @@ -2808,6 +2891,10 @@ gtk_scrolled_window_button_release_event (GtkWidget *widget, if (event->button != 1) return FALSE; + child = gtk_bin_get_child (GTK_BIN (widget)); + if (!child) + return FALSE; + gdk_device_ungrab (gdk_event_get_device (_event), event->time); if (priv->motion_notify_id > 0) @@ -2825,13 +2912,30 @@ gtk_scrolled_window_button_release_event (GtkWidget *widget, { gtk_scrolled_window_auto_hide_scrollbars_start (scrolled_window, AUTO_HIDE_SCROLLBARS_TIMEOUT); + /* There hasn't been scrolling at all, so just let the + * child widget handle the events normally + */ + if (priv->button_press_event) + { + g_signal_handler_block (widget, priv->button_press_id); + gtk_main_do_event (priv->button_press_event); + g_signal_handler_unblock (widget, priv->button_press_id); + gdk_event_free (priv->button_press_event); + priv->button_press_event = NULL; + gtk_main_do_event (_event); + + return TRUE; + } + return FALSE; } priv->in_drag = FALSE; - child = gtk_bin_get_child (GTK_BIN (widget)); - if (!child) - return FALSE; + if (priv->button_press_event) + { + gdk_event_free (priv->button_press_event); + priv->button_press_event = NULL; + } distance = gtk_scrolled_window_get_deceleration_distance (scrolled_window, event->x_root, event->y_root); gtk_scrolled_window_start_deceleration (scrolled_window, distance); @@ -2877,6 +2981,12 @@ gtk_scrolled_window_motion_notify_event (GtkWidget *widget, return FALSE; } + if (priv->button_press_event) + { + gdk_event_free (priv->button_press_event); + priv->button_press_event = NULL; + } + motion = motion_event_list_last (&priv->motion_events); hadjustment = gtk_range_get_adjustment (GTK_RANGE (priv->hscrollbar)); @@ -2979,7 +3089,9 @@ gtk_scrolled_window_button_press_event (GtkWidget *widget, else priv->in_drag = FALSE; - return FALSE; + priv->button_press_event = gdk_event_copy (_event); + + return TRUE; } static gboolean diff --git a/tests/testkineticscrolling.c b/tests/testkineticscrolling.c index 693825a13c..6410ce56b9 100644 --- a/tests/testkineticscrolling.c +++ b/tests/testkineticscrolling.c @@ -1,5 +1,15 @@ #include +enum +{ + TARGET_GTK_TREE_MODEL_ROW +}; + +static GtkTargetEntry row_targets[] = +{ + { "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_APP, TARGET_GTK_TREE_MODEL_ROW } +}; + static void on_button_clicked (GtkWidget *widget, gpointer data) { @@ -12,6 +22,9 @@ kinetic_scrolling (void) GtkWidget *window, *swindow, *table; GtkWidget *label; GtkWidget *vbox, *button; + GtkWidget *treeview; + GtkCellRenderer *renderer; + GtkListStore *store; GtkWidget *textview; gint i; @@ -20,18 +33,23 @@ kinetic_scrolling (void) g_signal_connect (window, "delete_event", G_CALLBACK (gtk_main_quit), NULL); - table = gtk_table_new (2, 2, FALSE); + table = gtk_table_new (2, 3, FALSE); label = gtk_label_new ("Non scrollable widget using viewport"); gtk_table_attach (GTK_TABLE (table), label, 0, 1, 0, 1, GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0); gtk_widget_show (label); - label = gtk_label_new ("Scrollable widget"); + label = gtk_label_new ("Scrollable widget: TreeView"); gtk_table_attach (GTK_TABLE (table), label, 1, 2, 0, 1, GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0); gtk_widget_show (label); + label = gtk_label_new ("Scrollable widget: TextView"); + gtk_table_attach (GTK_TABLE (table), label, + 2, 3, 0, 1, GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0); + gtk_widget_show (label); + vbox = gtk_vbox_new (FALSE, 1); for (i = 0; i < 80; i++) { @@ -55,6 +73,46 @@ kinetic_scrolling (void) 0, 1, 1, 2); gtk_widget_show (swindow); + treeview = gtk_tree_view_new (); + gtk_tree_view_enable_model_drag_source (GTK_TREE_VIEW (treeview), + GDK_BUTTON1_MASK, + row_targets, + G_N_ELEMENTS (row_targets), + GDK_ACTION_MOVE | GDK_ACTION_COPY); + gtk_tree_view_enable_model_drag_dest (GTK_TREE_VIEW (treeview), + row_targets, + G_N_ELEMENTS (row_targets), + GDK_ACTION_MOVE | GDK_ACTION_COPY); + + renderer = gtk_cell_renderer_text_new (); + g_object_set (renderer, "editable", TRUE, NULL); + gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (treeview), + 0, "Title", + renderer, + "text", 0, + NULL); + store = gtk_list_store_new (1, G_TYPE_STRING); + for (i = 0; i < 80; i++) + { + GtkTreeIter iter; + gchar *label = g_strdup_printf ("Row number %d", i); + + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, 0, label, -1); + g_free (label); + } + gtk_tree_view_set_model (GTK_TREE_VIEW (treeview), GTK_TREE_MODEL (store)); + g_object_unref (store); + + swindow = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_kinetic_scrolling (GTK_SCROLLED_WINDOW (swindow), TRUE); + gtk_container_add (GTK_CONTAINER (swindow), treeview); + gtk_widget_show (treeview); + + gtk_table_attach_defaults (GTK_TABLE (table), swindow, + 1, 2, 1, 2); + gtk_widget_show (swindow); + textview = gtk_text_view_new (); swindow = gtk_scrolled_window_new (NULL, NULL); gtk_scrolled_window_set_kinetic_scrolling (GTK_SCROLLED_WINDOW (swindow), TRUE); @@ -62,7 +120,7 @@ kinetic_scrolling (void) gtk_widget_show (textview); gtk_table_attach_defaults (GTK_TABLE (table), swindow, - 1, 2, 1, 2); + 2, 3, 1, 2); gtk_widget_show (swindow); gtk_container_add (GTK_CONTAINER (window), table);