diff options
author | Benjamin Otte <otte@redhat.com> | 2018-09-03 00:48:28 +0200 |
---|---|---|
committer | Benjamin Otte <otte@redhat.com> | 2018-09-16 18:50:17 +0200 |
commit | 32ec7dec61f9dc0a14ee3e6fccb834c229c8a0cf (patch) | |
tree | 32842ed69330a3c88ef70e17ee073bf6646a3c16 | |
parent | d6161e09cdfa662733e01d1a40cede50291743a5 (diff) | |
download | gtk+-32ec7dec61f9dc0a14ee3e6fccb834c229c8a0cf.tar.gz |
gtk: Add GtkFlattenListModel
We can flatten lists of lists into lists now!
-rw-r--r-- | docs/reference/gtk/gtk4-docs.xml | 1 | ||||
-rw-r--r-- | docs/reference/gtk/gtk4-sections.txt | 18 | ||||
-rw-r--r-- | gtk/gtk.h | 1 | ||||
-rw-r--r-- | gtk/gtkflattenlistmodel.c | 533 | ||||
-rw-r--r-- | gtk/gtkflattenlistmodel.h | 50 | ||||
-rw-r--r-- | gtk/meson.build | 2 | ||||
-rw-r--r-- | testsuite/gtk/flattenlistmodel.c | 372 | ||||
-rw-r--r-- | testsuite/gtk/meson.build | 1 |
8 files changed, 978 insertions, 0 deletions
diff --git a/docs/reference/gtk/gtk4-docs.xml b/docs/reference/gtk/gtk4-docs.xml index edb2529c6a..98f0c897e0 100644 --- a/docs/reference/gtk/gtk4-docs.xml +++ b/docs/reference/gtk/gtk4-docs.xml @@ -44,6 +44,7 @@ <chapter id="Lists"> <title>GListModel support</title> <xi:include href="xml/gtkfilterlistmodel.xml" /> + <xi:include href="xml/gtkflattenlistmodel.xml" /> <xi:include href="xml/gtktreelistmodel.xml" /> </chapter> diff --git a/docs/reference/gtk/gtk4-sections.txt b/docs/reference/gtk/gtk4-sections.txt index 81f3600f94..1f65a80c6c 100644 --- a/docs/reference/gtk/gtk4-sections.txt +++ b/docs/reference/gtk/gtk4-sections.txt @@ -1280,6 +1280,24 @@ gtk_fixed_get_type </SECTION> <SECTION> +<FILE>gtkflattenlistmodel</FILE> +<TITLE>GtkFlattenListModel</TITLE> +GtkFlattenListModel +gtk_flatten_list_model_new +gtk_flatten_list_model_set_model +gtk_flatten_list_model_get_model +<SUBSECTION Standard> +GTK_FLATTEN_LIST_MODEL +GTK_IS_FLATTEN_LIST_MODEL +GTK_TYPE_FLATTEN_LIST_MODEL +GTK_FLATTEN_LIST_MODEL_CLASS +GTK_IS_FLATTEN_LIST_MODEL_CLASS +GTK_FLATTEN_LIST_MODEL_GET_CLASS +<SUBSECTION Private> +gtk_flatten_list_model_get_type +</SECTION> + +<SECTION> <FILE>gtkfontbutton</FILE> <TITLE>GtkFontButton</TITLE> GtkFontButton @@ -105,6 +105,7 @@ #include <gtk/gtkfilechooserwidget.h> #include <gtk/gtkfilefilter.h> #include <gtk/gtkfilterlistmodel.h> +#include <gtk/gtkflattenlistmodel.h> #include <gtk/gtkflowbox.h> #include <gtk/gtkfontbutton.h> #include <gtk/gtkfontchooser.h> diff --git a/gtk/gtkflattenlistmodel.c b/gtk/gtkflattenlistmodel.c new file mode 100644 index 0000000000..3f5e83711e --- /dev/null +++ b/gtk/gtkflattenlistmodel.c @@ -0,0 +1,533 @@ +/* + * Copyright © 2018 Benjamin Otte + * + * This library 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 library 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 Lesser General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + * + * Authors: Benjamin Otte <otte@gnome.org> + */ + +#include "config.h" + +#include "gtkflattenlistmodel.h" + +#include "gtkcssrbtreeprivate.h" +#include "gtkintl.h" +#include "gtkprivate.h" + +/** + * SECTION:gtkflattenlistmodel + * @title: GtkFlattenListModel + * @short_description: a #GListModel that flattens a given listmodel + * @see_also: #GListModel + * + * #GtkFlattenListModel is a list model that takes a list model containing + * list models and flattens it into a single model. + * + * Another term for this is concatenation: #GtkFlattenListModel takes a + * list of lists and concatenates them into a single list. + */ + +enum { + PROP_0, + PROP_ITEM_TYPE, + PROP_MODEL, + NUM_PROPERTIES +}; + +typedef struct _FlattenNode FlattenNode; +typedef struct _FlattenAugment FlattenAugment; + +struct _FlattenNode +{ + GListModel *model; + GtkFlattenListModel *list; +}; + +struct _FlattenAugment +{ + guint n_items; + guint n_models; +}; + +struct _GtkFlattenListModel +{ + GObject parent_instance; + + GType item_type; + GListModel *model; + GtkCssRbTree *items; /* NULL if model == NULL */ +}; + +struct _GtkFlattenListModelClass +{ + GObjectClass parent_class; +}; + +static GParamSpec *properties[NUM_PROPERTIES] = { NULL, }; + +static FlattenNode * +gtk_flatten_list_model_get_nth (GtkCssRbTree *tree, + guint position, + guint *model_position) +{ + FlattenNode *node, *tmp; + guint model_n_items; + + node = gtk_css_rb_tree_get_root (tree); + + while (node) + { + tmp = gtk_css_rb_tree_get_left (tree, node); + if (tmp) + { + FlattenAugment *aug = gtk_css_rb_tree_get_augment (tree, tmp); + if (position < aug->n_items) + { + node = tmp; + continue; + } + position -= aug->n_items; + } + + model_n_items = g_list_model_get_n_items (node->model); + if (position < model_n_items) + break; + position -= model_n_items; + + node = gtk_css_rb_tree_get_right (tree, node); + } + + if (model_position) + *model_position = node ? position : 0; + + return node; +} + +static FlattenNode * +gtk_flatten_list_model_get_nth_model (GtkCssRbTree *tree, + guint position, + guint *items_before) +{ + FlattenNode *node, *tmp; + guint before; + + node = gtk_css_rb_tree_get_root (tree); + before = 0; + + while (node) + { + tmp = gtk_css_rb_tree_get_left (tree, node); + if (tmp) + { + FlattenAugment *aug = gtk_css_rb_tree_get_augment (tree, tmp); + if (position < aug->n_models) + { + node = tmp; + continue; + } + position -= aug->n_models; + before += aug->n_items; + } + + if (position == 0) + break; + position--; + before += g_list_model_get_n_items (node->model); + + node = gtk_css_rb_tree_get_right (tree, node); + } + + if (items_before) + *items_before = before; + + return node; +} + +static GType +gtk_flatten_list_model_get_item_type (GListModel *list) +{ + GtkFlattenListModel *self = GTK_FLATTEN_LIST_MODEL (list); + + return self->item_type; +} + +static guint +gtk_flatten_list_model_get_n_items (GListModel *list) +{ + GtkFlattenListModel *self = GTK_FLATTEN_LIST_MODEL (list); + FlattenAugment *aug; + FlattenNode *node; + + if (!self->items) + return 0; + + node = gtk_css_rb_tree_get_root (self->items); + if (node == NULL) + return 0; + + aug = gtk_css_rb_tree_get_augment (self->items, node); + return aug->n_items; +} + +static gpointer +gtk_flatten_list_model_get_item (GListModel *list, + guint position) +{ + GtkFlattenListModel *self = GTK_FLATTEN_LIST_MODEL (list); + FlattenNode *node; + guint model_pos; + + if (!self->items) + return NULL; + + node = gtk_flatten_list_model_get_nth (self->items, position, &model_pos); + if (node == NULL) + return NULL; + + return g_list_model_get_item (node->model, model_pos); +} + +static void +gtk_flatten_list_model_model_init (GListModelInterface *iface) +{ + iface->get_item_type = gtk_flatten_list_model_get_item_type; + iface->get_n_items = gtk_flatten_list_model_get_n_items; + iface->get_item = gtk_flatten_list_model_get_item; +} + +G_DEFINE_TYPE_WITH_CODE (GtkFlattenListModel, gtk_flatten_list_model, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, gtk_flatten_list_model_model_init)) + +static void +gtk_flatten_list_model_items_changed_cb (GListModel *model, + guint position, + guint removed, + guint added, + gpointer _node) +{ + FlattenNode *node = _node, *parent; + GtkFlattenListModel *self = node->list; + guint real_position; + + gtk_css_rb_tree_mark_dirty (self->items, node); + + for (real_position = position; + (parent = gtk_css_rb_tree_get_parent (self->items, node)) != NULL; + node = parent) + { + FlattenNode *left = gtk_css_rb_tree_get_left (self->items, parent); + if (left != node) + { + if (left) + { + FlattenAugment *aug = gtk_css_rb_tree_get_augment (self->items, left); + real_position += aug->n_items; + } + real_position += g_list_model_get_n_items (parent->model); + } + } + + g_list_model_items_changed (G_LIST_MODEL (self), real_position, removed, added); +} + +static void +gtk_flatten_list_model_clear_node (gpointer _node) +{ + FlattenNode *node= _node; + + g_signal_handlers_disconnect_by_func (node->model, gtk_flatten_list_model_items_changed_cb, node); + g_object_unref (node->model); +} + +static void +gtk_flatten_list_model_augment (GtkCssRbTree *flatten, + gpointer _aug, + gpointer _node, + gpointer left, + gpointer right) +{ + FlattenNode *node= _node; + FlattenAugment *aug = _aug; + + aug->n_items = g_list_model_get_n_items (node->model); + aug->n_models = 1; + + if (left) + { + FlattenAugment *left_aug = gtk_css_rb_tree_get_augment (flatten, left); + aug->n_items += left_aug->n_items; + aug->n_models += left_aug->n_models; + } + if (right) + { + FlattenAugment *right_aug = gtk_css_rb_tree_get_augment (flatten, right); + aug->n_items += right_aug->n_items; + aug->n_models += right_aug->n_models; + } +} + +static guint +gtk_flatten_list_model_add_items (GtkFlattenListModel *self, + FlattenNode *after, + guint position, + guint n) +{ + FlattenNode *node; + guint added, i; + + added = 0; + for (i = 0; i < n; i++) + { + node = gtk_css_rb_tree_insert_before (self->items, after); + node->model = g_list_model_get_item (self->model, position + i); + g_warn_if_fail (g_type_is_a (g_list_model_get_item_type (node->model), self->item_type)); + g_signal_connect (node->model, + "items-changed", + G_CALLBACK (gtk_flatten_list_model_items_changed_cb), + node); + node->list = self; + added +=g_list_model_get_n_items (node->model); + } + + return added; +} + +static void +gtk_flatten_list_model_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GtkFlattenListModel *self = GTK_FLATTEN_LIST_MODEL (object); + + switch (prop_id) + { + case PROP_ITEM_TYPE: + self->item_type = g_value_get_gtype (value); + break; + + case PROP_MODEL: + gtk_flatten_list_model_set_model (self, g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gtk_flatten_list_model_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GtkFlattenListModel *self = GTK_FLATTEN_LIST_MODEL (object); + + switch (prop_id) + { + case PROP_ITEM_TYPE: + g_value_set_gtype (value, self->item_type); + break; + + case PROP_MODEL: + g_value_set_object (value, self->model); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gtk_flatten_list_model_model_items_changed_cb (GListModel *model, + guint position, + guint removed, + guint added, + GtkFlattenListModel *self) +{ + FlattenNode *node; + guint i, real_position, real_removed, real_added; + + node = gtk_flatten_list_model_get_nth_model (self->items, position, &real_position); + + real_removed = 0; + for (i = 0; i < removed; i++) + { + FlattenNode *next = gtk_css_rb_tree_get_next (self->items, node); + real_removed += g_list_model_get_n_items (node->model); + gtk_css_rb_tree_remove (self->items, node); + node = next; + } + + real_added = gtk_flatten_list_model_add_items (self, node, position, added); + + if (real_removed > 0 || real_added > 0) + g_list_model_items_changed (G_LIST_MODEL (self), real_position, real_removed, real_added); +} + +static void +gtk_flatten_list_clear_model (GtkFlattenListModel *self) +{ + if (self->model) + { + g_signal_handlers_disconnect_by_func (self->model, gtk_flatten_list_model_model_items_changed_cb, self); + g_clear_object (&self->model); + g_clear_pointer (&self->items, gtk_css_rb_tree_unref); + } +} + +static void +gtk_flatten_list_model_dispose (GObject *object) +{ + GtkFlattenListModel *self = GTK_FLATTEN_LIST_MODEL (object); + + gtk_flatten_list_clear_model (self); + + G_OBJECT_CLASS (gtk_flatten_list_model_parent_class)->dispose (object); +}; + +static void +gtk_flatten_list_model_class_init (GtkFlattenListModelClass *class) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (class); + + gobject_class->set_property = gtk_flatten_list_model_set_property; + gobject_class->get_property = gtk_flatten_list_model_get_property; + gobject_class->dispose = gtk_flatten_list_model_dispose; + + /** + * GtkFlattenListModel:item-type: + * + * The #GTpe for elements of this object + */ + properties[PROP_ITEM_TYPE] = + g_param_spec_gtype ("item-type", + P_("Item type"), + P_("The type of elements of this object"), + G_TYPE_OBJECT, + GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY); + + /** + * GtkFlattenListModel:model: + * + * The model being flattened + */ + properties[PROP_MODEL] = + g_param_spec_object ("model", + P_("Model"), + P_("The model being flattened"), + G_TYPE_LIST_MODEL, + GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); + + g_object_class_install_properties (gobject_class, NUM_PROPERTIES, properties); +} + +static void +gtk_flatten_list_model_init (GtkFlattenListModel *self) +{ +} + +/** + * gtk_flatten_list_model_new: + * @item_type: The type of items in the to-be-flattened models + * @model: (nullable) (transfer none): the item to be flattened + * + * Creates a new #GtkFlattenListModel that flattens @list. The + * models returned by @model must conform to the given @item_type, + * either by having an identical type or a subtype. + * + * Returns: a new #GtkFlattenListModel + **/ +GtkFlattenListModel * +gtk_flatten_list_model_new (GType item_type, + GListModel *model) +{ + GtkFlattenListModel *result; + + g_return_val_if_fail (g_type_is_a (item_type, G_TYPE_OBJECT), NULL); + g_return_val_if_fail (model == NULL || G_IS_LIST_MODEL (model), NULL); + + result = g_object_new (GTK_TYPE_FLATTEN_LIST_MODEL, + "item-type", item_type, + "model", model, + NULL); + + return result; +} + +/** + * gtk_flatten_list_model_set_model: + * @self: a #GtkFlattenListModel + * @model: (nullable) (transfer none): the new model or %NULL + * + * Sets a new model to be flattened. The model must contain items of + * #GtkListModel that conform to the item type of @self. + **/ +void +gtk_flatten_list_model_set_model (GtkFlattenListModel *self, + GListModel *model) +{ + guint removed, added; + + g_return_if_fail (GTK_IS_FLATTEN_LIST_MODEL (self)); + g_return_if_fail (model == NULL || G_IS_LIST_MODEL (model)); + if (model) + { + g_return_if_fail (g_list_model_get_item_type (model) == G_TYPE_LIST_MODEL); + } + + if (self->model == model) + return; + + removed = g_list_model_get_n_items (G_LIST_MODEL (self)); + gtk_flatten_list_clear_model (self); + + self->model = model; + + if (model) + { + g_object_ref (model); + g_signal_connect (model, "items-changed", G_CALLBACK (gtk_flatten_list_model_model_items_changed_cb), self); + self->items = gtk_css_rb_tree_new (FlattenNode, + FlattenAugment, + gtk_flatten_list_model_augment, + gtk_flatten_list_model_clear_node, + NULL); + + added = gtk_flatten_list_model_add_items (self, NULL, 0, g_list_model_get_n_items (model)); + } + + if (removed > 0 || added > 0) + g_list_model_items_changed (G_LIST_MODEL (self), 0, removed, added); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MODEL]); +} + +/** + * gtk_flatten_list_model_get_model: + * @self: a #GtkFlattenListModel + * + * Gets the model set via gtk_flatten_list_model_set_model(). + * + * Returns: (nullable) (transfer none): The model flattened by @self + **/ +GListModel * +gtk_flatten_list_model_get_model (GtkFlattenListModel *self) +{ + g_return_val_if_fail (GTK_IS_FLATTEN_LIST_MODEL (self), NULL); + + return self->model; +} diff --git a/gtk/gtkflattenlistmodel.h b/gtk/gtkflattenlistmodel.h new file mode 100644 index 0000000000..75e39404d7 --- /dev/null +++ b/gtk/gtkflattenlistmodel.h @@ -0,0 +1,50 @@ +/* + * Copyright © 2018 Benjamin Otte + * + * This library 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 library 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 Lesser General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + * + * Authors: Benjamin Otte <otte@gnome.org> + */ + +#ifndef __GTK_FLATTEN_LIST_MODEL_H__ +#define __GTK_FLATTEN_LIST_MODEL_H__ + + +#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION) +#error "Only <gtk/gtk.h> can be included directly." +#endif + +#include <gdk/gdk.h> + + +G_BEGIN_DECLS + +#define GTK_TYPE_FLATTEN_LIST_MODEL (gtk_flatten_list_model_get_type ()) + +GDK_AVAILABLE_IN_ALL +G_DECLARE_FINAL_TYPE (GtkFlattenListModel, gtk_flatten_list_model, GTK, FLATTEN_LIST_MODEL, GObject) + +GDK_AVAILABLE_IN_ALL +GtkFlattenListModel * gtk_flatten_list_model_new (GType item_type, + GListModel *model); + +GDK_AVAILABLE_IN_ALL +void gtk_flatten_list_model_set_model (GtkFlattenListModel *self, + GListModel *model); +GDK_AVAILABLE_IN_ALL +GListModel * gtk_flatten_list_model_get_model (GtkFlattenListModel *self); + +G_END_DECLS + +#endif /* __GTK_FLATTEN_LIST_MODEL_H__ */ diff --git a/gtk/meson.build b/gtk/meson.build index cd962dc411..dbdc455467 100644 --- a/gtk/meson.build +++ b/gtk/meson.build @@ -231,6 +231,7 @@ gtk_public_sources = files([ 'gtkfilefilter.c', 'gtkfilterlistmodel.c', 'gtkfixed.c', + 'gtkflattenlistmodel.c', 'gtkflowbox.c', 'gtkfontbutton.c', 'gtkfontchooser.c', @@ -475,6 +476,7 @@ gtk_public_headers = files([ 'gtkfilefilter.h', 'gtkfilterlistmodel.h', 'gtkfixed.h', + 'gtkflattenlistmodel.h', 'gtkflowbox.h', 'gtkfontbutton.h', 'gtkfontchooser.h', diff --git a/testsuite/gtk/flattenlistmodel.c b/testsuite/gtk/flattenlistmodel.c new file mode 100644 index 0000000000..c1ea5dfaf3 --- /dev/null +++ b/testsuite/gtk/flattenlistmodel.c @@ -0,0 +1,372 @@ +/* GtkRBTree tests. + * + * Copyright (C) 2011, Red Hat, Inc. + * Authors: Benjamin Otte <otte@gnome.org> + * + * This library 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 of the License, or (at your option) any later version. + * + * This library 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 Lesser General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <locale.h> + +#include <gtk/gtk.h> + +static GQuark number_quark; +static GQuark changes_quark; + +static guint +get (GListModel *model, + guint position) +{ + GObject *object = g_list_model_get_item (model, position); + g_assert (object != NULL); + return GPOINTER_TO_UINT (g_object_get_qdata (object, number_quark)); +} + +static char * +model_to_string (GListModel *model) +{ + GString *string = g_string_new (NULL); + guint i; + + for (i = 0; i < g_list_model_get_n_items (model); i++) + { + if (i > 0) + g_string_append (string, " "); + g_string_append_printf (string, "%u", get (model, i)); + } + + return g_string_free (string, FALSE); +} + +static GListStore * +new_store (guint start, + guint end, + guint step); + +static void +splice (GListStore *store, + guint pos, + guint removed, + guint *numbers, + guint added) +{ + GObject *objects[added]; + guint i; + + for (i = 0; i < added; i++) + { + /* 0 cannot be differentiated from NULL, so don't use it */ + g_assert (numbers[i] != 0); + objects[i] = g_object_new (G_TYPE_OBJECT, NULL); + g_object_set_qdata (objects[i], number_quark, GUINT_TO_POINTER (numbers[i])); + } + + g_list_store_splice (store, pos, removed, (gpointer *) objects, added); + + for (i = 0; i < added; i++) + g_object_unref (objects[i]); +} + +static void +insert (GListStore *store, + guint pos, + guint number) +{ + GObject *object; + + /* 0 cannot be differentiated from NULL, so don't use it */ + g_assert (number != 0); + + object = g_object_new (G_TYPE_OBJECT, NULL); + g_object_set_qdata (object, number_quark, GUINT_TO_POINTER (number)); + g_list_store_insert (store, pos, object); + g_object_unref (object); +} + +static void +add (GListStore *store, + guint number) +{ + GObject *object; + + /* 0 cannot be differentiated from NULL, so don't use it */ + g_assert (number != 0); + + object = g_object_new (G_TYPE_OBJECT, NULL); + g_object_set_qdata (object, number_quark, GUINT_TO_POINTER (number)); + g_list_store_append (store, object); + g_object_unref (object); +} + +static GListStore * +add_store (GListStore *store, + guint start, + guint end, + guint step) +{ + GListStore *child; + + child = new_store (start, end, step); + g_list_store_append (store, child); + g_object_unref (child); + + return child; +} + +#define assert_model(model, expected) G_STMT_START{ \ + char *s = model_to_string (G_LIST_MODEL (model)); \ + if (!g_str_equal (s, expected)) \ + g_assertion_message_cmpstr (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \ + #model " == " #expected, s, "==", expected); \ + g_free (s); \ +}G_STMT_END + +#define assert_changes(model, expected) G_STMT_START{ \ + GString *changes = g_object_get_qdata (G_OBJECT (model), changes_quark); \ + if (!g_str_equal (changes->str, expected)) \ + g_assertion_message_cmpstr (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \ + #model " == " #expected, changes->str, "==", expected); \ + g_string_set_size (changes, 0); \ +}G_STMT_END + +static GListStore * +new_empty_store (void) +{ + return g_list_store_new (G_TYPE_OBJECT); +} + +static GListStore * +new_store (guint start, + guint end, + guint step) +{ + GListStore *store = new_empty_store (); + guint i; + + for (i = start; i <= end; i += step) + add (store, i); + + return store; +} + +static void +items_changed (GListModel *model, + guint position, + guint removed, + guint added, + GString *changes) +{ + g_assert (removed != 0 || added != 0); + + if (changes->len) + g_string_append (changes, ", "); + + if (removed == 1 && added == 0) + { + g_string_append_printf (changes, "-%u", position); + } + else if (removed == 0 && added == 1) + { + g_string_append_printf (changes, "+%u", position); + } + else + { + g_string_append_printf (changes, "%u", position); + if (removed > 0) + g_string_append_printf (changes, "-%u", removed); + if (added > 0) + g_string_append_printf (changes, "+%u", added); + } +} + +static void +free_changes (gpointer data) +{ + GString *changes = data; + + /* all changes must have been checked via assert_changes() before */ + g_assert_cmpstr (changes->str, ==, ""); + + g_string_free (changes, TRUE); +} + +static GtkFlattenListModel * +new_model (GListStore *store) +{ + GtkFlattenListModel *result; + GString *changes; + + result = gtk_flatten_list_model_new (G_TYPE_OBJECT, G_LIST_MODEL (store)); + changes = g_string_new (""); + g_object_set_qdata_full (G_OBJECT(result), changes_quark, changes, free_changes); + g_signal_connect (result, "items-changed", G_CALLBACK (items_changed), changes); + + return result; +} + +static void +test_create_empty (void) +{ + GtkFlattenListModel *flat; + + flat = new_model (NULL); + assert_model (flat, ""); + assert_changes (flat, ""); + + g_object_unref (flat); +} + +static void +test_create (void) +{ + GtkFlattenListModel *flat; + GListStore *model; + + model = g_list_store_new (G_TYPE_LIST_MODEL); + add_store (model, 1, 3, 1); + add_store (model, 4, 4, 1); + add_store (model, 5, 7, 1); + add_store (model, 8, 10, 1); + flat = new_model (model); + assert_model (flat, "1 2 3 4 5 6 7 8 9 10"); + assert_changes (flat, ""); + + g_object_unref (model); + assert_model (flat, "1 2 3 4 5 6 7 8 9 10"); + assert_changes (flat, ""); + + g_object_unref (flat); +} + +static void +test_model_add (void) +{ + GtkFlattenListModel *flat; + GListStore *model; + + model = g_list_store_new (G_TYPE_LIST_MODEL); + flat = new_model (model); + assert_model (flat, ""); + assert_changes (flat, ""); + + add_store (model, 1, 3, 1); + add_store (model, 4, 4, 1); + add_store (model, 5, 7, 1); + add_store (model, 8, 10, 1); + + assert_model (flat, "1 2 3 4 5 6 7 8 9 10"); + assert_changes (flat, "0+3, +3, 4+3, 7+3"); + + g_object_unref (model); + g_object_unref (flat); +} + +static void +test_submodel_add (void) +{ + GtkFlattenListModel *flat; + GListStore *model, *store[4]; + + model = g_list_store_new (G_TYPE_LIST_MODEL); + flat = new_model (model); + assert_model (flat, ""); + assert_changes (flat, ""); + + store[0] = add_store (model, 2, 3, 1); + store[1] = add_store (model, 4, 4, 1); + store[2] = add_store (model, 5, 4, 1); + store[3] = add_store (model, 8, 8, 1); + assert_model (flat, "2 3 4 8"); + assert_changes (flat, "0+2, +2, +3"); + + insert (store[0], 0, 1); + splice (store[2], 0, 0, (guint[3]) { 5, 6, 7 }, 3); + splice (store[3], 1, 0, (guint[2]) { 9, 10 }, 2); + assert_model (flat, "1 2 3 4 5 6 7 8 9 10"); + assert_changes (flat, "+0, 4+3, 8+2"); + + g_object_unref (model); + g_object_unref (flat); +} + +static void +test_model_remove (void) +{ + GtkFlattenListModel *flat; + GListStore *model; + + model = g_list_store_new (G_TYPE_LIST_MODEL); + add_store (model, 1, 3, 1); + add_store (model, 4, 4, 1); + add_store (model, 5, 7, 1); + add_store (model, 8, 10, 1); + flat = new_model (model); + assert_model (flat, "1 2 3 4 5 6 7 8 9 10"); + assert_changes (flat, ""); + + splice (model, 1, 2, NULL, 0); + g_list_store_remove (model, 1); + g_list_store_remove (model, 0); + g_object_unref (model); + assert_model (flat, ""); + assert_changes (flat, "3-4, 3-3, 0-3"); + + g_object_unref (flat); +} + +static void +test_submodel_remove (void) +{ + GtkFlattenListModel *flat; + GListStore *model, *store[4]; + + model = g_list_store_new (G_TYPE_LIST_MODEL); + store[0] = add_store (model, 1, 3, 1); + store[1] = add_store (model, 4, 4, 1); + store[2] = add_store (model, 5, 7, 1); + store[3] = add_store (model, 8, 10, 1); + flat = new_model (model); + assert_model (flat, "1 2 3 4 5 6 7 8 9 10"); + assert_changes (flat, ""); + + g_list_store_remove (store[0], 0); + splice (store[2], 0, 3, NULL, 0); + splice (store[3], 1, 2, NULL, 0); + g_object_unref (model); + + assert_model (flat, "2 3 4 8"); + assert_changes (flat, "-0, 3-3, 4-2"); + + g_object_unref (flat); +} + +int +main (int argc, char *argv[]) +{ + g_test_init (&argc, &argv, NULL); + setlocale (LC_ALL, "C"); + g_test_bug_base ("http://bugzilla.gnome.org/show_bug.cgi?id=%s"); + + number_quark = g_quark_from_static_string ("Hell and fire was spawned to be released."); + changes_quark = g_quark_from_static_string ("What did I see? Can I believe what I saw?"); + + g_test_add_func ("/flattenlistmodel/create_empty", test_create_empty); + g_test_add_func ("/flattenlistmodel/create", test_create); + g_test_add_func ("/flattenlistmodel/model/add", test_model_add); + g_test_add_func ("/flattenlistmodel/submodel/add", test_submodel_add); + g_test_add_func ("/flattenlistmodel/model/remove", test_model_remove); + g_test_add_func ("/flattenlistmodel/submodel/remove", test_submodel_remove); + + return g_test_run (); +} diff --git a/testsuite/gtk/meson.build b/testsuite/gtk/meson.build index de1bcb15a7..052de2abfa 100644 --- a/testsuite/gtk/meson.build +++ b/testsuite/gtk/meson.build @@ -20,6 +20,7 @@ tests = [ ['defaultvalue'], ['entry'], ['filterlistmodel'], + ['flattenlistmodel'], ['firefox-stylecontext'], ['floating'], ['focus'], |