[gtk/wip/otte/for-master: 2/10] selectionmodels: Add set_model() support



commit 795d3122cc53897cbc25cbfc64a1e15e806f5ce2
Author: Benjamin Otte <otte redhat com>
Date:   Sun Jul 5 01:08:12 2020 +0200

    selectionmodels: Add set_model() support
    
    Now that we don't care about item types anymore, we can make the child
    models settable.
    
    We try to retain the selection, even when the model changes.

 docs/reference/gtk/gtk4-sections.txt |  3 ++
 gtk/gtkmultiselection.c              | 63 ++++++++++++++++++++++++++++----
 gtk/gtkmultiselection.h              |  3 ++
 gtk/gtknoselection.c                 | 58 ++++++++++++++++++++++++-----
 gtk/gtknoselection.h                 |  3 ++
 gtk/gtksingleselection.c             | 71 ++++++++++++++++++++++++++++++++----
 gtk/gtksingleselection.h             | 23 +++++++-----
 testsuite/gtk/multiselection.c       | 54 +++++++++++++++++++++++++++
 testsuite/gtk/singleselection.c      | 52 ++++++++++++++++++++++++++
 9 files changed, 296 insertions(+), 34 deletions(-)
---
diff --git a/docs/reference/gtk/gtk4-sections.txt b/docs/reference/gtk/gtk4-sections.txt
index 15bdac5bc3..b02457dca8 100644
--- a/docs/reference/gtk/gtk4-sections.txt
+++ b/docs/reference/gtk/gtk4-sections.txt
@@ -422,6 +422,7 @@ gtk_selection_model_get_type
 GtkNoSelection
 gtk_no_selection_new
 gtk_no_selection_get_model
+gtk_no_selection_set_model
 <SUBSECTION Private>
 gtk_no_selection_get_type
 </SECTION>
@@ -433,6 +434,7 @@ GtkSingleSelection
 GTK_INVALID_LIST_POSITION
 gtk_single_selection_new
 gtk_single_selection_get_model
+gtk_single_selection_set_model
 gtk_single_selection_get_selected
 gtk_single_selection_set_selected
 gtk_single_selection_get_selected_item
@@ -450,6 +452,7 @@ gtk_single_selection_get_type
 GtkMultiSelection
 gtk_multi_selection_new
 gtk_multi_selection_get_model
+gtk_multi_selection_set_model
 <SUBSECTION Private>
 gtk_multi_selection_get_type
 </SECTION>
diff --git a/gtk/gtkmultiselection.c b/gtk/gtkmultiselection.c
index ad6d3857de..4dd93b5234 100644
--- a/gtk/gtkmultiselection.c
+++ b/gtk/gtkmultiselection.c
@@ -70,6 +70,9 @@ gtk_multi_selection_get_n_items (GListModel *list)
 {
   GtkMultiSelection *self = GTK_MULTI_SELECTION (list);
 
+  if (self->model == NULL)
+    return 0;
+
   return g_list_model_get_n_items (self->model);
 }
 
