[libdazzle] util: add read-only GListModel wrapper



commit 5c6200aab4c76f022803ec4d2cf4cabfbc8e3265
Author: Christian Hergert <chergert redhat com>
Date:   Fri Aug 17 12:59:14 2018 -0700

    util: add read-only GListModel wrapper
    
    This allows passing a read-only wrapper around a GListModel that might
    otherwise be mutable if the consumer knows the type (such as GListStore).
    
    Using this, you can pass back your "internal" list model and be sure the
    consumer can't mutate it out from under you.

 src/dazzle.h                        |   1 +
 src/util/dzl-read-only-list-model.c | 208 ++++++++++++++++++++++++++++++++++++
 src/util/dzl-read-only-list-model.h |  35 ++++++
 src/util/meson.build                |   2 +
 tests/meson.build                   |   7 ++
 tests/test-read-only-list-model.c   |  87 +++++++++++++++
 6 files changed, 340 insertions(+)
---
diff --git a/src/dazzle.h b/src/dazzle.h
index 0e08b7a..4d0f694 100644
--- a/src/dazzle.h
+++ b/src/dazzle.h
@@ -140,6 +140,7 @@ G_BEGIN_DECLS
 #include "util/dzl-int-pair.h"
 #include "util/dzl-macros.h"
 #include "util/dzl-pango.h"
+#include "util/dzl-read-only-list-model.h"
 #include "util/dzl-rgba.h"
 #include "util/dzl-ring.h"
 #include "util/dzl-variant.h"
