From 06c34ce02f7b9b549a97082bd7e7e160767f16d7 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Thu, 4 Jun 2020 21:35:53 -0400 Subject: [PATCH 1/4] set: Add a few useful functions Allow to find out if a set is empty, and its min and max. --- gtk/gtkset.c | 32 ++++++++++++++++++++++++++++++++ gtk/gtkset.h | 4 ++++ 2 files changed, 36 insertions(+) diff --git a/gtk/gtkset.c b/gtk/gtkset.c index 69f1dea72e..b4e0ae7627 100644 --- a/gtk/gtkset.c +++ b/gtk/gtkset.c @@ -335,6 +335,38 @@ next_range: return TRUE; } +gboolean +gtk_set_is_empty (GtkSet *set) +{ + return set->ranges->len == 0; +} + +guint +gtk_set_get_min (GtkSet *set) +{ + Range *r; + + if (gtk_set_is_empty (set)) + return 0; + + r = &g_array_index (set->ranges, Range, 0); + + return r->first; +} + +guint +gtk_set_get_max (GtkSet *set) +{ + Range *r; + + if (gtk_set_is_empty (set)) + return 0; + + r = &g_array_index (set->ranges, Range, set->ranges->len - 1); + + return r->first + r->n_items; +} + #if 0 void gtk_set_dump (GtkSet *set) diff --git a/gtk/gtkset.h b/gtk/gtkset.h index d0ba4e1a96..1ab6c6d1a2 100644 --- a/gtk/gtkset.h +++ b/gtk/gtkset.h @@ -67,4 +67,8 @@ void gtk_set_iter_init (GtkSetIter *iter, gboolean gtk_set_iter_next (GtkSetIter *iter, guint *item); +gboolean gtk_set_is_empty (GtkSet *set); +guint gtk_set_get_min (GtkSet *set); +guint gtk_set_get_max (GtkSet *set); + #endif /* __GTK_SET_H__ */ From 20611cf68c6a95765bd0c1a87f8e66423ee56f9f Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Thu, 4 Jun 2020 21:33:44 -0400 Subject: [PATCH 2/4] Add gtk_selection_model_[un]select_callback Add a methods to add or remove a whole set (specified via a query-range style callback). --- gtk/gtkmultiselection.c | 57 +++++++++++++++++++++++++++++++++++++++++ gtk/gtkselectionmodel.c | 54 ++++++++++++++++++++++++++++++++------ gtk/gtkselectionmodel.h | 23 ++++++++++++++++- 3 files changed, 125 insertions(+), 9 deletions(-) diff --git a/gtk/gtkmultiselection.c b/gtk/gtkmultiselection.c index f8ed44c2d4..a0ffb92fee 100644 --- a/gtk/gtkmultiselection.c +++ b/gtk/gtkmultiselection.c @@ -167,6 +167,61 @@ gtk_multi_selection_unselect_all (GtkSelectionModel *model) return gtk_multi_selection_unselect_range (model, 0, g_list_model_get_n_items (G_LIST_MODEL (model))); } +static gboolean +gtk_multi_selection_add_or_remove (GtkSelectionModel *model, + gboolean add, + GtkSelectionCallback callback, + gpointer data) +{ + GtkMultiSelection *self = GTK_MULTI_SELECTION (model); + guint pos, start, n; + gboolean in; + guint min, max; + + min = G_MAXUINT; + max = 0; + + pos = 0; + do + { + callback (pos, &start, &n, &in, data); + if (in) + { + if (start < min) + min = start; + if (start + n - 1 > max) + max = start + n - 1; + + if (add) + gtk_set_add_range (self->selected, start, n); + else + gtk_set_remove_range (self->selected, start, n); + } + pos = start + n; + } + while (n > 0); + + gtk_selection_model_selection_changed (model, min, max - min + 1); + + return TRUE; +} + +static gboolean +gtk_multi_selection_select_callback (GtkSelectionModel *model, + GtkSelectionCallback callback, + gpointer data) +{ + return gtk_multi_selection_add_or_remove (model, TRUE, callback, data); +} + +static gboolean +gtk_multi_selection_unselect_callback (GtkSelectionModel *model, + GtkSelectionCallback callback, + gpointer data) +{ + return gtk_multi_selection_add_or_remove (model, FALSE, callback, data); +} + static void gtk_multi_selection_query_range (GtkSelectionModel *model, guint position, @@ -190,6 +245,8 @@ gtk_multi_selection_selection_model_init (GtkSelectionModelInterface *iface) iface->unselect_range = gtk_multi_selection_unselect_range; iface->select_all = gtk_multi_selection_select_all; iface->unselect_all = gtk_multi_selection_unselect_all; + iface->select_callback = gtk_multi_selection_select_callback; + iface->unselect_callback = gtk_multi_selection_unselect_callback; iface->query_range = gtk_multi_selection_query_range; } diff --git a/gtk/gtkselectionmodel.c b/gtk/gtkselectionmodel.c index 8053368c6b..dfbf167f9a 100644 --- a/gtk/gtkselectionmodel.c +++ b/gtk/gtkselectionmodel.c @@ -113,6 +113,22 @@ gtk_selection_model_default_unselect_range (GtkSelectionModel *model, return FALSE; } +static gboolean +gtk_selection_model_default_select_callback (GtkSelectionModel *model, + GtkSelectionCallback callback, + gpointer data) +{ + return FALSE; +} + +static gboolean +gtk_selection_model_default_unselect_callback (GtkSelectionModel *model, + GtkSelectionCallback callback, + gpointer data) +{ + return FALSE; +} + static gboolean gtk_selection_model_default_select_all (GtkSelectionModel *model) { @@ -142,20 +158,22 @@ gtk_selection_model_default_query_range (GtkSelectionModel *model, else { *n_items = 1; - *selected = gtk_selection_model_is_selected (model, position); + *selected = gtk_selection_model_is_selected (model, position); } } static void gtk_selection_model_default_init (GtkSelectionModelInterface *iface) { - iface->is_selected = gtk_selection_model_default_is_selected; - iface->select_item = gtk_selection_model_default_select_item; - iface->unselect_item = gtk_selection_model_default_unselect_item; - iface->select_range = gtk_selection_model_default_select_range; - iface->unselect_range = gtk_selection_model_default_unselect_range; - iface->select_all = gtk_selection_model_default_select_all; - iface->unselect_all = gtk_selection_model_default_unselect_all; + iface->is_selected = gtk_selection_model_default_is_selected; + iface->select_item = gtk_selection_model_default_select_item; + iface->unselect_item = gtk_selection_model_default_unselect_item; + iface->select_range = gtk_selection_model_default_select_range; + iface->unselect_range = gtk_selection_model_default_unselect_range; + iface->select_all = gtk_selection_model_default_select_all; + iface->unselect_all = gtk_selection_model_default_unselect_all; + iface->select_callback = gtk_selection_model_default_select_callback; + iface->unselect_callback = gtk_selection_model_default_unselect_callback; iface->query_range = gtk_selection_model_default_query_range; /** @@ -324,6 +342,26 @@ gtk_selection_model_unselect_all (GtkSelectionModel *model) return iface->unselect_all (model); } +gboolean +gtk_selection_model_select_callback (GtkSelectionModel *model, + GtkSelectionCallback callback, + gpointer data) +{ + g_return_val_if_fail (GTK_IS_SELECTION_MODEL (model), FALSE); + + return GTK_SELECTION_MODEL_GET_IFACE (model)->select_callback (model, callback, data); +} + +gboolean +gtk_selection_model_unselect_callback (GtkSelectionModel *model, + GtkSelectionCallback callback, + gpointer data) +{ + g_return_val_if_fail (GTK_IS_SELECTION_MODEL (model), FALSE); + + return GTK_SELECTION_MODEL_GET_IFACE (model)->unselect_callback (model, callback, data); +} + /** * gtk_selection_model_query_range: * @model: a #GtkSelectionModel diff --git a/gtk/gtkselectionmodel.h b/gtk/gtkselectionmodel.h index 9e8de6a66b..3a73572ac2 100644 --- a/gtk/gtkselectionmodel.h +++ b/gtk/gtkselectionmodel.h @@ -29,10 +29,17 @@ G_BEGIN_DECLS #define GTK_TYPE_SELECTION_MODEL (gtk_selection_model_get_type ()) - + GDK_AVAILABLE_IN_ALL G_DECLARE_INTERFACE (GtkSelectionModel, gtk_selection_model, GTK, SELECTION_MODEL, GListModel) +typedef void (* GtkSelectionCallback) (guint position, + guint *start_range, + guint *n_items, + gboolean *selected, + gpointer data); + + /** * GtkSelectionModelInterface: * @is_selected: Return if the item at the given position is selected. @@ -79,6 +86,12 @@ struct _GtkSelectionModelInterface guint n_items); gboolean (* select_all) (GtkSelectionModel *model); gboolean (* unselect_all) (GtkSelectionModel *model); + gboolean (* select_callback) (GtkSelectionModel *model, + GtkSelectionCallback callback, + gpointer data); + gboolean (* unselect_callback) (GtkSelectionModel *model, + GtkSelectionCallback callback, + gpointer data); void (* query_range) (GtkSelectionModel *model, guint position, guint *start_range, @@ -111,6 +124,14 @@ gboolean gtk_selection_model_select_all (GtkSelectionMod GDK_AVAILABLE_IN_ALL gboolean gtk_selection_model_unselect_all (GtkSelectionModel *model); +GDK_AVAILABLE_IN_ALL +gboolean gtk_selection_model_select_callback (GtkSelectionModel *model, + GtkSelectionCallback callback, + gpointer data); +gboolean gtk_selection_model_unselect_callback (GtkSelectionModel *model, + GtkSelectionCallback callback, + gpointer data); + GDK_AVAILABLE_IN_ALL void gtk_selection_model_query_range (GtkSelectionModel *model, guint position, From eeb2d2cc38b2306505e1d61340cbb716cb5c2f14 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Thu, 4 Jun 2020 22:30:47 -0400 Subject: [PATCH 3/4] listbase: Redo rubberbanding Make it so that the selection is only updated in the end. --- gtk/gtklistbase.c | 95 ++++++++++++++++++++++++++--------------------- 1 file changed, 52 insertions(+), 43 deletions(-) diff --git a/gtk/gtklistbase.c b/gtk/gtklistbase.c index 7f8bf34ba5..373ba704e3 100644 --- a/gtk/gtklistbase.c +++ b/gtk/gtklistbase.c @@ -34,13 +34,13 @@ #include "gtksnapshot.h" #include "gtkmultiselection.h" #include "gtkgizmoprivate.h" +#include "gtkset.h" typedef struct _RubberbandData RubberbandData; struct _RubberbandData { GtkWidget *widget; - GtkSelectionModel *selection; double x1, y1; double x2, y2; gboolean modify; @@ -53,7 +53,6 @@ rubberband_data_free (gpointer data) RubberbandData *rdata = data; g_clear_pointer (&rdata->widget, gtk_widget_unparent); - g_clear_object (&rdata->selection); g_free (rdata); } @@ -1324,7 +1323,6 @@ gtk_list_base_start_rubberband (GtkListBase *self, { GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self); double value_x, value_y; - GtkSelectionModel *selection; if (priv->rubberband) return; @@ -1343,24 +1341,66 @@ gtk_list_base_start_rubberband (GtkListBase *self, priv->rubberband->widget = gtk_gizmo_new ("rubberband", NULL, NULL, NULL, NULL, NULL, NULL); gtk_widget_set_parent (priv->rubberband->widget, GTK_WIDGET (self)); +} - selection = gtk_list_item_manager_get_model (priv->item_manager); - if ((modify || extend) && - GTK_IS_MULTI_SELECTION (selection)) - priv->rubberband->selection = GTK_SELECTION_MODEL (gtk_multi_selection_copy (selection)); +static void +range_cb (guint position, + guint *start, + guint *n_items, + gboolean *selected, + gpointer data) +{ + GtkSet *set = data; - if (!modify && !extend) - gtk_selection_model_unselect_all (selection); + gtk_set_find_range (set, position, gtk_set_get_max (set), start, n_items, selected); } static void gtk_list_base_stop_rubberband (GtkListBase *self) { GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self); + GtkListItemManagerItem *item; + GtkSelectionModel *model; + GtkSet *active; if (!priv->rubberband) return; + active = gtk_set_new (); + + for (item = gtk_list_item_manager_get_first (priv->item_manager); + item != NULL; + item = gtk_rb_tree_node_get_next (item)) + { + if (!item->widget) + continue; + + if (gtk_widget_get_state_flags (item->widget) & GTK_STATE_FLAG_ACTIVE) + { + guint pos; + + pos = gtk_list_item_manager_get_item_position (priv->item_manager, item); + gtk_set_add_item (active, pos); + gtk_widget_unset_state_flags (item->widget, GTK_STATE_FLAG_ACTIVE); + } + } + + model = gtk_list_item_manager_get_model (priv->item_manager); + + if (priv->rubberband->modify) + { + gtk_selection_model_unselect_callback (model, range_cb, active); + } + else + { + if (!priv->rubberband->extend) + gtk_selection_model_unselect_all (model); + + gtk_selection_model_select_callback (model, range_cb, active); + } + + gtk_set_free (active); + g_clear_pointer (&priv->rubberband, rubberband_data_free); remove_autoscroll (self); @@ -1423,55 +1463,24 @@ gtk_list_base_update_rubberband_selection (GtkListBase *self) GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self); GdkRectangle rect; GdkRectangle alloc; - GtkSelectionModel *model; GtkListItemManagerItem *item; gtk_list_base_allocate_rubberband (self); gtk_widget_get_allocation (priv->rubberband->widget, &rect); - 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 selected; - gboolean was_selected; - if (!item->widget) continue; - pos = gtk_list_item_manager_get_item_position (priv->item_manager, item); - gtk_widget_get_allocation (item->widget, &alloc); - if (priv->rubberband->selection) - was_selected = gtk_selection_model_is_selected (priv->rubberband->selection, pos); + if (gdk_rectangle_intersect (&rect, &alloc, &alloc)) + gtk_widget_set_state_flags (item->widget, GTK_STATE_FLAG_ACTIVE, FALSE); else - was_selected = FALSE; - - selected = gdk_rectangle_intersect (&rect, &alloc, &alloc); - - if (priv->rubberband->modify) - { - if (was_selected) - { - if (selected) - gtk_selection_model_unselect_item (model, pos); - else - gtk_selection_model_select_item (model, pos, FALSE); - } - else - gtk_selection_model_unselect_item (model, pos); - } - else - { - if (selected || was_selected) - gtk_selection_model_select_item (model, pos, FALSE); - else - gtk_selection_model_unselect_item (model, pos); - } + gtk_widget_unset_state_flags (item->widget, GTK_STATE_FLAG_ACTIVE); } } From 843bf23f848c518471a7261fd8bc3038fc24e292 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Fri, 5 Jun 2020 00:15:09 -0400 Subject: [PATCH 4/4] Drop gtk_multi_selection-copy It is not used anymore. --- gtk/gtkmultiselection.c | 48 ----------------------------------------- gtk/gtkmultiselection.h | 2 -- 2 files changed, 50 deletions(-) diff --git a/gtk/gtkmultiselection.c b/gtk/gtkmultiselection.c index a0ffb92fee..00d02998d5 100644 --- a/gtk/gtkmultiselection.c +++ b/gtk/gtkmultiselection.c @@ -387,51 +387,3 @@ gtk_multi_selection_new (GListModel *model) "model", model, NULL); } - -/** - * gtk_multi_selection_copy: - * @selection: the #GtkSelectionModel to copy - * - * Creates a #GtkMultiSelection that has the same underlying - * model and the same selected items as @selection. - * - * Returns: (transfer full): a new #GtkMultiSelection - */ -GtkMultiSelection * -gtk_multi_selection_copy (GtkSelectionModel *selection) -{ - GtkMultiSelection *copy; - GListModel *model; - - g_object_get (selection, "model", &model, NULL); - - copy = GTK_MULTI_SELECTION (gtk_multi_selection_new (model)); - - if (GTK_IS_MULTI_SELECTION (selection)) - { - GtkMultiSelection *multi = GTK_MULTI_SELECTION (selection); - - gtk_set_free (copy->selected); - copy->selected = gtk_set_copy (multi->selected); - copy->last_selected = multi->last_selected; - } - else - { - guint pos, n; - guint start, n_items; - gboolean selected; - - n = g_list_model_get_n_items (model); - n_items = 0; - for (pos = 0; pos < n; pos += n_items) - { - gtk_selection_model_query_range (selection, pos, &start, &n_items, &selected); - if (selected) - gtk_selection_model_select_range (GTK_SELECTION_MODEL (copy), start, n_items, FALSE); - } - } - - g_object_unref (model); - - return copy; -} diff --git a/gtk/gtkmultiselection.h b/gtk/gtkmultiselection.h index e4147c2a66..2045e858ed 100644 --- a/gtk/gtkmultiselection.h +++ b/gtk/gtkmultiselection.h @@ -33,8 +33,6 @@ G_DECLARE_FINAL_TYPE (GtkMultiSelection, gtk_multi_selection, GTK, MULTI_SELECTI GDK_AVAILABLE_IN_ALL GListModel * gtk_multi_selection_new (GListModel *model); -GDK_AVAILABLE_IN_ALL -GtkMultiSelection * gtk_multi_selection_copy (GtkSelectionModel *selection); G_END_DECLS