From dbaec8756d51258c37ea1991f83625bb1f0a4593 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Sat, 16 Nov 2019 21:46:57 +0100 Subject: [PATCH] filter: Add GtkAnyFilter --- gtk/gtkfilters.c | 165 +++++++++++++++++++++++++++++++++++ gtk/gtkfilters.h | 14 +++ testsuite/gtk/filter.c | 177 ++++++++++++++++++++++++++++++++++++++ testsuite/gtk/meson.build | 1 + 4 files changed, 357 insertions(+) create mode 100644 testsuite/gtk/filter.c diff --git a/gtk/gtkfilters.c b/gtk/gtkfilters.c index 943b59390a..9a0157b466 100644 --- a/gtk/gtkfilters.c +++ b/gtk/gtkfilters.c @@ -24,6 +24,8 @@ #include "gtkintl.h" #include "gtktypebuiltins.h" +/*** CUSTOM FILTER ***/ + struct _GtkCustomFilter { GtkFilter parent_instance; @@ -101,3 +103,166 @@ gtk_custom_filter_new (GtkCustomFilterFunc match_func, return GTK_FILTER (result); } +/*** ANY FILTER ***/ + +struct _GtkAnyFilter +{ + GtkFilter parent_instance; + + GSequence *filters; +}; + +static GType +gtk_any_filter_get_item_type (GListModel *list) +{ + return GTK_TYPE_FILTER; +} + +static guint +gtk_any_filter_get_n_items (GListModel *list) +{ + GtkAnyFilter *self = GTK_ANY_FILTER (list); + + return g_sequence_get_length (self->filters); +} + +static gpointer +gtk_any_filter_get_item (GListModel *list, + guint position) +{ + GtkAnyFilter *self = GTK_ANY_FILTER (list); + GSequenceIter *iter; + + iter = g_sequence_get_iter_at_pos (self->filters, position); + + if (g_sequence_iter_is_end (iter)) + return NULL; + else + return g_object_ref (g_sequence_get (iter)); +} + +static void +gtk_any_filter_list_model_init (GListModelInterface *iface) +{ + iface->get_item_type = gtk_any_filter_get_item_type; + iface->get_n_items = gtk_any_filter_get_n_items; + iface->get_item = gtk_any_filter_get_item; +} + +G_DEFINE_TYPE_WITH_CODE (GtkAnyFilter, gtk_any_filter, GTK_TYPE_FILTER, + G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, gtk_any_filter_list_model_init)) + +static gboolean +gtk_any_filter_match (GtkFilter *filter, + gpointer item) +{ + GtkAnyFilter *self = GTK_ANY_FILTER (filter); + GSequenceIter *iter; + + for (iter = g_sequence_get_begin_iter (self->filters); + !g_sequence_iter_is_end (iter); + iter = g_sequence_iter_next (iter)) + { + GtkFilter *child = g_sequence_get (iter); + + if (gtk_filter_match (child, item)) + return TRUE; + } + + return FALSE; +} + +static void +gtk_any_filter_dispose (GObject *object) +{ + GtkAnyFilter *self = GTK_ANY_FILTER (object); + + g_clear_pointer (&self->filters, g_sequence_free); + + G_OBJECT_CLASS (gtk_any_filter_parent_class)->dispose (object); +} + +static void +gtk_any_filter_class_init (GtkAnyFilterClass *class) +{ + GtkFilterClass *filter_class = GTK_FILTER_CLASS (class); + GObjectClass *object_class = G_OBJECT_CLASS (class); + + filter_class->match = gtk_any_filter_match; + + object_class->dispose = gtk_any_filter_dispose; +} + +static void +gtk_any_filter_init (GtkAnyFilter *self) +{ + self->filters = g_sequence_new (g_object_unref); + + gtk_filter_changed (GTK_FILTER (self), GTK_FILTER_CHANGE_MATCH_NONE); +} + +/** + * gtk_any_filter_new: + * + * Creates a new "any" filter. + * + * This filter matches an item if any of the filters added to it + * matches the item. + * In particular, this means that if no filter has been added to + * it, the filter matches no item. + * + * Returns: a new #GtkFilter + **/ +GtkFilter * +gtk_any_filter_new (void) +{ + return g_object_new (GTK_TYPE_ANY_FILTER, NULL); +} + +/** + * gtk_any_filter_append: + * @self: a #GtkAnyFilter + * @filter: (tranfer none): A new filter to use + * + * Adds a @filter to @self to use for matching. + **/ +void +gtk_any_filter_append (GtkAnyFilter *self, + GtkFilter *filter) +{ + g_return_if_fail (GTK_IS_ANY_FILTER (self)); + g_return_if_fail (GTK_IS_FILTER (filter)); + + g_sequence_append (self->filters, g_object_ref (filter)); + + gtk_filter_changed (GTK_FILTER (self), GTK_FILTER_CHANGE_LESS_STRICT); +} + +/** + * gtk_any_filter_remove: + * @self: a #GtkAnyFilter + * @position: position of filter to remove + * + * Removes the filter at the given @position from the list of filters used + * by @self. + * If @position is larger than the number of filters, nothing happens and + * the function returns. + **/ +void +gtk_any_filter_remove (GtkAnyFilter *self, + guint position) +{ + GSequenceIter *iter; + guint length; + + length = g_sequence_get_length (self->filters); + if (position >= length) + return; + + iter = g_sequence_get_iter_at_pos (self->filters, position); + g_sequence_remove (iter); + + gtk_filter_changed (GTK_FILTER (self), length == 1 + ? GTK_FILTER_CHANGE_MATCH_NONE + : GTK_FILTER_CHANGE_MORE_STRICT); +} diff --git a/gtk/gtkfilters.h b/gtk/gtkfilters.h index b68fa469fb..948bdf5a94 100644 --- a/gtk/gtkfilters.h +++ b/gtk/gtkfilters.h @@ -49,6 +49,20 @@ GtkFilter * gtk_custom_filter_new (GtkCustomFilter gpointer user_data, GDestroyNotify user_destroy); +#define GTK_TYPE_ANY_FILTER (gtk_any_filter_get_type ()) +GDK_AVAILABLE_IN_ALL +G_DECLARE_FINAL_TYPE (GtkAnyFilter, gtk_any_filter, GTK, ANY_FILTER, GtkFilter) +GDK_AVAILABLE_IN_ALL +GtkFilter * gtk_any_filter_new (void); +GDK_AVAILABLE_IN_ALL +void gtk_any_filter_append (GtkAnyFilter *self, + GtkFilter *filter); +GDK_AVAILABLE_IN_ALL +void gtk_any_filter_remove (GtkAnyFilter *self, + guint position); + + + G_END_DECLS diff --git a/testsuite/gtk/filter.c b/testsuite/gtk/filter.c new file mode 100644 index 0000000000..af5505e604 --- /dev/null +++ b/testsuite/gtk/filter.c @@ -0,0 +1,177 @@ +/* GtkRBTree tests. + * + * Copyright (C) 2011, Red Hat, Inc. + * Authors: 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 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 . + */ + +#include + +#include + +static GQuark number_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 + +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 GtkFilterListModel * +new_model (guint size, + GtkFilter *filter) +{ + GtkFilterListModel *result; + + result = gtk_filter_list_model_new (G_LIST_MODEL (new_store (1, size, 1)), filter); + + return result; +} + +static gboolean +divisible_by (gpointer item, + gpointer data) +{ + return GPOINTER_TO_UINT (g_object_get_qdata (item, number_quark)) % GPOINTER_TO_UINT (data) == 0; +} + +static void +test_simple (void) +{ + GtkFilterListModel *model; + GtkFilter *filter; + + filter = gtk_custom_filter_new (divisible_by, GUINT_TO_POINTER (3), NULL); + model = new_model (20, filter); + g_object_unref (filter); + assert_model (model, "3 6 9 12 15 18"); + g_object_unref (model); +} + +static void +test_any_simple (void) +{ + GtkFilterListModel *model; + GtkFilter *any, *filter1, *filter2; + + any = gtk_any_filter_new (); + filter1 = gtk_custom_filter_new (divisible_by, GUINT_TO_POINTER (3), NULL); + filter2 = gtk_custom_filter_new (divisible_by, GUINT_TO_POINTER (5), NULL); + + model = new_model (20, any); + assert_model (model, ""); + + gtk_any_filter_append (GTK_ANY_FILTER (any), filter1); + assert_model (model, "3 6 9 12 15 18"); + + gtk_any_filter_append (GTK_ANY_FILTER (any), filter2); + assert_model (model, "3 5 6 9 10 12 15 18 20"); + + gtk_any_filter_remove (GTK_ANY_FILTER (any), 0); + assert_model (model, "5 10 15 20"); + + /* doesn't exist */ + gtk_any_filter_remove (GTK_ANY_FILTER (any), 10); + assert_model (model, "5 10 15 20"); + + gtk_any_filter_remove (GTK_ANY_FILTER (any), 0); + assert_model (model, ""); + + g_object_unref (model); + g_object_unref (filter2); + g_object_unref (filter1); + g_object_unref (any); +} + +int +main (int argc, char *argv[]) +{ + g_test_init (&argc, &argv, NULL); + setlocale (LC_ALL, "C"); + + number_quark = g_quark_from_static_string ("Hell and fire was spawned to be released."); + + g_test_add_func ("/filter/simple", test_simple); + g_test_add_func ("/filter/any/simple", test_any_simple); + + return g_test_run (); +} diff --git a/testsuite/gtk/meson.build b/testsuite/gtk/meson.build index b02d3f51ce..e2eca8162c 100644 --- a/testsuite/gtk/meson.build +++ b/testsuite/gtk/meson.build @@ -26,6 +26,7 @@ tests = [ ['rbtree-crash', ['../../gtk/gtkrbtree.c'], ['-DGTK_COMPILATION', '-UG_ENABLE_DEBUG']], ['defaultvalue'], ['entry'], + ['filter'], ['filterlistmodel'], ['flattenlistmodel'], ['firefox-stylecontext'],