diff options
author | Matthias Clasen <mclasen@redhat.com> | 2019-12-21 19:07:11 -0500 |
---|---|---|
committer | Matthias Clasen <mclasen@redhat.com> | 2020-06-03 13:32:57 -0400 |
commit | a0f04bdcf368cd789de0407b181bd7c394e642e5 (patch) | |
tree | 35d44ca4beaa81817b00b4d0d0d135c53d7087d1 | |
parent | 28f6e2727635497b21ff098aeb7f88ab0404965e (diff) | |
download | gtk+-a0f04bdcf368cd789de0407b181bd7c394e642e5.tar.gz |
listbase: Add rubberband selection
Implement the typical rubberband selection, including
autoscroll. This is only useful with multiselection,
and not very compatible with single-click-activate.
Therefore, it is not enabled by default, and needs
to be turned on explicitly.
-rw-r--r-- | gtk/gtklistbase.c | 286 | ||||
-rw-r--r-- | gtk/gtklistbaseprivate.h | 3 | ||||
-rw-r--r-- | gtk/gtklistitemwidget.c | 10 |
3 files changed, 297 insertions, 2 deletions
diff --git a/gtk/gtklistbase.c b/gtk/gtklistbase.c index 8ec9bc8784..701e41e4a1 100644 --- a/gtk/gtklistbase.c +++ b/gtk/gtklistbase.c @@ -28,6 +28,12 @@ #include "gtkscrollable.h" #include "gtksingleselection.h" #include "gtktypebuiltins.h" +#include "gtkgesturedrag.h" +#include "gtkwidgetprivate.h" +#include "gtkcssnodeprivate.h" +#include "gtkstylecontextprivate.h" +#include "gtksnapshot.h" +#include "gtkmultiselection.h" typedef struct _GtkListBasePrivate GtkListBasePrivate; @@ -50,6 +56,18 @@ struct _GtkListBasePrivate GtkListItemTracker *selected; /* the item that has input focus */ GtkListItemTracker *focus; + + gboolean enable_rubberband; + gboolean doing_rubberband; + double rb_x1; + double rb_y1; + double rb_x2; + double rb_y2; + GtkGesture *drag_gesture; + GtkCssNode *rubberband_node; + GtkSelectionModel *old_selection; + gboolean modify; + gboolean extend; }; enum @@ -1061,6 +1079,20 @@ gtk_list_base_add_custom_move_binding (GtkWidgetClass *widget_class, "(bbb)", TRUE, TRUE, TRUE); } +static void gtk_list_base_snapshot_rubberband (GtkListBase *self, + GtkSnapshot *snapshot); + +static void +gtk_list_base_snapshot (GtkWidget *widget, + GtkSnapshot *snapshot) +{ + GtkListBase *self = GTK_LIST_BASE (widget); + + GTK_WIDGET_CLASS (gtk_list_base_parent_class)->snapshot (widget, snapshot); + + gtk_list_base_snapshot_rubberband (self, snapshot); +} + static void gtk_list_base_class_init (GtkListBaseClass *klass) { @@ -1069,6 +1101,7 @@ gtk_list_base_class_init (GtkListBaseClass *klass) gpointer iface; widget_class->focus = gtk_list_base_focus; + widget_class->snapshot = gtk_list_base_snapshot; gobject_class->dispose = gtk_list_base_dispose; gobject_class->get_property = gtk_list_base_get_property; @@ -1186,6 +1219,258 @@ gtk_list_base_class_init (GtkListBaseClass *klass) gtk_widget_class_add_binding_action (widget_class, GDK_KEY_backslash, GDK_CONTROL_MASK, "list.unselect-all", NULL); } +static void gtk_list_base_update_rubberband_selection (GtkListBase *self); + +static void +gtk_list_base_start_rubberband (GtkListBase *self, + double x, + double y, + gboolean modify, + gboolean extend) +{ + GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self); + GtkCssNode *widget_node; + double value_x, value_y; + GtkSelectionModel *selection; + + if (priv->doing_rubberband) + return; + + value_x = gtk_adjustment_get_value (priv->adjustment[GTK_ORIENTATION_HORIZONTAL]); + value_y = gtk_adjustment_get_value (priv->adjustment[GTK_ORIENTATION_VERTICAL]); + + priv->rb_x1 = priv->rb_x2 = x + value_x; + priv->rb_y1 = priv->rb_y2 = y + value_y; + + priv->modify = modify; + priv->extend = extend; + + widget_node = gtk_widget_get_css_node (GTK_WIDGET (self)); + priv->rubberband_node = gtk_css_node_new (); + gtk_css_node_set_name (priv->rubberband_node, + g_quark_from_static_string ("rubberband")); + gtk_css_node_set_parent (priv->rubberband_node, widget_node); + gtk_css_node_set_state (priv->rubberband_node, gtk_css_node_get_state (widget_node)); + g_object_unref (priv->rubberband_node); + + selection = gtk_list_item_manager_get_model (priv->item_manager); + + if (modify) + priv->old_selection = GTK_SELECTION_MODEL (gtk_multi_selection_copy (selection)); + + priv->doing_rubberband = TRUE; +} + +static void +gtk_list_base_stop_rubberband (GtkListBase *self) +{ + GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self); + + if (!priv->doing_rubberband) + return; + + priv->doing_rubberband = FALSE; + gtk_css_node_set_parent (priv->rubberband_node, NULL); + priv->rubberband_node = NULL; + + g_clear_object (&priv->old_selection); + + gtk_widget_queue_draw (GTK_WIDGET (self)); +} + +#define SCROLL_EDGE_SIZE 15 + +static void +gtk_list_base_update_rubberband (GtkListBase *self, + double x, + double y) +{ + GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self); + double value_x, value_y; + + if (!priv->doing_rubberband) + return; + + value_x = gtk_adjustment_get_value (priv->adjustment[GTK_ORIENTATION_HORIZONTAL]); + value_y = gtk_adjustment_get_value (priv->adjustment[GTK_ORIENTATION_VERTICAL]); + + priv->rb_x2 = x + value_x; + priv->rb_y2 = y + value_y; + + gtk_list_base_update_rubberband_selection (self); + + gtk_widget_queue_draw (GTK_WIDGET (self)); +} + +static void +gtk_list_base_update_rubberband_selection (GtkListBase *self) +{ + GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self); + GdkRectangle rect; + GdkRectangle alloc; + double value_x, value_y; + GtkSelectionModel *model; + GtkListItemManagerItem *item; + + value_x = gtk_adjustment_get_value (priv->adjustment[GTK_ORIENTATION_HORIZONTAL]); + value_y = gtk_adjustment_get_value (priv->adjustment[GTK_ORIENTATION_VERTICAL]); + + rect.x = MIN (priv->rb_x1, priv->rb_x2) - value_x; + rect.y = MIN (priv->rb_y1, priv->rb_y2) - value_y; + rect.width = ABS (priv->rb_x1 - priv->rb_x2) + 1; + rect.height = ABS (priv->rb_y1 - priv->rb_y2) + 1; + + model = gtk_list_item_manager_get_model (priv->item_manager); + + for (item = gtk_list_item_manager_get_first (priv->item_manager); + item != NULL; + item = gtk_rb_tree_node_get_next (item)) + { + guint pos; + gboolean was_selected, selected; + + if (!item->widget) + continue; + + pos = gtk_list_item_manager_get_item_position (priv->item_manager, item); + + gtk_widget_get_allocation (item->widget, &alloc); + + selected = gdk_rectangle_intersect (&rect, &alloc, &alloc); + + if (priv->modify) + { + was_selected = gtk_selection_model_is_selected (priv->old_selection, pos); + selected = selected ^ was_selected; + } + + if (selected) + gtk_selection_model_select_item (model, pos, FALSE); + else if (!priv->extend) + gtk_selection_model_unselect_item (model, pos); + } +} + +static void +gtk_list_base_snapshot_rubberband (GtkListBase *self, + GtkSnapshot *snapshot) +{ + GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self); + GtkStyleContext *context; + GdkRectangle rect; + double value_x, value_y; + + if (!priv->doing_rubberband) + return; + + value_x = gtk_adjustment_get_value (priv->adjustment[GTK_ORIENTATION_HORIZONTAL]); + value_y = gtk_adjustment_get_value (priv->adjustment[GTK_ORIENTATION_VERTICAL]); + + rect.x = MIN (priv->rb_x1, priv->rb_x2) - value_x; + rect.y = MIN (priv->rb_y1, priv->rb_y2) - value_y; + rect.width = ABS (priv->rb_x1 - priv->rb_x2) + 1; + rect.height = ABS (priv->rb_y1 - priv->rb_y2) + 1; + + context = gtk_widget_get_style_context (GTK_WIDGET (self)); + + gtk_style_context_save_to_node (context, priv->rubberband_node); + + gtk_snapshot_render_background (snapshot, context, rect.x, rect.y, rect.width, rect.height); + gtk_snapshot_render_frame (snapshot, context, rect.x, rect.y, rect.width, rect.height); + + gtk_style_context_restore (context); + +} + +static void +get_selection_modifiers (GtkGesture *gesture, + gboolean *modify, + gboolean *extend) +{ + GdkEventSequence *sequence; + GdkEvent *event; + GdkModifierType state; + + *modify = FALSE; + *extend = FALSE; + + sequence = gtk_gesture_get_last_updated_sequence (gesture); + event = gtk_gesture_get_last_event (gesture, sequence); + state = gdk_event_get_modifier_state (event); + if ((state & GDK_CONTROL_MASK) == GDK_CONTROL_MASK) + *modify = TRUE; + if ((state & GDK_SHIFT_MASK) == GDK_SHIFT_MASK) + *extend = TRUE; +} + +static void +gtk_list_base_drag_begin (GtkGestureDrag *gesture, + double start_x, + double start_y, + GtkListBase *self) +{ + gboolean modify; + gboolean extend; + + get_selection_modifiers (GTK_GESTURE (gesture), &modify, &extend); + gtk_list_base_start_rubberband (self, start_x, start_y, modify, extend); +} + +static void +gtk_list_base_drag_update (GtkGestureDrag *gesture, + double offset_x, + double offset_y, + GtkListBase *self) +{ + double start_x, start_y; + gtk_gesture_drag_get_start_point (gesture, &start_x, &start_y); + gtk_list_base_update_rubberband (self, start_x + offset_x, start_y + offset_y); +} + +static void +gtk_list_base_drag_end (GtkGestureDrag *gesture, + double offset_x, + double offset_y, + GtkListBase *self) +{ + gtk_list_base_drag_update (gesture, offset_x, offset_y, self); + gtk_list_base_stop_rubberband (self); +} + +void +gtk_list_base_set_enable_rubberband (GtkListBase *self, + gboolean enable) +{ + GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self); + + if (priv->enable_rubberband == enable) + return; + + priv->enable_rubberband = enable; + + if (enable) + { + priv->drag_gesture = gtk_gesture_drag_new (); + g_signal_connect (priv->drag_gesture, "drag-begin", G_CALLBACK (gtk_list_base_drag_begin), self); + g_signal_connect (priv->drag_gesture, "drag-update", G_CALLBACK (gtk_list_base_drag_update), self); + g_signal_connect (priv->drag_gesture, "drag-end", G_CALLBACK (gtk_list_base_drag_end), self); + gtk_widget_add_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (priv->drag_gesture)); + } + else + { + gtk_widget_remove_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (priv->drag_gesture)); + priv->drag_gesture = NULL; + } +} + +gboolean +gtk_list_base_get_enable_rubberband (GtkListBase *self) +{ + GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self); + + return priv->enable_rubberband; +} + static void gtk_list_base_init_real (GtkListBase *self, GtkListBaseClass *g_class) @@ -1537,4 +1822,3 @@ gtk_list_base_set_model (GtkListBase *self, return TRUE; } - diff --git a/gtk/gtklistbaseprivate.h b/gtk/gtklistbaseprivate.h index 06757985d3..73d20151c8 100644 --- a/gtk/gtklistbaseprivate.h +++ b/gtk/gtklistbaseprivate.h @@ -99,5 +99,8 @@ gboolean gtk_list_base_grab_focus_on_item (GtkListBase gboolean select, gboolean modify, gboolean extend); +void gtk_list_base_set_enable_rubberband (GtkListBase *self, + gboolean enable); +gboolean gtk_list_base_get_enable_rubberband (GtkListBase *self); #endif /* __GTK_LIST_BASE_PRIVATE_H__ */ diff --git a/gtk/gtklistitemwidget.c b/gtk/gtklistitemwidget.c index 2e6078d704..84e51a166c 100644 --- a/gtk/gtklistitemwidget.c +++ b/gtk/gtklistitemwidget.c @@ -29,6 +29,7 @@ #include "gtkintl.h" #include "gtklistitemfactoryprivate.h" #include "gtklistitemprivate.h" +#include "gtklistbaseprivate.h" #include "gtkmain.h" #include "gtkselectionmodel.h" #include "gtkwidget.h" @@ -309,6 +310,8 @@ gtk_list_item_widget_click_gesture_pressed (GtkGestureClick *gesture, { GtkListItemWidgetPrivate *priv = gtk_list_item_widget_get_instance_private (self); GtkWidget *widget = GTK_WIDGET (self); + GtkWidget * parent = gtk_widget_get_parent (widget); + gboolean rubberband; if (priv->list_item && !priv->list_item->selectable && !priv->list_item->activatable) { @@ -316,7 +319,12 @@ gtk_list_item_widget_click_gesture_pressed (GtkGestureClick *gesture, return; } - if (!priv->list_item || priv->list_item->selectable) + if (GTK_IS_LIST_BASE (parent)) + rubberband = gtk_list_base_get_enable_rubberband (GTK_LIST_BASE (parent)); + else + rubberband = FALSE; + + if (!rubberband && (!priv->list_item || priv->list_item->selectable)) { GdkModifierType state; GdkEvent *event; |