@@ -79,6 +82,9 @@ gtk_multi_selection_get_item (GListModel *list,
 {
   GtkMultiSelection *self = GTK_MULTI_SELECTION (list);
 
+  if (self->model == NULL)
+    return NULL;
+
   return g_list_model_get_item (self->model, position);
 }
 
@@ -172,7 +178,7 @@ gtk_multi_selection_set_selection (GtkSelectionModel *model,
   max = gtk_bitset_get_maximum (changes);
 
   /* sanity check */
-  n_items = g_list_model_get_n_items (self->model);
+  n_items = self->model ? g_list_model_get_n_items (self->model) : 0;
   if (max >= n_items)
     {
       gtk_bitset_remove_range_closed (changes, n_items, max);
@@ -289,12 +295,7 @@ gtk_multi_selection_set_property (GObject      *object,
   switch (prop_id)
     {
     case PROP_MODEL:
-      self->model = g_value_dup_object (value);
-      g_warn_if_fail (self->model != NULL);
-      g_signal_connect (self->model,
-                        "items-changed",
-                        G_CALLBACK (gtk_multi_selection_items_changed_cb),
-                        self);
+      gtk_multi_selection_set_model (self, g_value_get_object (value));
       break;
 
     default:
@@ -355,7 +356,7 @@ gtk_multi_selection_class_init (GtkMultiSelectionClass *klass)
                          P_("Model"),
                          P_("List managed by this selection"),
                          G_TYPE_LIST_MODEL,
-                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY | 
G_PARAM_STATIC_STRINGS);
+                         G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
 
   g_object_class_install_properties (gobject_class, N_PROPS, properties);
 }
@@ -400,3 +401,49 @@ gtk_multi_selection_get_model (GtkMultiSelection *self)
 
   return self->model;
 }
+
+/**
+ * gtk_multi_selection_set_model:
+ * @self: a #GtkMultiSelection
+ * @model: (allow-none): A #GListModel to wrap
+ *
+ * Sets the model that @self should wrap. If @model is %NULL, @self
+ * will be empty.
+ **/
+void
+gtk_multi_selection_set_model (GtkMultiSelection *self,
+                               GListModel        *model)
+{
+  guint n_items_before;
+
+  g_return_if_fail (GTK_IS_MULTI_SELECTION (self));
+  g_return_if_fail (model == NULL || G_IS_LIST_MODEL (model));
+
+  if (self->model == model)
+    return;
+
+  n_items_before = self->model ? g_list_model_get_n_items (self->model) : 0;
+  gtk_multi_selection_clear_model (self);
+
+  if (model)
+    {
+      self->model = g_object_ref (model);
+      g_signal_connect (self->model,
+                        "items-changed",
+                        G_CALLBACK (gtk_multi_selection_items_changed_cb),
+                        self);
+      gtk_multi_selection_items_changed_cb (self->model,
+                                            0,
+                                            n_items_before,
+                                            g_list_model_get_n_items (model),
+                                            self);
+    }
+  else
+    {
+      gtk_bitset_remove_all (self->selected);
+      g_hash_table_remove_all (self->items);
+      g_list_model_items_changed (G_LIST_MODEL (self), 0, n_items_before, 0);
+    }
+
+  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MODEL]);
+}
diff --git a/gtk/gtkmultiselection.h b/gtk/gtkmultiselection.h
index 92074683c0..69c8a69028 100644
--- a/gtk/gtkmultiselection.h
+++ b/gtk/gtkmultiselection.h
@@ -35,6 +35,9 @@ GListModel *    gtk_multi_selection_new                (GListModel           *mo
 
 GDK_AVAILABLE_IN_ALL
 GListModel *    gtk_multi_selection_get_model          (GtkMultiSelection    *self);
+GDK_AVAILABLE_IN_ALL
+void            gtk_multi_selection_set_model          (GtkMultiSelection    *self,
+                                                        GListModel           *model);
 
 G_END_DECLS
 
diff --git a/gtk/gtknoselection.c b/gtk/gtknoselection.c
index 652b148735..8027d003b2 100644
--- a/gtk/gtknoselection.c
+++ b/gtk/gtknoselection.c
@@ -68,15 +68,21 @@ gtk_no_selection_get_n_items (GListModel *list)
 {
   GtkNoSelection *self = GTK_NO_SELECTION (list);
 
+  if (self->model == NULL)
+    return 0;
+
   return g_list_model_get_n_items (self->model);
 }
 
 static gpointer
 gtk_no_selection_get_item (GListModel *list,
-                               guint       position)
+                           guint       position)
 {
   GtkNoSelection *self = GTK_NO_SELECTION (list);
 
+  if (self->model == NULL)
+    return NULL;
+
   return g_list_model_get_item (self->model, position);
 }
 
