Commit 5c6200aa authored by Christian Hergert's avatar Christian Hergert
Browse files

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.
parent 02d0d270
......@@ -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"
......
/* 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);
}
/* 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
......@@ -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',
......
......@@ -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
/* 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 ();
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment