diff options
author | Benjamin Otte <otte@redhat.com> | 2018-08-31 05:34:18 +0200 |
---|---|---|
committer | Benjamin Otte <otte@redhat.com> | 2018-09-16 18:50:17 +0200 |
commit | 4f70f7234997bcb6ae91bbaaa277c573fc17b414 (patch) | |
tree | b97972a6fe6e172c9dbe35bb9e5db9b3c274f2a0 | |
parent | dd94129e27b0ad4f4f635053f8b09e94d94c29ae (diff) | |
download | gtk+-4f70f7234997bcb6ae91bbaaa277c573fc17b414.tar.gz |
gtk: Add GtkFilterListModel
This is a GListModel implementation that filters the given source model.
-rw-r--r-- | docs/reference/gtk/gtk4-docs.xml | 1 | ||||
-rw-r--r-- | docs/reference/gtk/gtk4-sections.txt | 20 | ||||
-rw-r--r-- | gtk/gtk.h | 1 | ||||
-rw-r--r-- | gtk/gtkfilterlistmodel.c | 711 | ||||
-rw-r--r-- | gtk/gtkfilterlistmodel.h | 67 | ||||
-rw-r--r-- | gtk/meson.build | 2 | ||||
-rw-r--r-- | testsuite/gtk/defaultvalue.c | 12 | ||||
-rw-r--r-- | testsuite/gtk/filterlistmodel.c | 324 | ||||
-rw-r--r-- | testsuite/gtk/meson.build | 1 | ||||
-rw-r--r-- | testsuite/gtk/notify.c | 8 | ||||
-rw-r--r-- | testsuite/gtk/objects-finalize.c | 8 |
11 files changed, 1155 insertions, 0 deletions
diff --git a/docs/reference/gtk/gtk4-docs.xml b/docs/reference/gtk/gtk4-docs.xml index 2104d147ea..edb2529c6a 100644 --- a/docs/reference/gtk/gtk4-docs.xml +++ b/docs/reference/gtk/gtk4-docs.xml @@ -43,6 +43,7 @@ <chapter id="Lists"> <title>GListModel support</title> + <xi:include href="xml/gtkfilterlistmodel.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 382305663c..81f3600f94 100644 --- a/docs/reference/gtk/gtk4-sections.txt +++ b/docs/reference/gtk/gtk4-sections.txt @@ -1240,6 +1240,26 @@ gtk_file_filter_get_type </SECTION> <SECTION> +<FILE>gtkfilterlistmodel</FILE> +<TITLE>GtkFilterListModel</TITLE> +GtkFilterListModel +gtk_filter_list_model_new +gtk_filter_list_model_get_model +gtk_filter_list_model_set_filter_func +gtk_filter_list_model_has_filter +gtk_filter_list_model_refilter +<SUBSECTION Standard> +GTK_FILTER_LIST_MODEL +GTK_IS_FILTER_LIST_MODEL +GTK_TYPE_FILTER_LIST_MODEL +GTK_FILTER_LIST_MODEL_CLASS +GTK_IS_FILTER_LIST_MODEL_CLASS +GTK_FILTER_LIST_MODEL_GET_CLASS +<SUBSECTION Private> +gtk_filter_list_model_get_type +</SECTION> + +<SECTION> <FILE>gtkfixed</FILE> <TITLE>GtkFixed</TITLE> GtkFixed @@ -104,6 +104,7 @@ #include <gtk/gtkfilechoosernative.h> #include <gtk/gtkfilechooserwidget.h> #include <gtk/gtkfilefilter.h> +#include <gtk/gtkfilterlistmodel.h> #include <gtk/gtkflowbox.h> #include <gtk/gtkfontbutton.h> #include <gtk/gtkfontchooser.h> diff --git a/gtk/gtkfilterlistmodel.c b/gtk/gtkfilterlistmodel.c new file mode 100644 index 0000000000..4703d66816 --- /dev/null +++ b/gtk/gtkfilterlistmodel.c @@ -0,0 +1,711 @@ +/* + * 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 "gtkfilterlistmodel.h" + +#include "gtkcssrbtreeprivate.h" +#include "gtkintl.h" +#include "gtkprivate.h" + +/** + * SECTION:gtkfilterlistmodel + * @title: GtkFilterListModel + * @short_description: a #GListModel that filters its items + * @see_also: #GListModel + * + * #GtkFilterListModel is a list model that filters a given other + * listmodel. + * It hides some elements from the other elements according to + * criteria given by a #GtkFilterListModelFilterFunc. + */ + +enum { + PROP_0, + PROP_HAS_FILTER, + PROP_ITEM_TYPE, + PROP_MODEL, + NUM_PROPERTIES +}; + +typedef struct _FilterNode FilterNode; +typedef struct _FilterAugment FilterAugment; + +struct _FilterNode +{ + guint visible : 1; +}; + +struct _FilterAugment +{ + guint n_items; + guint n_visible; +}; + +struct _GtkFilterListModel +{ + GObject parent_instance; + + GType item_type; + GListModel *model; + GtkFilterListModelFilterFunc filter_func; + gpointer user_data; + GDestroyNotify user_destroy; + + GtkCssRbTree *items; /* NULL if filter_func == NULL */ +}; + +struct _GtkFilterListModelClass +{ + GObjectClass parent_class; +}; + +static GParamSpec *properties[NUM_PROPERTIES] = { NULL, }; + +static FilterNode * +gtk_filter_list_model_get_nth_filtered (GtkCssRbTree *tree, + guint position, + guint *out_unfiltered) +{ + FilterNode *node, *tmp; + guint unfiltered; + + node = gtk_css_rb_tree_get_root (tree); + unfiltered = 0; + + while (node) + { + tmp = gtk_css_rb_tree_get_left (tree, node); + if (tmp) + { + FilterAugment *aug = gtk_css_rb_tree_get_augment (tree, tmp); + if (position < aug->n_visible) + { + node = tmp; + continue; + } + position -= aug->n_visible; + unfiltered += aug->n_items; + } + + if (node->visible) + { + if (position == 0) + break; + position--; + } + + unfiltered++; + + node = gtk_css_rb_tree_get_right (tree, node); + } + + if (out_unfiltered) + *out_unfiltered = unfiltered; + + return node; +} + +static FilterNode * +gtk_filter_list_model_get_nth (GtkCssRbTree *tree, + guint position, + guint *out_filtered) +{ + FilterNode *node, *tmp; + guint filtered; + + node = gtk_css_rb_tree_get_root (tree); + filtered = 0; + + while (node) + { + tmp = gtk_css_rb_tree_get_left (tree, node); + if (tmp) + { + FilterAugment *aug = gtk_css_rb_tree_get_augment (tree, tmp); + if (position < aug->n_items) + { + node = tmp; + continue; + } + position -= aug->n_items; + filtered += aug->n_visible; + } + + if (position == 0) + break; + + position--; + if (node->visible) + filtered++; + + node = gtk_css_rb_tree_get_right (tree, node); + } + + if (out_filtered) + *out_filtered = filtered; + + return node; +} + +static GType +gtk_filter_list_model_get_item_type (GListModel *list) +{ + GtkFilterListModel *self = GTK_FILTER_LIST_MODEL (list); + + return self->item_type; +} + +static guint +gtk_filter_list_model_get_n_items (GListModel *list) +{ + GtkFilterListModel *self = GTK_FILTER_LIST_MODEL (list); + FilterAugment *aug; + FilterNode *node; + + if (self->model == NULL) + return 0; + + if (!self->items) + return g_list_model_get_n_items (self->model); + + 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_visible; +} + +static gpointer +gtk_filter_list_model_get_item (GListModel *list, + guint position) +{ + GtkFilterListModel *self = GTK_FILTER_LIST_MODEL (list); + guint unfiltered; + + if (self->model == NULL) + return NULL; + + if (self->items) + gtk_filter_list_model_get_nth_filtered (self->items, position, &unfiltered); + else + unfiltered = position; + + return g_list_model_get_item (self->model, unfiltered); +} + +static void +gtk_filter_list_model_model_init (GListModelInterface *iface) +{ + iface->get_item_type = gtk_filter_list_model_get_item_type; + iface->get_n_items = gtk_filter_list_model_get_n_items; + iface->get_item = gtk_filter_list_model_get_item; +} + +G_DEFINE_TYPE_WITH_CODE (GtkFilterListModel, gtk_filter_list_model, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, gtk_filter_list_model_model_init)) + +static gboolean +gtk_filter_list_model_run_filter (GtkFilterListModel *self, + guint position) +{ + gpointer item; + gboolean visible; + + item = g_list_model_get_item (self->model, position); + visible = self->filter_func (item, self->user_data); + g_object_unref (item); + + return visible; +} + +static guint +gtk_filter_list_model_add_items (GtkFilterListModel *self, + FilterNode *after, + guint position, + guint n_items) +{ + FilterNode *node; + guint i, n_visible; + + n_visible = 0; + + for (i = 0; i < n_items; i++) + { + node = gtk_css_rb_tree_insert_before (self->items, after); + node->visible = gtk_filter_list_model_run_filter (self, position + i); + if (node->visible) + n_visible++; + } + + return n_visible; +} + +static void +gtk_filter_list_model_items_changed_cb (GListModel *model, + guint position, + guint removed, + guint added, + GtkFilterListModel *self) +{ + FilterNode *node; + guint i, filter_position, filter_removed, filter_added; + + if (self->items == NULL) + { + g_list_model_items_changed (G_LIST_MODEL (self), position, removed, added); + return; + } + + node = gtk_filter_list_model_get_nth (self->items, position, &filter_position); + + filter_removed = 0; + for (i = 0; i < removed; i++) + { + FilterNode *next = gtk_css_rb_tree_get_next (self->items, node); + if (node->visible) + filter_removed++; + gtk_css_rb_tree_remove (self->items, node); + node = next; + } + + filter_added = gtk_filter_list_model_add_items (self, node, position, added); + + if (filter_removed > 0 || filter_added > 0) + g_list_model_items_changed (G_LIST_MODEL (self), filter_position, filter_removed, filter_added); +} + +static void +gtk_filter_list_model_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GtkFilterListModel *self = GTK_FILTER_LIST_MODEL (object); + + switch (prop_id) + { + case PROP_ITEM_TYPE: + self->item_type = g_value_get_gtype (value); + break; + + case PROP_MODEL: + gtk_filter_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_filter_list_model_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GtkFilterListModel *self = GTK_FILTER_LIST_MODEL (object); + + switch (prop_id) + { + case PROP_HAS_FILTER: + g_value_set_boolean (value, self->items != NULL); + break; + + 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_filter_list_model_clear_model (GtkFilterListModel *self) +{ + if (self->model == NULL) + return; + + g_signal_handlers_disconnect_by_func (self->model, gtk_filter_list_model_items_changed_cb, self); + g_clear_object (&self->model); + if (self->items) + gtk_css_rb_tree_remove_all (self->items); +} + +static void +gtk_filter_list_model_dispose (GObject *object) +{ + GtkFilterListModel *self = GTK_FILTER_LIST_MODEL (object); + + gtk_filter_list_model_clear_model (self); + if (self->user_destroy) + self->user_destroy (self->user_data); + self->filter_func = NULL; + self->user_data = NULL; + self->user_destroy = NULL; + g_clear_pointer (&self->items, gtk_css_rb_tree_unref); + + G_OBJECT_CLASS (gtk_filter_list_model_parent_class)->dispose (object); +}; + +static void +gtk_filter_list_model_class_init (GtkFilterListModelClass *class) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (class); + + gobject_class->set_property = gtk_filter_list_model_set_property; + gobject_class->get_property = gtk_filter_list_model_get_property; + gobject_class->dispose = gtk_filter_list_model_dispose; + + /** + * GtkFilterListModel:has-filter: + * + * If a filter is set for this model + */ + properties[PROP_HAS_FILTER] = + g_param_spec_boolean ("has-filter", + P_("has filter"), + P_("If a filter is set for this model"), + FALSE, + GTK_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY); + + /** + * GtkFilterListModel:item-type: + * + * The #GType 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); + + /** + * GtkFilterListModel:model: + * + * The model being filtered + */ + properties[PROP_MODEL] = + g_param_spec_object ("model", + P_("Model"), + P_("The model being filtered"), + G_TYPE_LIST_MODEL, + GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY); + + g_object_class_install_properties (gobject_class, NUM_PROPERTIES, properties); +} + +static void +gtk_filter_list_model_init (GtkFilterListModel *self) +{ +} + + +static void +gtk_filter_list_model_augment (GtkCssRbTree *filter, + gpointer _aug, + gpointer _node, + gpointer left, + gpointer right) +{ + FilterNode *node= _node; + FilterAugment *aug = _aug; + + aug->n_items = 1; + aug->n_visible = node->visible ? 1 : 0; + + if (left) + { + FilterAugment *left_aug = gtk_css_rb_tree_get_augment (filter, left); + aug->n_items += left_aug->n_items; + aug->n_visible += left_aug->n_visible; + } + if (right) + { + FilterAugment *right_aug = gtk_css_rb_tree_get_augment (filter, right); + aug->n_items += right_aug->n_items; + aug->n_visible += right_aug->n_visible; + } +} + +/** + * gtk_filter_list_model_new: + * @model: the model to sort + * @filter_func: (allow-none): filter function or %NULL to not filter items + * @user_data: user data passed to @filter_func + * @destroy: destroy notifier for @user_data + * + * Creates a new #GtkFilterListModel that will filter @model using the given + * @filter_func. + * + * Returns: a new #GtkFilterListModel + **/ +GtkFilterListModel * +gtk_filter_list_model_new (GListModel *model, + GtkFilterListModelFilterFunc filter_func, + gpointer user_data, + GDestroyNotify user_destroy) +{ + GtkFilterListModel *result; + + g_return_val_if_fail (G_IS_LIST_MODEL (model), NULL); + + result = g_object_new (GTK_TYPE_FILTER_LIST_MODEL, + "item-type", g_list_model_get_item_type (model), + "model", model, + NULL); + + if (filter_func) + gtk_filter_list_model_set_filter_func (result, filter_func, user_data, user_destroy); + + return result; +} + +/** + * gtk_filter_list_model_new_for_type: + * @item_type: the type of the items that will be returned + * + * Creates a new empty filter list model set up to return items of type @item_type. + * It is up to the application to set a proper filter function and model to ensure + * the item type is matched. + * + * Returns: a new #GtkFilterListModel + **/ +GtkFilterListModel * +gtk_filter_list_model_new_for_type (GType item_type) +{ + g_return_val_if_fail (g_type_is_a (item_type, G_TYPE_OBJECT), NULL); + + return g_object_new (GTK_TYPE_FILTER_LIST_MODEL, + "item-type", item_type, + NULL); +} + +/** + * gtk_filter_list_model_set_filter_func: + * @self: a #GtkFilterListModel + * @filter_func: (allow-none): filter function or %NULL to not filter items + * @user_data: user data passed to @filter_func + * @destroy: destroy notifier for @user_data + * + * Sets the function used to filter items. The function will be called for every + * item and if it returns %TRUE the item is considered visible. + **/ +void +gtk_filter_list_model_set_filter_func (GtkFilterListModel *self, + GtkFilterListModelFilterFunc filter_func, + gpointer user_data, + GDestroyNotify user_destroy) +{ + gboolean was_filtered, will_be_filtered; + + g_return_if_fail (GTK_IS_FILTER_LIST_MODEL (self)); + g_return_if_fail (filter_func != NULL || (user_data == NULL && !user_destroy)); + + was_filtered = self->filter_func != NULL; + will_be_filtered = filter_func != NULL; + + if (!was_filtered && !will_be_filtered) + return; + + if (self->user_destroy) + self->user_destroy (self->user_data); + + self->filter_func = filter_func; + self->user_data = user_data; + self->user_destroy = user_destroy; + + if (!will_be_filtered) + { + g_clear_pointer (&self->items, gtk_css_rb_tree_unref); + } + else if (!was_filtered) + { + guint i, n_items; + + self->items = gtk_css_rb_tree_new (FilterNode, + FilterAugment, + gtk_filter_list_model_augment, + NULL, NULL); + if (self->model) + { + n_items = g_list_model_get_n_items (self->model); + for (i = 0; i < n_items; i++) + { + FilterNode *node = gtk_css_rb_tree_insert_before (self->items, NULL); + node->visible = TRUE; + } + } + } + + gtk_filter_list_model_refilter (self); + + if (was_filtered != will_be_filtered) + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_HAS_FILTER]); +} + +/** + * gtk_filter_list_model_set_model: + * @self: a #GtkFilterListModel + * @model: (allow-none): The model to be filtered + * + * Sets the model to be filtered. + * + * Note that GTK makes no effort to ensure that @model conforms to + * the item type of @self. It assumes that the caller knows what they + * are doing and have set up an appropriate filter function to ensure + * that item types match. + **/ +void +gtk_filter_list_model_set_model (GtkFilterListModel *self, + GListModel *model) +{ + guint removed, added; + + g_return_if_fail (GTK_IS_FILTER_LIST_MODEL (self)); + g_return_if_fail (model == NULL || G_IS_LIST_MODEL (model)); + /* Note: We don't check for matching item type here, we just assume the + * filter func takes care of filtering wrong items. */ + + if (self->model == model) + return; + + removed = g_list_model_get_n_items (G_LIST_MODEL (self)); + gtk_filter_list_model_clear_model (self); + + if (model) + { + self->model = g_object_ref (model); + g_signal_connect (model, "items-changed", G_CALLBACK (gtk_filter_list_model_items_changed_cb), self); + if (self->items) + added = gtk_filter_list_model_add_items (self, NULL, 0, g_list_model_get_n_items (model)); + else + added = g_list_model_get_n_items (model); + } + else + added = 0; + + 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_filter_list_model_get_model: + * @self: a #GtkFilterListModel + * + * Gets the model currently filtered or %NULL if none. + * + * Returns: (nullable) (transfer none): The model that gets filtered + **/ +GListModel * +gtk_filter_list_model_get_model (GtkFilterListModel *self) +{ + g_return_val_if_fail (GTK_IS_FILTER_LIST_MODEL (self), NULL); + + return self->model; +} + +/** + * gtk_filter_list_model_has_filter: + * @self: a #GtkFilterListModel + * + * Checks if a filter function is currently set on @self + * + * Returns: %TRUE if a filter function is set + **/ +gboolean +gtk_filter_list_model_has_filter (GtkFilterListModel *self) +{ + g_return_val_if_fail (GTK_IS_FILTER_LIST_MODEL (self), FALSE); + + return self->filter_func != NULL; +} + +/** + * gtk_filter_list_model_refilter: + * @self: a #GtkFilterListModel + * + * Causes @self to refilter all items in the model. + * + * Calling this function is necessary when data used by the filter + * function has changed. + **/ +void +gtk_filter_list_model_refilter (GtkFilterListModel *self) +{ + FilterNode *node; + guint i, first_change, last_change; + guint n_is_visible, n_was_visible; + gboolean visible; + + g_return_if_fail (GTK_IS_FILTER_LIST_MODEL (self)); + + if (self->items == NULL || self->model == NULL) + return; + + first_change = G_MAXUINT; + last_change = 0; + n_is_visible = 0; + n_was_visible = 0; + for (i = 0, node = gtk_css_rb_tree_get_first (self->items); + node != NULL; + i++, node = gtk_css_rb_tree_get_next (self->items, node)) + { + visible = gtk_filter_list_model_run_filter (self, i); + if (visible == node->visible) + { + if (visible) + { + n_is_visible++; + n_was_visible++; + } + continue; + } + + node->visible = visible; + gtk_css_rb_tree_mark_dirty (self->items, node); + first_change = MIN (n_is_visible, first_change); + if (visible) + n_is_visible++; + else + n_was_visible++; + last_change = MAX (n_is_visible, last_change); + } + + if (first_change <= last_change) + { + g_list_model_items_changed (G_LIST_MODEL (self), + first_change, + last_change - first_change + n_was_visible - n_is_visible, + last_change - first_change); + } +} + diff --git a/gtk/gtkfilterlistmodel.h b/gtk/gtkfilterlistmodel.h new file mode 100644 index 0000000000..f19607fd70 --- /dev/null +++ b/gtk/gtkfilterlistmodel.h @@ -0,0 +1,67 @@ +/* + * 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_FILTER_LIST_MODEL_H__ +#define __GTK_FILTER_LIST_MODEL_H__ + + +#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION) +#error "Only <gtk/gtk.h> can be included directly." +#endif + +#include <gio/gio.h> +#include <gtk/gtkwidget.h> + + +G_BEGIN_DECLS + +#define GTK_TYPE_FILTER_LIST_MODEL (gtk_filter_list_model_get_type ()) + +GDK_AVAILABLE_IN_ALL +G_DECLARE_FINAL_TYPE (GtkFilterListModel, gtk_filter_list_model, GTK, FILTER_LIST_MODEL, GObject) + +typedef gboolean (* GtkFilterListModelFilterFunc) (gpointer item, gpointer data); + +GDK_AVAILABLE_IN_ALL +GtkFilterListModel * gtk_filter_list_model_new (GListModel *model, + GtkFilterListModelFilterFunc filter_func, + gpointer user_data, + GDestroyNotify user_destroy); +GDK_AVAILABLE_IN_ALL +GtkFilterListModel * gtk_filter_list_model_new_for_type (GType item_type); + +GDK_AVAILABLE_IN_ALL +void gtk_filter_list_model_set_filter_func (GtkFilterListModel *self, + GtkFilterListModelFilterFunc filter_func, + gpointer user_data, + GDestroyNotify user_destroy); +GDK_AVAILABLE_IN_ALL +void gtk_filter_list_model_set_model (GtkFilterListModel *self, + GListModel *model); +GDK_AVAILABLE_IN_ALL +GListModel * gtk_filter_list_model_get_model (GtkFilterListModel *self); +GDK_AVAILABLE_IN_ALL +gboolean gtk_filter_list_model_has_filter (GtkFilterListModel *self); + +GDK_AVAILABLE_IN_ALL +void gtk_filter_list_model_refilter (GtkFilterListModel *self); + +G_END_DECLS + +#endif /* __GTK_FILTER_LIST_MODEL_H__ */ diff --git a/gtk/meson.build b/gtk/meson.build index d191922f5b..cd962dc411 100644 --- a/gtk/meson.build +++ b/gtk/meson.build @@ -229,6 +229,7 @@ gtk_public_sources = files([ 'gtkfilechoosernative.c', 'gtkfilechooserwidget.c', 'gtkfilefilter.c', + 'gtkfilterlistmodel.c', 'gtkfixed.c', 'gtkflowbox.c', 'gtkfontbutton.c', @@ -472,6 +473,7 @@ gtk_public_headers = files([ 'gtkfilechoosernative.h', 'gtkfilechooserwidget.h', 'gtkfilefilter.h', + 'gtkfilterlistmodel.h', 'gtkfixed.h', 'gtkflowbox.h', 'gtkfontbutton.h', diff --git a/testsuite/gtk/defaultvalue.c b/testsuite/gtk/defaultvalue.c index 4b559a0d23..f4c335037f 100644 --- a/testsuite/gtk/defaultvalue.c +++ b/testsuite/gtk/defaultvalue.c @@ -108,6 +108,14 @@ test_type (gconstpointer data) instance = G_OBJECT (g_object_ref (gdk_surface_new_popup (display, &(GdkRectangle) { 0, 0, 100, 100 }))); } + else if (g_type_is_a (type, GTK_TYPE_FILTER_LIST_MODEL)) + { + GListStore *list_store = g_list_store_new (G_TYPE_OBJECT); + instance = g_object_new (type, + "model", list_store, + NULL); + g_object_unref (list_store); + } else if (g_type_is_a (type, GDK_TYPE_CLIPBOARD) || g_str_equal (g_type_name (type), "GdkX11Cursor")) instance = g_object_new (type, "display", display, NULL); @@ -230,6 +238,10 @@ G_GNUC_BEGIN_IGNORE_DEPRECATIONS G_GNUC_END_IGNORE_DEPRECATIONS + if (g_type_is_a (type, GTK_TYPE_FILTER_LIST_MODEL) && + strcmp (pspec->name, "model") == 0) + continue; + /* This is set in init() */ if (g_type_is_a (type, GTK_TYPE_FONT_CHOOSER_WIDGET) && strcmp (pspec->name, "tweak-action") == 0) diff --git a/testsuite/gtk/filterlistmodel.c b/testsuite/gtk/filterlistmodel.c new file mode 100644 index 0000000000..1e259789dc --- /dev/null +++ b/testsuite/gtk/filterlistmodel.c @@ -0,0 +1,324 @@ +/* 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 +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); +} + +#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 GtkFilterListModel * +new_model (guint size, + GtkFilterListModelFilterFunc filter_func, + gpointer data) +{ + GtkFilterListModel *result; + GString *changes; + + result = gtk_filter_list_model_new (G_LIST_MODEL (new_store (1, size, 1)), filter_func, data, NULL); + 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 gboolean +is_smaller_than (gpointer item, + gpointer data) +{ + return g_object_get_qdata (item, number_quark) < data; +} + +static gboolean +is_larger_than (gpointer item, + gpointer data) +{ + return g_object_get_qdata (item, number_quark) > data; +} + +static gboolean +is_near (gpointer item, + gpointer data) +{ + return ABS (g_object_get_qdata (item, number_quark) - data) <= 2; +} + +static gboolean +is_not_near (gpointer item, + gpointer data) +{ + return ABS (g_object_get_qdata (item, number_quark) - data) > 2; +} + +static void +test_create (void) +{ + GtkFilterListModel *filter; + + filter = new_model (10, NULL, NULL); + assert_model (filter, "1 2 3 4 5 6 7 8 9 10"); + assert_changes (filter, ""); + g_object_unref (filter); + + filter = new_model (10, is_smaller_than, GUINT_TO_POINTER (20)); + assert_model (filter, "1 2 3 4 5 6 7 8 9 10"); + assert_changes (filter, ""); + g_object_unref (filter); + + filter = new_model (10, is_smaller_than, GUINT_TO_POINTER (7)); + assert_model (filter, "1 2 3 4 5 6"); + assert_changes (filter, ""); + g_object_unref (filter); + + filter = new_model (10, is_smaller_than, GUINT_TO_POINTER (0)); + assert_model (filter, ""); + assert_changes (filter, ""); + g_object_unref (filter); +} + +static void +test_empty_set_filter_func (void) +{ + GtkFilterListModel *filter; + + filter = new_model (10, NULL, NULL); + gtk_filter_list_model_set_filter_func (filter, is_smaller_than, GUINT_TO_POINTER (20), NULL); + assert_model (filter, "1 2 3 4 5 6 7 8 9 10"); + assert_changes (filter, ""); + g_object_unref (filter); + + filter = new_model (10, NULL, NULL); + gtk_filter_list_model_set_filter_func (filter, is_smaller_than, GUINT_TO_POINTER (7), NULL); + assert_model (filter, "1 2 3 4 5 6"); + assert_changes (filter, "6-4"); + g_object_unref (filter); + + filter = new_model (10, NULL, NULL); + gtk_filter_list_model_set_filter_func (filter, is_smaller_than, GUINT_TO_POINTER (0), NULL); + assert_model (filter, ""); + assert_changes (filter, "0-10"); + g_object_unref (filter); + + filter = new_model (10, NULL, NULL); + gtk_filter_list_model_set_filter_func (filter, is_larger_than, GUINT_TO_POINTER (0), NULL); + assert_model (filter, "1 2 3 4 5 6 7 8 9 10"); + assert_changes (filter, ""); + g_object_unref (filter); + + filter = new_model (10, NULL, NULL); + gtk_filter_list_model_set_filter_func (filter, is_larger_than, GUINT_TO_POINTER (3), NULL); + assert_model (filter, "4 5 6 7 8 9 10"); + assert_changes (filter, "0-3"); + g_object_unref (filter); + + filter = new_model (10, NULL, NULL); + gtk_filter_list_model_set_filter_func (filter, is_larger_than, GUINT_TO_POINTER (20), NULL); + assert_model (filter, ""); + assert_changes (filter, "0-10"); + g_object_unref (filter); + + filter = new_model (10, NULL, NULL); + gtk_filter_list_model_set_filter_func (filter, is_near, GUINT_TO_POINTER (5), NULL); + assert_model (filter, "3 4 5 6 7"); + assert_changes (filter, "0-10+5"); + g_object_unref (filter); + + filter = new_model (10, NULL, NULL); + gtk_filter_list_model_set_filter_func (filter, is_not_near, GUINT_TO_POINTER (5), NULL); + assert_model (filter, "1 2 8 9 10"); + assert_changes (filter, "2-5"); + g_object_unref (filter); +} + +static void +test_change_filter_func (void) +{ + GtkFilterListModel *filter; + + filter = new_model (10, is_not_near, GUINT_TO_POINTER (5)); + assert_model (filter, "1 2 8 9 10"); + assert_changes (filter, ""); + + gtk_filter_list_model_set_filter_func (filter, is_not_near, GUINT_TO_POINTER (6), NULL); + assert_model (filter, "1 2 3 9 10"); + assert_changes (filter, "2-1+1"); + + gtk_filter_list_model_set_filter_func (filter, is_not_near, GUINT_TO_POINTER (9), NULL); + assert_model (filter, "1 2 3 4 5 6"); + assert_changes (filter, "3-2+3"); + + gtk_filter_list_model_set_filter_func (filter, is_smaller_than, GUINT_TO_POINTER (6), NULL); + assert_model (filter, "1 2 3 4 5"); + assert_changes (filter, "-5"); + + gtk_filter_list_model_set_filter_func (filter, is_larger_than, GUINT_TO_POINTER (4), NULL); + assert_model (filter, "5 6 7 8 9 10"); + assert_changes (filter, "0-5+6"); + + gtk_filter_list_model_set_filter_func (filter, is_not_near, GUINT_TO_POINTER (2), NULL); + assert_model (filter, "5 6 7 8 9 10"); + assert_changes (filter, ""); + + gtk_filter_list_model_set_filter_func (filter, is_not_near, GUINT_TO_POINTER (4), NULL); + assert_model (filter, "1 7 8 9 10"); + assert_changes (filter, "0-2+1"); + + g_object_unref (filter); +} + +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 ("/filterlistmodel/create", test_create); + g_test_add_func ("/filterlistmodel/empty_set_filter_func", test_empty_set_filter_func); + g_test_add_func ("/filterlistmodel/change_filter_func", test_change_filter_func); + + return g_test_run (); +} diff --git a/testsuite/gtk/meson.build b/testsuite/gtk/meson.build index 963355cfbe..de1bcb15a7 100644 --- a/testsuite/gtk/meson.build +++ b/testsuite/gtk/meson.build @@ -19,6 +19,7 @@ tests = [ ['cssprovider'], ['defaultvalue'], ['entry'], + ['filterlistmodel'], ['firefox-stylecontext'], ['floating'], ['focus'], diff --git a/testsuite/gtk/notify.c b/testsuite/gtk/notify.c index 13d5954fb5..6474e2656f 100644 --- a/testsuite/gtk/notify.c +++ b/testsuite/gtk/notify.c @@ -430,6 +430,14 @@ test_type (gconstpointer data) NULL); gdk_content_formats_unref (formats); } + else if (g_type_is_a (type, GTK_TYPE_FILTER_LIST_MODEL)) + { + GListStore *list_store = g_list_store_new (G_TYPE_OBJECT); + instance = g_object_new (type, + "model", list_store, + NULL); + g_object_unref (list_store); + } else instance = g_object_new (type, NULL); diff --git a/testsuite/gtk/objects-finalize.c b/testsuite/gtk/objects-finalize.c index 9e91fcca5f..cced1ccd46 100644 --- a/testsuite/gtk/objects-finalize.c +++ b/testsuite/gtk/objects-finalize.c @@ -64,6 +64,14 @@ test_finalize_object (gconstpointer data) NULL); gdk_content_formats_unref (formats); } + else if (g_type_is_a (test_type, GTK_TYPE_FILTER_LIST_MODEL)) + { + GListStore *list_store = g_list_store_new (G_TYPE_OBJECT); + object = g_object_new (test_type, + "model", list_store, + NULL); + g_object_unref (list_store); + } else object = g_object_new (test_type, NULL); g_assert (G_IS_OBJECT (object)); |