diff --git a/src/util/dzl-read-only-list-model.c b/src/util/dzl-read-only-list-model.c
new file mode 100644
index 0000000..48792b8
--- /dev/null
+++ b/src/util/dzl-read-only-list-model.c
@@ -0,0 +1,208 @@
+/* dzl-read-only-list-model.c
+ *
+ * Copyright © 2018 Christian Hergert <chergert redhat com>
+ *
+ * This file is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2.1 of the License, or (at your option)
+ * any later version.
+ *
+ * This file is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#define G_LOG_DOMAIN "dzl-read-only-list-model"
+
+#include "util/dzl-read-only-list-model.h"
+
+struct _DzlReadOnlyListModel
+{
+  GObject     parent_instance;
+  GListModel *base_model;
+};
+
+static GType
+dzl_read_only_list_model_get_item_type (GListModel *model)
+{
+  DzlReadOnlyListModel *self = (DzlReadOnlyListModel *)model;
+
+  g_assert (DZL_IS_READ_ONLY_LIST_MODEL (self));
+
+  if (self->base_model != NULL)
+    return g_list_model_get_item_type (self->base_model);
+
+  return G_TYPE_OBJECT;
+}
+
+static guint
+dzl_read_only_list_model_get_n_items (GListModel *model)
+{
+  DzlReadOnlyListModel *self = (DzlReadOnlyListModel *)model;
+
+  g_assert (DZL_IS_READ_ONLY_LIST_MODEL (self));
+
+  if (self->base_model != NULL)
+    return g_list_model_get_n_items (self->base_model);
+
+  return 0;
+}
+
+static gpointer
+dzl_read_only_list_model_get_item (GListModel *model,
+                                   guint       position)
+{
+  DzlReadOnlyListModel *self = (DzlReadOnlyListModel *)model;
+
+  g_assert (DZL_IS_READ_ONLY_LIST_MODEL (self));
+
+  if (self->base_model != NULL)
+    return g_list_model_get_item (self->base_model, position);
+
+  g_critical ("No item at position %u", position);
+
+  return NULL;
+}
+
+static void
+list_model_iface_init (GListModelInterface *iface)
+{
+  iface->get_n_items = dzl_read_only_list_model_get_n_items;
+  iface->get_item = dzl_read_only_list_model_get_item;
+  iface->get_item_type = dzl_read_only_list_model_get_item_type;
+}
+
+G_DEFINE_TYPE_WITH_CODE (DzlReadOnlyListModel, dzl_read_only_list_model, G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, list_model_iface_init))
+
+enum {
+  PROP_0,
+  PROP_BASE_MODEL,
+  N_PROPS
+};
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+dzl_read_only_list_model_items_changed_cb (DzlReadOnlyListModel *self,
+                                           guint                 position,
+                                           guint                 removed,
+                                           guint                 added,
+                                           GListModel           *base_model)
+{
+  g_assert (DZL_IS_READ_ONLY_LIST_MODEL (self));
+  g_assert (G_IS_LIST_MODEL (base_model));
+
+  g_list_model_items_changed (G_LIST_MODEL (self), position, removed, added);
+}
+
+static void
+dzl_read_only_list_model_set_base_model (DzlReadOnlyListModel *self,
+                                         GListModel           *base_model)
+{
+  g_assert (DZL_IS_READ_ONLY_LIST_MODEL (self));
+
+  if (base_model == NULL)
+    return;
+
+  self->base_model = g_object_ref (base_model);
+
+  g_signal_connect_object (self->base_model,
+                           "items-changed",
+                           G_CALLBACK (dzl_read_only_list_model_items_changed_cb),
+                           self,
+                           G_CONNECT_SWAPPED);
+}
+
+static void
+dzl_read_only_list_model_dispose (GObject *object)
+{
+  DzlReadOnlyListModel *self = (DzlReadOnlyListModel *)object;
+
+  g_clear_object (&self->base_model);
+
+  G_OBJECT_CLASS (dzl_read_only_list_model_parent_class)->dispose (object);
+}
+
+static void
+dzl_read_only_list_model_set_property (GObject      *object,
+                                       guint         prop_id,
+                                       const GValue *value,
+                                       GParamSpec   *pspec)
+{
+  DzlReadOnlyListModel *self = DZL_READ_ONLY_LIST_MODEL (object);
+
+  switch (prop_id)
+    {
+    case PROP_BASE_MODEL:
+      dzl_read_only_list_model_set_base_model (self, g_value_get_object (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+dzl_read_only_list_model_class_init (DzlReadOnlyListModelClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->dispose = dzl_read_only_list_model_dispose;
+  object_class->set_property = dzl_read_only_list_model_set_property;
+
+  /**
+   * DzlReadOnlyListModel:base-model:
+   *
+   * The "base-model" property is the #GListModel that will be wrapped.
+   *
+   * This base model is not accessible after creation so that API creators can
+   * be sure the consumer cannot mutate the underlying model. That is useful
+   * when you want to give a caller access to a #GListModel without the ability
+   * to introspect on the type and mutate it without your knowledge (such as
+   * with #GListStore).
+   *
+   * Since: 3.30
+   */
+  properties [PROP_BASE_MODEL] =
+    g_param_spec_object ("base-model",
+                         "Base Model",
+                         "The list model to be wrapped as read-only",
+                         G_TYPE_LIST_MODEL,
+                         (G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+  
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+dzl_read_only_list_model_init (DzlReadOnlyListModel *self)
+{
+}
+
+/**
+ * dzl_read_only_list_model_new:
+ * @base_model: a #GListModel
+ *
+ * Creates a new #DzlReadOnlyListModel which is a read-only wrapper around
+ * @base_model. This is useful when you want to give API consumers access to
+ * a #GListModel but without the ability to mutate the underlying list.
+ *
+ * Returns: (transfer full): a #DzlReadOnlyListModel
+ *
+ * Since: 3.30
+ */
+GListModel *
+dzl_read_only_list_model_new (GListModel *base_model)
+{
+  g_return_val_if_fail (G_IS_LIST_MODEL (base_model), NULL);
+
+  return g_object_new (DZL_TYPE_READ_ONLY_LIST_MODEL,
+                       "base-model", base_model,
+                       NULL);
+}
diff --git a/src/util/dzl-read-only-list-model.h b/src/util/dzl-read-only-list-model.h
new file mode 100644
index 0000000..3060307
--- /dev/null
+++ b/src/util/dzl-read-only-list-model.h
@@ -0,0 +1,35 @@
+/* dzl-read-only-list-model.h
+ *
+ * Copyright © 2018 Christian Hergert <chergert redhat com>
+ *
+ * This file is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or (at
+ * your option) any later version.
+ *
+ * This file is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <gio/gio.h>
+
+#include "dzl-version-macros.h"
+
+G_BEGIN_DECLS
+
+#define DZL_TYPE_READ_ONLY_LIST_MODEL (dzl_read_only_list_model_get_type())
+
+DZL_AVAILABLE_IN_3_30
+G_DECLARE_FINAL_TYPE (DzlReadOnlyListModel, dzl_read_only_list_model, DZL, READ_ONLY_LIST_MODEL, GObject)
+
+DZL_AVAILABLE_IN_3_30
+GListModel *dzl_read_only_list_model_new (GListModel *base_model);
+
+G_END_DECLS
diff --git a/src/util/meson.build b/src/util/meson.build
index f953b5f..bd4d0ba 100644
--- a/src/util/meson.build
+++ b/src/util/meson.build
@@ -10,6 +10,7 @@ util_headers = [
   'dzl-int-pair.h',
   'dzl-macros.h',
   'dzl-pango.h',
+  'dzl-read-only-list-model.h',
   'dzl-rgba.h',
   'dzl-ring.h',
   'dzl-variant.h',
@@ -25,6 +26,7 @@ util_sources = [
   'dzl-gtk.c',
   'dzl-heap.c',
   'dzl-pango.c',
+  'dzl-read-only-list-model.c',
   'dzl-rgba.c',
   'dzl-ring.c',
   'dzl-util.c',
diff --git a/tests/meson.build b/tests/meson.build
index 98ee87a..ae868e6 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -377,4 +377,11 @@ test_graph_model = executable('test-graph-model', 'test-graph-model.c',
 )
 test('test-graph-model', test_graph_model, env: test_env)
 
+test_read_only_list_model = executable('test-read-only-list-model', 'test-read-only-list-model.c',
+        c_args: test_cflags,
+     link_args: test_link_args,
+  dependencies: libdazzle_deps + [libdazzle_dep],
+)
+test('test-read-only-list-model', test_read_only_list_model, env: test_env)
+
 endif
diff --git a/tests/test-read-only-list-model.c b/tests/test-read-only-list-model.c
new file mode 100644
index 0000000..b7be67f
--- /dev/null
+++ b/tests/test-read-only-list-model.c
@@ -0,0 +1,87 @@
+/* test-read-only-list-model.c
+ *
+ * Copyright © 2018 Christian Hergert <chergert redhat com>
+ *
+ * This file is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or (at
+ * your option) any later version.
+ *
+ * This file is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <dazzle.h>
+
+static void
+on_items_changed_cb (GListModel *model,
+                     guint       position,
+                     guint       removed,
+                     guint       added,
+                     guint      *count)
+{
+  g_assert (DZL_IS_READ_ONLY_LIST_MODEL (model));
+  g_assert (added == 1);
+  g_assert (removed == 0);
+
+  (*count)++;
+}
+
+static void
+test_basic (void)
+{
+  g_autoptr(GListStore) store = g_list_store_new (G_TYPE_OBJECT);
+  g_autoptr(GListModel) wrapper = dzl_read_only_list_model_new (G_LIST_MODEL (store));
+  g_autoptr(GObject) obj1 = g_object_new (G_TYPE_OBJECT, NULL);
+  g_autoptr(GObject) obj2 = g_object_new (G_TYPE_OBJECT, NULL);
+  g_autoptr(GObject) obj3 = g_object_new (G_TYPE_OBJECT, NULL);
+  g_autoptr(GObject) obj4 = g_object_new (G_TYPE_OBJECT, NULL);
+  g_autoptr(GObject) obj5 = g_object_new (G_TYPE_OBJECT, NULL);
+  g_autoptr(GObject) read1 = NULL;
+  g_autoptr(GObject) read2 = NULL;
+  g_autoptr(GObject) read3 = NULL;
+  g_autoptr(GObject) read4 = NULL;
+  g_autoptr(GObject) read5 = NULL;
+  guint count = 0;
+
+  g_signal_connect (wrapper,
+                    "items-changed",
+                    G_CALLBACK (on_items_changed_cb),
+                    &count);
+
+  g_list_store_append (store, obj3);
+  g_list_store_insert (store, 0, obj2);
+  g_list_store_append (store, obj4);
+  g_list_store_insert (store, 0, obj1);
+  g_list_store_append (store, obj5);
+
+  g_assert_cmpint (5, ==, count);
+  g_assert_cmpint (5, ==, g_list_model_get_n_items (wrapper));
+  g_assert (G_TYPE_OBJECT == g_list_model_get_item_type (wrapper));
+
+  read1 = g_list_model_get_item (wrapper, 0);
+  read2 = g_list_model_get_item (wrapper, 1);
+  read3 = g_list_model_get_item (wrapper, 2);
+  read4 = g_list_model_get_item (wrapper, 3);
+  read5 = g_list_model_get_item (wrapper, 4);
+
+  g_assert (read1 == obj1);
+  g_assert (read2 == obj2);
+  g_assert (read3 == obj3);
+  g_assert (read4 == obj4);
+  g_assert (read5 == obj5);
+}
+
+gint
+main (gint argc,
+      gchar *argv[])
+{
+  g_test_init (&argc, &argv, NULL);
+  g_test_add_func ("/Dazzle/ReadOnlyListModel/basic", test_basic);
+  return g_test_run ();
+}


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