@@ -130,9 +136,9 @@ gtk_no_selection_clear_model (GtkNoSelection *self)
 
 static void
 gtk_no_selection_set_property (GObject      *object,
-                                   guint         prop_id,
-                                   const GValue *value,
-                                   GParamSpec   *pspec)
+                               guint         prop_id,
+                               const GValue *value,
+                               GParamSpec   *pspec)
 
 {
   GtkNoSelection *self = GTK_NO_SELECTION (object);
@@ -140,10 +146,7 @@ gtk_no_selection_set_property (GObject      *object,
   switch (prop_id)
     {
     case PROP_MODEL:
-      gtk_no_selection_clear_model (self);
-      self->model = g_value_dup_object (value);
-      g_signal_connect_swapped (self->model, "items-changed",
-                                G_CALLBACK (g_list_model_items_changed), self);
+      gtk_no_selection_set_model (self, g_value_get_object (value));
       break;
 
     default:
@@ -201,7 +204,7 @@ gtk_no_selection_class_init (GtkNoSelectionClass *klass)
                        P_("The model"),
                        P_("The model being managed"),
                        G_TYPE_LIST_MODEL,
-                       G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+                       G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
 
   g_object_class_install_properties (gobject_class, N_PROPS, properties);
 }
@@ -245,3 +248,40 @@ gtk_no_selection_get_model (GtkNoSelection *self)
   return self->model;
 }
 
+/**
+ * gtk_no_selection_set_model:
+ * @self: a #GtkNoSelection
+ * @model: (allow-none): A #GListModel to wrap
+ *
+ * Sets the model that @self should wrap. If @model is %NULL, this
+ * model will be empty.
+ **/
+void
+gtk_no_selection_set_model (GtkNoSelection *self,
+                            GListModel     *model)
+{
+  guint n_items_before;
+
+  g_return_if_fail (GTK_IS_NO_SELECTION (self));
+  g_return_if_fail (model == NULL || G_IS_LIST_MODEL (model));
+
+  if (self->model == model)
+    return;
+
+  n_items_before = self->model ? g_list_model_get_n_items (self->model) : 0;
+  gtk_no_selection_clear_model (self);
+
+  if (model)
+    {
+      self->model = g_object_ref (model);
+      g_signal_connect_swapped (self->model, "items-changed",
+                                G_CALLBACK (g_list_model_items_changed), self);
+    }
+
+  g_list_model_items_changed (G_LIST_MODEL (self),
+                              0,
+                              n_items_before,
+                              model ? g_list_model_get_n_items (self->model) : 0);
+
+  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MODEL]);
+}
diff --git a/gtk/gtknoselection.h b/gtk/gtknoselection.h
index e6341df409..09f027c9d9 100644
--- a/gtk/gtknoselection.h
+++ b/gtk/gtknoselection.h
@@ -34,6 +34,9 @@ GtkNoSelection *        gtk_no_selection_new                    (GListModel
 
 GDK_AVAILABLE_IN_ALL
 GListModel *            gtk_no_selection_get_model              (GtkNoSelection         *self);
+GDK_AVAILABLE_IN_ALL
+void                    gtk_no_selection_set_model              (GtkNoSelection         *self,
+                                                                 GListModel             *model);
 
 G_END_DECLS
 
diff --git a/gtk/gtksingleselection.c b/gtk/gtksingleselection.c
index 70f0beab27..209f6524e8 100644
--- a/gtk/gtksingleselection.c
+++ b/gtk/gtksingleselection.c
@@ -80,6 +80,9 @@ gtk_single_selection_get_n_items (GListModel *list)
 {
   GtkSingleSelection *self = GTK_SINGLE_SELECTION (list);
 
+  if (self->model == NULL)
+    return 0;
+
   return g_list_model_get_n_items (self->model);
 }
 
@@ -89,6 +92,9 @@ gtk_single_selection_get_item (GListModel *list,
 {
   GtkSingleSelection *self = GTK_SINGLE_SELECTION (list);
 
+  if (self->model == NULL)
+    return NULL;
+
   return g_list_model_get_item (self->model, position);
 }
 
@@ -305,12 +311,7 @@ gtk_single_selection_set_property (GObject      *object,
       break;
 
     case PROP_MODEL:
-      gtk_single_selection_clear_model (self);
-      self->model = g_value_dup_object (value);
-      g_signal_connect (self->model, "items-changed",
-                        G_CALLBACK (gtk_single_selection_items_changed_cb), self);
-      if (self->autoselect)
-        gtk_single_selection_set_selected (self, 0);
+      gtk_single_selection_set_model (self, g_value_get_object (value));
       break;
 
     case PROP_SELECTED:
@@ -438,7 +439,7 @@ gtk_single_selection_class_init (GtkSingleSelectionClass *klass)
                        P_("The model"),
                        P_("The model being managed"),
                        G_TYPE_LIST_MODEL,
-                       G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+                       G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
 
   g_object_class_install_properties (gobject_class, N_PROPS, properties);
 }
@@ -484,6 +485,62 @@ gtk_single_selection_get_model (GtkSingleSelection *self)
   return self->model;
 }
 
+/**
+ * gtk_single_selection_set_model:
+ * @self: a #GtkSingleSelection
+ * @model: (allow-none): A #GListModel to wrap
+ *
+ * Sets the model that @self should wrap. If @model is %NULL, @self
+ * will be empty.
+ **/
+void
+gtk_single_selection_set_model (GtkSingleSelection *self,
+                                GListModel         *model)
+{
+  guint n_items_before;
+
+  g_return_if_fail (GTK_IS_SINGLE_SELECTION (self));
+  g_return_if_fail (model == NULL || G_IS_LIST_MODEL (model));
+
+  if (self->model == model)
+    return;
+
+  g_object_freeze_notify (G_OBJECT (self));
+  
+  n_items_before = self->model ? g_list_model_get_n_items (self->model) : 0;
+  gtk_single_selection_clear_model (self);
+
+  if (model)
+    {
+      self->model = g_object_ref (model);
+      g_signal_connect (self->model, "items-changed",
+                        G_CALLBACK (gtk_single_selection_items_changed_cb), self);
+      gtk_single_selection_items_changed_cb (self->model,
+                                             0,
+                                             n_items_before,
+                                             g_list_model_get_n_items (model),
+                                             self);
+    }
+  else
+    {
+      if (self->selected != GTK_INVALID_LIST_POSITION)
+        {
+          self->selected = GTK_INVALID_LIST_POSITION;
+          g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SELECTED]);
+        }
+      if (self->selected_item)
+        {
+          g_clear_object (&self->selected_item);
+          g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SELECTED_ITEM]);
+        }
+      g_list_model_items_changed (G_LIST_MODEL (self), 0, n_items_before, 0);
+    }
+
+  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MODEL]);
+
+  g_object_thaw_notify (G_OBJECT (self));
+}
+
 /**
  * gtk_single_selection_get_selected:
  * @self: a #GtkSingleSelection
diff --git a/gtk/gtksingleselection.h b/gtk/gtksingleselection.h
index f9bbcae9e9..edfb1fe5c1 100644
--- a/gtk/gtksingleselection.h
+++ b/gtk/gtksingleselection.h
@@ -35,22 +35,25 @@ GtkSingleSelection *    gtk_single_selection_new                (GListModel
 GDK_AVAILABLE_IN_ALL
 GListModel *            gtk_single_selection_get_model          (GtkSingleSelection     *self);
 GDK_AVAILABLE_IN_ALL
-guint           gtk_single_selection_get_selected       (GtkSingleSelection     *self);
+void                    gtk_single_selection_set_model          (GtkSingleSelection     *self,
+                                                                 GListModel             *model);
 GDK_AVAILABLE_IN_ALL
-void            gtk_single_selection_set_selected       (GtkSingleSelection     *self,
-                                                         guint                   position);
+guint                   gtk_single_selection_get_selected       (GtkSingleSelection     *self);
 GDK_AVAILABLE_IN_ALL
-gpointer        gtk_single_selection_get_selected_item  (GtkSingleSelection     *self);
+void                    gtk_single_selection_set_selected       (GtkSingleSelection     *self,
+                                                                 guint                   position);
 GDK_AVAILABLE_IN_ALL
-gboolean        gtk_single_selection_get_autoselect     (GtkSingleSelection     *self);
+gpointer                gtk_single_selection_get_selected_item  (GtkSingleSelection     *self);
 GDK_AVAILABLE_IN_ALL
-void            gtk_single_selection_set_autoselect     (GtkSingleSelection     *self,
-                                                         gboolean                autoselect);
+gboolean                gtk_single_selection_get_autoselect     (GtkSingleSelection     *self);
 GDK_AVAILABLE_IN_ALL
-gboolean        gtk_single_selection_get_can_unselect   (GtkSingleSelection     *self);
+void                    gtk_single_selection_set_autoselect     (GtkSingleSelection     *self,
+                                                                 gboolean                autoselect);
 GDK_AVAILABLE_IN_ALL
-void            gtk_single_selection_set_can_unselect   (GtkSingleSelection     *self,
-                                                         gboolean                can_unselect);
+gboolean                gtk_single_selection_get_can_unselect   (GtkSingleSelection     *self);
+GDK_AVAILABLE_IN_ALL
+void                    gtk_single_selection_set_can_unselect   (GtkSingleSelection     *self,
+                                                                 gboolean                can_unselect);
 
 G_END_DECLS
 
diff --git a/testsuite/gtk/multiselection.c b/testsuite/gtk/multiselection.c
index 1fd32bce71..970a6c4a6a 100644
--- a/testsuite/gtk/multiselection.c
+++ b/testsuite/gtk/multiselection.c
@@ -613,6 +613,59 @@ test_selection_filter (void)
   g_object_unref (store);
   g_object_unref (selection);
 }
+
+static void
+test_set_model (void)
+{
+  GtkSelectionModel *selection;
+  GListStore *store;
+  GListModel *m1, *m2;
+  gboolean ret;
+  
+  store = new_store (1, 5, 1);
+  m1 = G_LIST_MODEL (store);
+  m2 = G_LIST_MODEL (gtk_slice_list_model_new (m1, 0, 3));
+  selection = new_model (store);
+  assert_selection (selection, "");
+  assert_selection_changes (selection, "");
+
+  ret = gtk_selection_model_select_range (selection, 1, 3, FALSE);
+  g_assert_true (ret);
+  assert_selection (selection, "2 3 4");
+  assert_selection_changes (selection, "1:3");
+
+  /* we retain the selected item across model changes */
+  gtk_multi_selection_set_model (GTK_MULTI_SELECTION (selection), m2);
+  assert_changes (selection, "0-5+3");
+  assert_selection (selection, "2 3");
+  assert_selection_changes (selection, "");
+
+  gtk_multi_selection_set_model (GTK_MULTI_SELECTION (selection), NULL);
+  assert_changes (selection, "0-3");
+  assert_selection (selection, "");
+  assert_selection_changes (selection, "");
+
+  gtk_multi_selection_set_model (GTK_MULTI_SELECTION (selection), m2);
+  assert_changes (selection, "0+3");
+  assert_selection (selection, "");
+  assert_selection_changes (selection, "");
+
+  ret = gtk_selection_model_select_all (selection);
+  g_assert_true (ret);
+  assert_selection (selection, "1 2 3");
+  assert_selection_changes (selection, "0:3");
+
+  /* we retain no selected item across model changes */
+  gtk_multi_selection_set_model (GTK_MULTI_SELECTION (selection), m1);
+  assert_changes (selection, "0-3+5");
+  assert_selection (selection, "1 2 3");
+  assert_selection_changes (selection, "");
+
+  g_object_unref (m2);
+  g_object_unref (m1);
+  g_object_unref (selection);
+}
+
 int
 main (int argc, char *argv[])
 {
@@ -633,6 +686,7 @@ main (int argc, char *argv[])
   g_test_add_func ("/multiselection/readd", test_readd);
   g_test_add_func ("/multiselection/set_selection", test_set_selection);
   g_test_add_func ("/multiselection/selection-filter", test_selection_filter);
+  g_test_add_func ("/multiselection/set-model", test_set_model);
 
   return g_test_run ();
 }
