From 437fa857f4dbf80f1297eb9cbadc757f113aaa5e Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Sun, 6 Jan 2019 20:26:10 -0500 Subject: [PATCH] multi-selection: Implement persistence This is not very efficient for Ctrl-A. No way around it. --- gtk/gtkmultiselection.c | 60 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 59 insertions(+), 1 deletion(-) diff --git a/gtk/gtkmultiselection.c b/gtk/gtkmultiselection.c index cede5266da..e9c963f0b4 100644 --- a/gtk/gtkmultiselection.c +++ b/gtk/gtkmultiselection.c @@ -46,6 +46,17 @@ struct _GtkMultiSelection guint last_selected; }; +/* + * We store a set of positions for selected items. This can be maintained + * efficiently as long as it consists of a small number of ranges. I + * degenerate cases such as 'every second item in the list', it will + * be O(|model|). + * + * To implement persistence across add/remove changes in the underlying + * model (for example, resorting), we mark the selected objects, which + * is also going to be O(|model|) in the 'select all' case. + */ + struct _GtkMultiSelectionClass { GObjectClass parent_class; @@ -102,6 +113,38 @@ gtk_multi_selection_is_selected (GtkSelectionModel *model, return gtk_set_contains (self->selected, position); } +static void +mark_selected (GtkMultiSelection *self, gboolean in) +{ + GtkSetIter iter; + guint pos; + + gtk_set_iter_init (&iter, self->selected); + while (gtk_set_iter_next (&iter, &pos)) + { + /* Mark the object as being selected in this multiselection. + * See gtk_multi_selection_items_changed_cb, where this is + * used to identify objects that were removed and readded. + */ + GObject *obj = g_list_model_get_item (self->model, pos); + g_object_set_data (obj, "GtkMultiSelection", in ? self : NULL); + g_object_unref (obj); + } +} + +static void +mark_range (GtkMultiSelection *self, guint first, guint n_items, gboolean in) +{ + guint pos; + + for (pos = first; pos < first + n_items; pos++) + { + GObject *obj = g_list_model_get_item (self->model, pos); + g_object_set_data (obj, "GtkMultiSelection", in ? self : NULL); + g_object_unref (obj); + } +} + static gboolean gtk_multi_selection_select_range (GtkSelectionModel *model, guint position, @@ -111,7 +154,11 @@ gtk_multi_selection_select_range (GtkSelectionModel *model, GtkMultiSelection *self = GTK_MULTI_SELECTION (model); if (exclusive) - gtk_set_remove_all (self->selected); + { + mark_selected (self, FALSE); + gtk_set_remove_all (self->selected); + } + mark_range (self, position, n_items, TRUE); gtk_set_add_range (self->selected, position, n_items); gtk_selection_model_selection_changed (model, position, n_items); @@ -125,6 +172,7 @@ gtk_multi_selection_unselect_range (GtkSelectionModel *model, { GtkMultiSelection *self = GTK_MULTI_SELECTION (model); + mark_range (self, position, n_items, FALSE); gtk_set_remove_range (self->selected, position, n_items); gtk_selection_model_selection_changed (model, position, n_items); @@ -202,8 +250,18 @@ gtk_multi_selection_items_changed_cb (GListModel *model, guint added, GtkMultiSelection *self) { + guint pos; + gtk_set_remove_range (self->selected, position, removed); gtk_set_shift (self->selected, position, (int)added - (int)removed); + for (pos = position; pos < position + added; pos++) + { + GObject *obj = g_list_model_get_item (self->model, pos); + if (g_object_get_data (obj, "GtkMultiSelection") == self) + gtk_set_add_item (self->selected, pos); + g_object_unref (obj); + } + g_list_model_items_changed (G_LIST_MODEL (self), position, removed, added); }