diff --git a/testsuite/gtk/singleselection.c b/testsuite/gtk/singleselection.c
index 81b96e90d0..90356345da 100644
--- a/testsuite/gtk/singleselection.c
+++ b/testsuite/gtk/singleselection.c
@@ -644,6 +644,57 @@ test_query_range (void)
   g_object_unref (selection);
 }
 
+static void
+test_set_model (void)
+{
+  GtkSelectionModel *selection;
+  GListStore *store;
+  GListModel *m1, *m2;
+  
+  store = new_store (1, 5, 1);
+  m1 = G_LIST_MODEL (store);
+  m2 = G_LIST_MODEL (gtk_slice_list_model_new (m1, 0, 3));
+  selection = new_model (store, TRUE, TRUE);
+  assert_selection (selection, "1");
+  assert_selection_changes (selection, "");
+
+  /* we retain the selected item across model changes */
+  gtk_single_selection_set_model (GTK_SINGLE_SELECTION (selection), m2);
+  assert_changes (selection, "0-5+3");
+  assert_selection (selection, "1");
+  assert_selection_changes (selection, "");
+
+  gtk_single_selection_set_model (GTK_SINGLE_SELECTION (selection), NULL);
+  assert_changes (selection, "0-3");
+  assert_selection (selection, "");
+  assert_selection_changes (selection, "");
+
+  gtk_single_selection_set_autoselect (GTK_SINGLE_SELECTION (selection), FALSE);
+  gtk_single_selection_set_model (GTK_SINGLE_SELECTION (selection), m2);
+  assert_changes (selection, "0+3");
+  assert_selection (selection, "");
+  assert_selection_changes (selection, "");
+
+  /* we retain no selected item across model changes */
+  gtk_single_selection_set_model (GTK_SINGLE_SELECTION (selection), m1);
+  assert_changes (selection, "0-3+5");
+  assert_selection (selection, "");
+  assert_selection_changes (selection, "");
+
+  gtk_single_selection_set_selected (GTK_SINGLE_SELECTION (selection), 4);
+  assert_selection (selection, "5");
+  assert_selection_changes (selection, "4:1");
+
+  gtk_single_selection_set_model (GTK_SINGLE_SELECTION (selection), m2);
+  assert_changes (selection, "0-5+3");
+  assert_selection (selection, "");
+  assert_selection_changes (selection, "");
+
+  g_object_unref (m2);
+  g_object_unref (m1);
+  g_object_unref (selection);
+}
+
 int
 main (int argc, char *argv[])
 {
@@ -662,6 +713,7 @@ main (int argc, char *argv[])
   g_test_add_func ("/singleselection/persistence", test_persistence);
   g_test_add_func ("/singleselection/query-range", test_query_range);
   g_test_add_func ("/singleselection/changes", test_changes);
+  g_test_add_func ("/singleselection/set-model", test_set_model);
 
   return g_test_run ();
 }


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]