From 01aa8c04c2abb05496664e6c7c127851d0136c76 Mon Sep 17 00:00:00 2001 From: Evangelos Ribeiro Tzaras Date: Wed, 6 Jul 2022 02:21:17 +0200 Subject: [PATCH] Add various gtk list models This is an exact copy from GTK4. as grabbed by chattys commit 1ed5084fb965908e3ee0304781b0de06479c869b Slightly adapted for Calls. Based on GTKs 01bd4cc4e18a1ea697fe61791ba710d0d55e8290 --- src/gtklistmodels/gtk.h | 8 + src/gtklistmodels/gtkcustomfilter.c | 157 ++++ src/gtklistmodels/gtkcustomfilter.h | 61 ++ src/gtklistmodels/gtkcustomsorter.c | 165 +++++ src/gtklistmodels/gtkcustomsorter.h | 50 ++ src/gtklistmodels/gtkfilter.c | 181 +++++ src/gtklistmodels/gtkfilter.h | 122 ++++ src/gtklistmodels/gtkfilterlistmodel.c | 916 ++++++++++++++++++++++++ src/gtklistmodels/gtkfilterlistmodel.h | 59 ++ src/gtklistmodels/gtkflattenlistmodel.c | 541 ++++++++++++++ src/gtklistmodels/gtkflattenlistmodel.h | 51 ++ src/gtklistmodels/gtkintl.h | 22 + src/gtklistmodels/gtkprivate.h | 38 + src/gtklistmodels/gtkrbtree.c | 800 +++++++++++++++++++++ src/gtklistmodels/gtkrbtreeprivate.h | 75 ++ src/gtklistmodels/gtkslicelistmodel.c | 529 ++++++++++++++ src/gtklistmodels/gtkslicelistmodel.h | 65 ++ src/gtklistmodels/gtksorter.c | 207 ++++++ src/gtklistmodels/gtksorter.h | 131 ++++ src/gtklistmodels/gtksortlistmodel.c | 595 +++++++++++++++ src/gtklistmodels/gtksortlistmodel.h | 61 ++ src/gtklistmodels/gtktypebuiltins.c | 89 +++ src/gtklistmodels/gtktypebuiltins.h | 18 + 23 files changed, 4941 insertions(+) create mode 100644 src/gtklistmodels/gtk.h create mode 100644 src/gtklistmodels/gtkcustomfilter.c create mode 100644 src/gtklistmodels/gtkcustomfilter.h create mode 100644 src/gtklistmodels/gtkcustomsorter.c create mode 100644 src/gtklistmodels/gtkcustomsorter.h create mode 100644 src/gtklistmodels/gtkfilter.c create mode 100644 src/gtklistmodels/gtkfilter.h create mode 100644 src/gtklistmodels/gtkfilterlistmodel.c create mode 100644 src/gtklistmodels/gtkfilterlistmodel.h create mode 100644 src/gtklistmodels/gtkflattenlistmodel.c create mode 100644 src/gtklistmodels/gtkflattenlistmodel.h create mode 100644 src/gtklistmodels/gtkintl.h create mode 100644 src/gtklistmodels/gtkprivate.h create mode 100644 src/gtklistmodels/gtkrbtree.c create mode 100644 src/gtklistmodels/gtkrbtreeprivate.h create mode 100644 src/gtklistmodels/gtkslicelistmodel.c create mode 100644 src/gtklistmodels/gtkslicelistmodel.h create mode 100644 src/gtklistmodels/gtksorter.c create mode 100644 src/gtklistmodels/gtksorter.h create mode 100644 src/gtklistmodels/gtksortlistmodel.c create mode 100644 src/gtklistmodels/gtksortlistmodel.h create mode 100644 src/gtklistmodels/gtktypebuiltins.c create mode 100644 src/gtklistmodels/gtktypebuiltins.h diff --git a/src/gtklistmodels/gtk.h b/src/gtklistmodels/gtk.h new file mode 100644 index 0000000..5bde461 --- /dev/null +++ b/src/gtklistmodels/gtk.h @@ -0,0 +1,8 @@ +#include "gtksorter.h" +#include "gtkcustomsorter.h" +#include "gtkfilter.h" +#include "gtkcustomfilter.h" +#include "gtksortlistmodel.h" +#include "gtkfilterlistmodel.h" +#include "gtkflattenlistmodel.h" +#include "gtkslicelistmodel.h" diff --git a/src/gtklistmodels/gtkcustomfilter.c b/src/gtklistmodels/gtkcustomfilter.c new file mode 100644 index 0000000..7bd72e3 --- /dev/null +++ b/src/gtklistmodels/gtkcustomfilter.c @@ -0,0 +1,157 @@ +/* + * Copyright © 2019 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 . + * + * Authors: Benjamin Otte + */ + +/* #include "config.h" */ + +#include "gtkcustomfilter.h" + +#include "gtkintl.h" +#include "gtktypebuiltins.h" + +/** + * SECTION:gtkcustomfilter + * @Title: GtkCustomFilter + * @Short_description: Filtering with callbacks + * + * #GtkCustomFilter is a #GtkFilter that uses a callback to determine whether + * to include an item or not. + */ +struct _GtkCustomFilter +{ + GtkFilter parent_instance; + + GtkCustomFilterFunc match_func; + gpointer user_data; + GDestroyNotify user_destroy; +}; + +G_DEFINE_TYPE (GtkCustomFilter, gtk_custom_filter, GTK_TYPE_FILTER) + +static gboolean +gtk_custom_filter_match (GtkFilter *filter, + gpointer item) +{ + GtkCustomFilter *self = GTK_CUSTOM_FILTER (filter); + + if (!self->match_func) + return TRUE; + + return self->match_func (item, self->user_data); +} + +static GtkFilterMatch +gtk_custom_filter_get_strictness (GtkFilter *filter) +{ + GtkCustomFilter *self = GTK_CUSTOM_FILTER (filter); + + if (!self->match_func) + return GTK_FILTER_MATCH_ALL; + + return GTK_FILTER_MATCH_SOME; +} + +static void +gtk_custom_filter_dispose (GObject *object) +{ + GtkCustomFilter *self = GTK_CUSTOM_FILTER (object); + + if (self->user_destroy) + self->user_destroy (self->user_data); + + G_OBJECT_CLASS (gtk_custom_filter_parent_class)->dispose (object); +} + +static void +gtk_custom_filter_class_init (GtkCustomFilterClass *class) +{ + GtkFilterClass *filter_class = GTK_FILTER_CLASS (class); + GObjectClass *object_class = G_OBJECT_CLASS (class); + + filter_class->match = gtk_custom_filter_match; + filter_class->get_strictness = gtk_custom_filter_get_strictness; + + object_class->dispose = gtk_custom_filter_dispose; +} + +static void +gtk_custom_filter_init (GtkCustomFilter *self) +{ +} + +/** + * gtk_custom_filter_new: + * @match_func: (nullable): function to filter items + * @user_data: (nullable): user data to pass to @match_func + * @user_destroy: destory notify + * + * Creates a new filter using the given @match_func to filter + * items. + * + * If the filter func changes its filtering behavior, + * gtk_filter_changed() needs to be called. + * + * Returns: a new #GtkFilter + **/ +GtkFilter * +gtk_custom_filter_new (GtkCustomFilterFunc match_func, + gpointer user_data, + GDestroyNotify user_destroy) +{ + GtkCustomFilter *result; + + result = g_object_new (GTK_TYPE_CUSTOM_FILTER, NULL); + + gtk_custom_filter_set_filter_func (result, match_func, user_data, user_destroy); + + return GTK_FILTER (result); +} + +/** + * gtk_custom_filter_set_filter_func: + * @self: a #GtkCustomFilter + * @match_func: (nullable): function to filter items + * @user_data: (nullable): user data to pass to @match_func + * @user_destroy: destory notify + * + * Sets (or unsets) the function used for filtering items. + * + * If the filter func changes its filtering behavior, + * gtk_filter_changed() needs to be called. + * + * If a previous function was set, its @user_destroy will be + * called now. + **/ +void +gtk_custom_filter_set_filter_func (GtkCustomFilter *self, + GtkCustomFilterFunc match_func, + gpointer user_data, + GDestroyNotify user_destroy) +{ + g_return_if_fail (GTK_IS_CUSTOM_FILTER (self)); + g_return_if_fail (match_func || (user_data == NULL && !user_destroy)); + + if (self->user_destroy) + self->user_destroy (self->user_data); + + self->match_func = match_func; + self->user_data = user_data; + self->user_destroy = user_destroy; + + gtk_filter_changed (GTK_FILTER (self), GTK_FILTER_CHANGE_DIFFERENT); +} diff --git a/src/gtklistmodels/gtkcustomfilter.h b/src/gtklistmodels/gtkcustomfilter.h new file mode 100644 index 0000000..90f6751 --- /dev/null +++ b/src/gtklistmodels/gtkcustomfilter.h @@ -0,0 +1,61 @@ +/* + * Copyright © 2019 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 . + * + * Authors: Benjamin Otte + */ + +#ifndef __GTK_CUSTOM_FILTER_H__ +#define __GTK_CUSTOM_FILTER_H__ + +#define GTK_COMPILATION +#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION) +#error "Only can be included directly." +#endif + +#include "gtkfilter.h" + +G_BEGIN_DECLS + +/** + * GtkCustomFilterFunc: + * @item: (type GObject): The item to be matched + * @user_data: user data + * + * User function that is called to determine if the @item should be matched. + * If the filter matches the item, this function must return %TRUE. If the + * item should be filtered out, %FALSE must be returned. + * + * Returns: %TRUE to keep the item around + */ +typedef gboolean (* GtkCustomFilterFunc) (gpointer item, gpointer user_data); + +#define GTK_TYPE_CUSTOM_FILTER (gtk_custom_filter_get_type ()) +GDK_AVAILABLE_IN_ALL +G_DECLARE_FINAL_TYPE (GtkCustomFilter, gtk_custom_filter, GTK, CUSTOM_FILTER, GtkFilter) +GDK_AVAILABLE_IN_ALL +GtkFilter * gtk_custom_filter_new (GtkCustomFilterFunc match_func, + gpointer user_data, + GDestroyNotify user_destroy); + +GDK_AVAILABLE_IN_ALL +void gtk_custom_filter_set_filter_func (GtkCustomFilter *self, + GtkCustomFilterFunc match_func, + gpointer user_data, + GDestroyNotify user_destroy); + +G_END_DECLS + +#endif /* __GTK_CUSTOM_FILTER_H__ */ diff --git a/src/gtklistmodels/gtkcustomsorter.c b/src/gtklistmodels/gtkcustomsorter.c new file mode 100644 index 0000000..e70c6c8 --- /dev/null +++ b/src/gtklistmodels/gtkcustomsorter.c @@ -0,0 +1,165 @@ +/* + * Copyright © 2019 Matthias Clasen + * + * 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 . + * + * Authors: Matthias Clasen + */ + +/* #include "config.h" */ + +#include "gtkcustomsorter.h" + +#include "gtkintl.h" +#include "gtktypebuiltins.h" + +/** + * SECTION:gtkcustomsorter + * @Title: GtkCustomSorter + * @Short_description: Sorting with a callback + * + * GtkCustomSorter is a #GtkSorter implementation that sorts + * via a traditional #GCompareDataFunc callback. + */ +struct _GtkCustomSorter +{ + GtkSorter parent_instance; + + GCompareDataFunc sort_func; + gpointer user_data; + GDestroyNotify user_destroy; +}; + +G_DEFINE_TYPE (GtkCustomSorter, gtk_custom_sorter, GTK_TYPE_SORTER) + +static inline GtkOrdering +gtk_ordering_from_cmpfunc (int cmpfunc_result) +{ + return (GtkOrdering) ((cmpfunc_result > 0) - (cmpfunc_result < 0)); +} + +static GtkOrdering +gtk_custom_sorter_compare (GtkSorter *sorter, + gpointer item1, + gpointer item2) +{ + GtkCustomSorter *self = GTK_CUSTOM_SORTER (sorter); + + if (!self->sort_func) + return GTK_ORDERING_EQUAL; + + return gtk_ordering_from_cmpfunc (self->sort_func (item1, item2, self->user_data)); +} + +static GtkSorterOrder +gtk_custom_sorter_get_order (GtkSorter *sorter) +{ + GtkCustomSorter *self = GTK_CUSTOM_SORTER (sorter); + + if (!self->sort_func) + return GTK_SORTER_ORDER_NONE; + + return GTK_SORTER_ORDER_PARTIAL; +} + +static void +gtk_custom_sorter_dispose (GObject *object) +{ + GtkCustomSorter *self = GTK_CUSTOM_SORTER (object); + + if (self->user_destroy) + self->user_destroy (self->user_data); + + self->sort_func = NULL; + self->user_destroy = NULL; + self->user_data = NULL; + + G_OBJECT_CLASS (gtk_custom_sorter_parent_class)->dispose (object); +} + +static void +gtk_custom_sorter_class_init (GtkCustomSorterClass *class) +{ + GtkSorterClass *sorter_class = GTK_SORTER_CLASS (class); + GObjectClass *object_class = G_OBJECT_CLASS (class); + + sorter_class->compare = gtk_custom_sorter_compare; + sorter_class->get_order = gtk_custom_sorter_get_order; + + object_class->dispose = gtk_custom_sorter_dispose; +} + +static void +gtk_custom_sorter_init (GtkCustomSorter *self) +{ +} + +/** + * gtk_custom_sorter_new: + * @sort_func: the #GCompareDataFunc to use for sorting + * @user_data: (nullable): user data to pass to @sort_func + * @user_destroy: (nullable): destroy notify for @user_data + * + * Creates a new #GtkSorter that works by calling + * @sort_func to compare items. + * + * Returns: a new #GTkSorter + */ +GtkSorter * +gtk_custom_sorter_new (GCompareDataFunc sort_func, + gpointer user_data, + GDestroyNotify user_destroy) +{ + GtkCustomSorter *sorter; + + sorter = g_object_new (GTK_TYPE_CUSTOM_SORTER, NULL); + + gtk_custom_sorter_set_sort_func (sorter, sort_func, user_data, user_destroy); + + return GTK_SORTER (sorter); +} + +/** + * gtk_custom_sorter_set_sort_func: + * @self: a #GtkCustomSorter + * @sort_func: (nullable): function to sort items + * @user_data: (nullable): user data to pass to @match_func + * @user_destroy: destory notify + * + * Sets (or unsets) the function used for sorting items. + * + * If the sort func changes its sorting behavior, + * gtk_sorter_changed() needs to be called. + * + * If a previous function was set, its @user_destroy will be + * called now. + **/ +void +gtk_custom_sorter_set_sort_func (GtkCustomSorter *self, + GCompareDataFunc sort_func, + gpointer user_data, + GDestroyNotify user_destroy) +{ + g_return_if_fail (GTK_IS_CUSTOM_SORTER (self)); + g_return_if_fail (sort_func || (user_data == NULL && !user_destroy)); + + if (self->user_destroy) + self->user_destroy (self->user_data); + + self->sort_func = sort_func; + self->user_data = user_data; + self->user_destroy = user_destroy; + + gtk_sorter_changed (GTK_SORTER (self), GTK_SORTER_CHANGE_DIFFERENT); +} diff --git a/src/gtklistmodels/gtkcustomsorter.h b/src/gtklistmodels/gtkcustomsorter.h new file mode 100644 index 0000000..1adf56a --- /dev/null +++ b/src/gtklistmodels/gtkcustomsorter.h @@ -0,0 +1,50 @@ +/* + * Copyright © 2019 Matthias Clasen + * + * 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 . + * + * Authors: Matthias Clasen + */ + +#ifndef __GTK_CUSTOM_SORTER_H__ +#define __GTK_CUSTOM_SORTER_H__ + +#define GTK_COMPILATION +#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION) +#error "Only can be included directly." +#endif + +/* #include */ +#include "gtksorter.h" + +G_BEGIN_DECLS + +#define GTK_TYPE_CUSTOM_SORTER (gtk_custom_sorter_get_type ()) +GDK_AVAILABLE_IN_ALL +G_DECLARE_FINAL_TYPE (GtkCustomSorter, gtk_custom_sorter, GTK, CUSTOM_SORTER, GtkSorter) + +GDK_AVAILABLE_IN_ALL +GtkSorter * gtk_custom_sorter_new (GCompareDataFunc sort_func, + gpointer user_data, + GDestroyNotify user_destroy); + +GDK_AVAILABLE_IN_ALL +void gtk_custom_sorter_set_sort_func (GtkCustomSorter *self, + GCompareDataFunc sort_func, + gpointer user_data, + GDestroyNotify user_destroy); + +G_END_DECLS + +#endif /* __GTK_CUSTOM_SORTER_H__ */ diff --git a/src/gtklistmodels/gtkfilter.c b/src/gtklistmodels/gtkfilter.c new file mode 100644 index 0000000..0ece82d --- /dev/null +++ b/src/gtklistmodels/gtkfilter.c @@ -0,0 +1,181 @@ +/* + * Copyright © 2019 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 . + * + * Authors: Benjamin Otte + */ + +/* #include "config.h" */ + +#include "gtkfilter.h" + +#include "gtkintl.h" +#include "gtktypebuiltins.h" + +/** + * SECTION:gtkfilter + * @Title: GtkFilter + * @Short_description: Filtering items + * @See_also: #GtkFilerListModel + * + * #GtkFilter is the way to describe filters to be used in #GtkFilterListModel. + * + * The model will use a filter to determine if it should filter items or not + * by calling gtk_filter_match() for each item and only keeping the ones + * visible that the function returns %TRUE for. + * + * Filters may change what items they match through their lifetime. In that + * case, they can call gtk_filter_changed() which will emit the #GtkFilter:changed + * signal to notify that previous filter results are no longer valid and that + * items should be checked via gtk_filter_match() again. + * + * GTK provides various premade filter implementations for common filtering + * operations. These filters often include properties that can be linked to + * various widgets to easily allow searches. + * + * However, in particular for large lists or complex search methods, it is + * also possible to subclass #GtkFilter and provide one's own filter. + */ + +enum { + CHANGED, + LAST_SIGNAL +}; + +G_DEFINE_TYPE (GtkFilter, gtk_filter, G_TYPE_OBJECT) + +static guint signals[LAST_SIGNAL] = { 0 }; + +static gboolean +gtk_filter_default_match (GtkFilter *self, + gpointer item) +{ + g_critical ("Filter of type '%s' does not implement GtkFilter::match", G_OBJECT_TYPE_NAME (self)); + + return FALSE; +} + +static GtkFilterMatch +gtk_filter_default_get_strictness (GtkFilter *self) +{ + return GTK_FILTER_MATCH_SOME; +} + +static void +gtk_filter_class_init (GtkFilterClass *class) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (class); + + class->match = gtk_filter_default_match; + class->get_strictness = gtk_filter_default_get_strictness; + + /** + * GtkFilter:changed: + * @self: The #GtkFilter + * @change: how the filter changed + * + * This signal is emitted whenever the filter changed. Users of the filter + * should then check items again via gtk_filter_match(). + * + * #GtkFilterListModel handles this signal automatically. + * + * Depending on the @change parameter, not all items need to be changed, but + * only some. Refer to the #GtkFilterChange documentation for details. + */ + signals[CHANGED] = + g_signal_new (I_("changed"), + G_TYPE_FROM_CLASS (gobject_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__ENUM, + G_TYPE_NONE, 1, + GTK_TYPE_FILTER_CHANGE); + g_signal_set_va_marshaller (signals[CHANGED], + G_TYPE_FROM_CLASS (gobject_class), + g_cclosure_marshal_VOID__ENUMv); +} + +static void +gtk_filter_init (GtkFilter *self) +{ +} + +/** + * gtk_filter_match: + * @self: a #GtkFilter + * @item: (type GObject) (transfer none): The item to check + * + * Checks if the given @item is matched by the filter or not. + * + * Returns: %TRUE if the filter matches the item and a filter model should + * keep it, %FALSE if not. + */ +gboolean +gtk_filter_match (GtkFilter *self, + gpointer item) +{ + g_return_val_if_fail (GTK_IS_FILTER (self), FALSE); + g_return_val_if_fail (item != NULL, FALSE); + + return GTK_FILTER_GET_CLASS (self)->match (self, item); +} + +/** + * gtk_filter_get_strictness: + * @self: a #GtkFilter + * + * Gets the known strictness of @filters. If the strictness is not known, + * %GTK_FILTER_MATCH_SOME is returned. + * + * This value may change after emission of the GtkFilter:changed signal. + * + * This function is meant purely for optimization purposes, filters can + * choose to omit implementing it, but #GtkFilterListModel uses it. + * + * Returns: the strictness of @self + **/ +GtkFilterMatch +gtk_filter_get_strictness (GtkFilter *self) +{ + g_return_val_if_fail (GTK_IS_FILTER (self), GTK_FILTER_MATCH_SOME); + + return GTK_FILTER_GET_CLASS (self)->get_strictness (self); +} + +/** + * gtk_filter_changed: + * @self: a #GtkFilter + * @change: How the filter changed + * + * Emits the #GtkFilter:changed signal to notify all users of the filter that + * the filter changed. Users of the filter should then check items again via + * gtk_filter_match(). + * + * Depending on the @change parameter, not all items need to be changed, but + * only some. Refer to the #GtkFilterChange documentation for details. + * + * This function is intended for implementors of #GtkFilter subclasses and + * should not be called from other functions. + */ +void +gtk_filter_changed (GtkFilter *self, + GtkFilterChange change) +{ + g_return_if_fail (GTK_IS_FILTER (self)); + + g_signal_emit (self, signals[CHANGED], 0, change); +} + diff --git a/src/gtklistmodels/gtkfilter.h b/src/gtklistmodels/gtkfilter.h new file mode 100644 index 0000000..f34cc90 --- /dev/null +++ b/src/gtklistmodels/gtkfilter.h @@ -0,0 +1,122 @@ +/* + * Copyright © 2019 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 . + * + * Authors: Benjamin Otte + */ + +#ifndef __GTK_FILTER_H__ +#define __GTK_FILTER_H__ + +#define GTK_COMPILATION +#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION) +#error "Only can be included directly." +#endif + +#include + +G_BEGIN_DECLS + +/** + * GtkFilterMatch: + * @GTK_FILTER_MATCH_SOME: The filter matches some items, + * gtk_filter_match() may return %TRUE or %FALSE + * @GTK_FILTER_MATCH_NONE: The filter does not match any item, + * gtk_filter_match() will always return %FALSE. + * @GTK_FILTER_MATCH_ALL: The filter matches all items, + * gtk_filter_match() will alays return %TRUE. + * + * Describes the known strictness of a filter. + * + * Note that for filters where the strictness is not known, + * %@GTK_FILTER_MATCH_SOME is always an acceptable value, + * even if a filter does match all or no items. + */ +typedef enum { + GTK_FILTER_MATCH_SOME = 0, + GTK_FILTER_MATCH_NONE, + GTK_FILTER_MATCH_ALL +} GtkFilterMatch; + +/** + * GtkFilterChange: + * @GTK_FILTER_CHANGE_DIFFERENT: The filter change cannot be + * described with any of the other enumeration values. + * @GTK_FILTER_CHANGE_LESS_STRICT: The filter is less strict than + * it was before: All items that it used to return %TRUE for + * still return %TRUE, others now may, too. + * @GTK_FILTER_CHANGE_MORE_STRICT: The filter is more strict than + * it was before: All items that it used to return %FALSE for + * still return %FALSE, others now may, too. + * + * Describes changes in a filter in more detail and allows objects + * using the filter to optimize refiltering items. + * + * If you are writing an implementation and are not sure which + * value to pass, @GTK_FILTER_CHANGE_DIFFERENT is always a correct + * choice. + */ +typedef enum { + GTK_FILTER_CHANGE_DIFFERENT = 0, + GTK_FILTER_CHANGE_LESS_STRICT, + GTK_FILTER_CHANGE_MORE_STRICT, +} GtkFilterChange; + +#define GTK_TYPE_FILTER (gtk_filter_get_type ()) + +/** + * GtkFilter: + * + * The object describing a filter. + */ +GDK_AVAILABLE_IN_ALL +G_DECLARE_DERIVABLE_TYPE (GtkFilter, gtk_filter, GTK, FILTER, GObject) + +struct _GtkFilterClass +{ + GObjectClass parent_class; + + gboolean (* match) (GtkFilter *self, + gpointer item); + + /* optional */ + GtkFilterMatch (* get_strictness) (GtkFilter *self); + + /* Padding for future expansion */ + void (*_gtk_reserved1) (void); + void (*_gtk_reserved2) (void); + void (*_gtk_reserved3) (void); + void (*_gtk_reserved4) (void); + void (*_gtk_reserved5) (void); + void (*_gtk_reserved6) (void); + void (*_gtk_reserved7) (void); + void (*_gtk_reserved8) (void); +}; + +GDK_AVAILABLE_IN_ALL +gboolean gtk_filter_match (GtkFilter *self, + gpointer item); +GDK_AVAILABLE_IN_ALL +GtkFilterMatch gtk_filter_get_strictness (GtkFilter *self); + +/* for filter implementations */ +GDK_AVAILABLE_IN_ALL +void gtk_filter_changed (GtkFilter *self, + GtkFilterChange change); + + +G_END_DECLS + +#endif /* __GTK_FILTER_H__ */ diff --git a/src/gtklistmodels/gtkfilterlistmodel.c b/src/gtklistmodels/gtkfilterlistmodel.c new file mode 100644 index 0000000..a9bd13b --- /dev/null +++ b/src/gtklistmodels/gtkfilterlistmodel.c @@ -0,0 +1,916 @@ +/* + * 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 . + * + * Authors: Benjamin Otte + */ + +/* #include "config.h" */ + +#include "gtkfilterlistmodel.h" + +#include "gtkrbtreeprivate.h" +#include "gtkintl.h" +#include "gtkprivate.h" + +/** + * SECTION:gtkfilterlistmodel + * @title: GtkFilterListModel + * @short_description: A list model that filters its items + * @see_also: #GListModel, #GtkFilter + * + * #GtkFilterListModel is a list model that filters a given other + * listmodel. + * It hides some elements from the other model according to + * criteria given by a #GtkFilter. + */ + +enum { + PROP_0, + PROP_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; + GtkFilter *filter; + GtkFilterMatch strictness; + + GtkRbTree *items; /* NULL if strictness != GTK_FILTER_MATCH_SOME */ +}; + +struct _GtkFilterListModelClass +{ + GObjectClass parent_class; +}; + +static GParamSpec *properties[NUM_PROPERTIES] = { NULL, }; + +static void +gtk_filter_list_model_augment (GtkRbTree *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_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_rb_tree_get_augment (filter, right); + aug->n_items += right_aug->n_items; + aug->n_visible += right_aug->n_visible; + } +} + +static FilterNode * +gtk_filter_list_model_get_nth_filtered (GtkRbTree *tree, + guint position, + guint *out_unfiltered) +{ + FilterNode *node, *tmp; + guint unfiltered; + + node = gtk_rb_tree_get_root (tree); + unfiltered = 0; + + while (node) + { + tmp = gtk_rb_tree_node_get_left (node); + if (tmp) + { + FilterAugment *aug = gtk_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_rb_tree_node_get_right (node); + } + + if (out_unfiltered) + *out_unfiltered = unfiltered; + + return node; +} + +static FilterNode * +gtk_filter_list_model_get_nth (GtkRbTree *tree, + guint position, + guint *out_filtered) +{ + FilterNode *node, *tmp; + guint filtered; + + node = gtk_rb_tree_get_root (tree); + filtered = 0; + + while (node) + { + tmp = gtk_rb_tree_node_get_left (node); + if (tmp) + { + FilterAugment *aug = gtk_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_rb_tree_node_get_right (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; + + switch (self->strictness) + { + case GTK_FILTER_MATCH_NONE: + return 0; + + case GTK_FILTER_MATCH_ALL: + return g_list_model_get_n_items (self->model); + + default: + g_assert_not_reached (); + G_GNUC_FALLTHROUGH; + case GTK_FILTER_MATCH_SOME: + break; + } + + node = gtk_rb_tree_get_root (self->items); + if (node == NULL) + return 0; + + aug = gtk_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; + + switch (self->strictness) + { + case GTK_FILTER_MATCH_NONE: + return NULL; + + case GTK_FILTER_MATCH_ALL: + unfiltered = position; + break; + + default: + g_assert_not_reached (); + G_GNUC_FALLTHROUGH; + case GTK_FILTER_MATCH_SOME: + gtk_filter_list_model_get_nth_filtered (self->items, position, &unfiltered); + break; + } + + 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; + + /* all other cases should have beeen optimized away */ + g_assert (self->strictness == GTK_FILTER_MATCH_SOME); + + item = g_list_model_get_item (self->model, position); + visible = gtk_filter_match (self->filter, item); + 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_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; + + switch (self->strictness) + { + case GTK_FILTER_MATCH_NONE: + return; + + case GTK_FILTER_MATCH_ALL: + g_list_model_items_changed (G_LIST_MODEL (self), position, removed, added); + return; + + default: + g_assert_not_reached (); + G_GNUC_FALLTHROUGH; + case GTK_FILTER_MATCH_SOME: + break; + } + + node = gtk_filter_list_model_get_nth (self->items, position, &filter_position); + + filter_removed = 0; + for (i = 0; i < removed; i++) + { + FilterNode *next = gtk_rb_tree_node_get_next (node); + if (node->visible) + filter_removed++; + gtk_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_FILTER: + gtk_filter_list_model_set_filter (self, g_value_get_object (value)); + break; + + 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_FILTER: + g_value_set_object (value, self->filter); + 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_rb_tree_remove_all (self->items); +} + +/* + * gtk_filter_list_model_find_filtered: + * @self: a #GtkFilterListModel + * @start: (out) (caller-allocates): number of unfiltered items + * at start of list + * @end: (out) (caller-allocates): number of unfiltered items + * at end of list + * @n_items: (out) (caller-allocates): number of unfiltered items in + * list + * + * Checks if elements in self->items are filtered out and returns + * the range that they occupy. + * This function is intended to be used for GListModel::items-changed + * emissions, so it is called in an intermediate state for @self. + * + * Returns: %TRUE if elements are filtered out, %FALSE if none are + **/ +static gboolean +gtk_filter_list_model_find_filtered (GtkFilterListModel *self, + guint *start, + guint *end, + guint *n_items) +{ + FilterNode *root, *node, *tmp; + FilterAugment *aug; + + if (self->items == NULL || self->model == NULL) + return FALSE; + + root = gtk_rb_tree_get_root (self->items); + if (root == NULL) + return FALSE; /* empty parent model */ + + aug = gtk_rb_tree_get_augment (self->items, root); + if (aug->n_items == aug->n_visible) + return FALSE; /* all items visible */ + + /* find first filtered */ + *start = 0; + *end = 0; + *n_items = aug->n_visible; + + node = root; + while (node) + { + tmp = gtk_rb_tree_node_get_left (node); + if (tmp) + { + aug = gtk_rb_tree_get_augment (self->items, tmp); + if (aug->n_visible < aug->n_items) + { + node = tmp; + continue; + } + *start += aug->n_items; + } + + if (!node->visible) + break; + + (*start)++; + + node = gtk_rb_tree_node_get_right (node); + } + + /* find last filtered by doing everything the opposite way */ + node = root; + while (node) + { + tmp = gtk_rb_tree_node_get_right (node); + if (tmp) + { + aug = gtk_rb_tree_get_augment (self->items, tmp); + if (aug->n_visible < aug->n_items) + { + node = tmp; + continue; + } + *end += aug->n_items; + } + + if (!node->visible) + break; + + (*end)++; + + node = gtk_rb_tree_node_get_left (node); + } + + return TRUE; +} + +static void +gtk_filter_list_model_refilter (GtkFilterListModel *self); + +static void +gtk_filter_list_model_update_strictness_and_refilter (GtkFilterListModel *self) +{ + GtkFilterMatch new_strictness; + + if (self->model == NULL) + new_strictness = GTK_FILTER_MATCH_NONE; + else if (self->filter == NULL) + new_strictness = GTK_FILTER_MATCH_ALL; + else + new_strictness = gtk_filter_get_strictness (self->filter); + + /* don't set self->strictness yet so get_n_items() and friends return old values */ + + switch (new_strictness) + { + case GTK_FILTER_MATCH_NONE: + { + guint n_before = g_list_model_get_n_items (G_LIST_MODEL (self)); + g_clear_pointer (&self->items, gtk_rb_tree_unref); + self->strictness = new_strictness; + if (n_before > 0) + g_list_model_items_changed (G_LIST_MODEL (self), 0, n_before, 0); + } + break; + + case GTK_FILTER_MATCH_ALL: + switch (self->strictness) + { + case GTK_FILTER_MATCH_NONE: + self->strictness = new_strictness; + g_list_model_items_changed (G_LIST_MODEL (self), 0, 0, g_list_model_get_n_items (self->model)); + break; + case GTK_FILTER_MATCH_ALL: + self->strictness = new_strictness; + break; + default: + case GTK_FILTER_MATCH_SOME: + { + guint start, end, n_before, n_after; + self->strictness = new_strictness; + if (gtk_filter_list_model_find_filtered (self, &start, &end, &n_before)) + { + n_after = g_list_model_get_n_items (G_LIST_MODEL (self)); + g_clear_pointer (&self->items, gtk_rb_tree_unref); + g_list_model_items_changed (G_LIST_MODEL (self), start, n_before - end - start, n_after - end - start); + } + else + { + g_clear_pointer (&self->items, gtk_rb_tree_unref); + } + } + break; + } + break; + + default: + g_assert_not_reached (); + G_GNUC_FALLTHROUGH; + case GTK_FILTER_MATCH_SOME: + switch (self->strictness) + { + case GTK_FILTER_MATCH_NONE: + { + guint n_after; + self->strictness = new_strictness; + self->items = gtk_rb_tree_new (FilterNode, + FilterAugment, + gtk_filter_list_model_augment, + NULL, NULL); + n_after = gtk_filter_list_model_add_items (self, NULL, 0, g_list_model_get_n_items (self->model)); + if (n_after > 0) + g_list_model_items_changed (G_LIST_MODEL (self), 0, 0, n_after); + } + break; + case GTK_FILTER_MATCH_ALL: + { + guint start, end, n_before, n_after; + self->strictness = new_strictness; + self->items = gtk_rb_tree_new (FilterNode, + FilterAugment, + gtk_filter_list_model_augment, + NULL, NULL); + n_before = g_list_model_get_n_items (self->model); + gtk_filter_list_model_add_items (self, NULL, 0, n_before); + if (gtk_filter_list_model_find_filtered (self, &start, &end, &n_after)) + g_list_model_items_changed (G_LIST_MODEL (self), start, n_before - end - start, n_after - end - start); + } + break; + default: + case GTK_FILTER_MATCH_SOME: + gtk_filter_list_model_refilter (self); + break; + } + } +} + +static void +gtk_filter_list_model_filter_changed_cb (GtkFilter *filter, + GtkFilterChange change, + GtkFilterListModel *self) +{ + gtk_filter_list_model_update_strictness_and_refilter (self); +} + +static void +gtk_filter_list_model_clear_filter (GtkFilterListModel *self) +{ + if (self->filter == NULL) + return; + + g_signal_handlers_disconnect_by_func (self->filter, gtk_filter_list_model_filter_changed_cb, self); + g_clear_object (&self->filter); +} + +static void +gtk_filter_list_model_dispose (GObject *object) +{ + GtkFilterListModel *self = GTK_FILTER_LIST_MODEL (object); + + gtk_filter_list_model_clear_model (self); + gtk_filter_list_model_clear_filter (self); + g_clear_pointer (&self->items, gtk_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:filter: + * + * The filter for this model + */ + properties[PROP_FILTER] = + g_param_spec_object ("filter", + P_("Filter"), + P_("The filter set for this model"), + GTK_TYPE_FILTER, + GTK_PARAM_READWRITE | 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) +{ + self->strictness = GTK_FILTER_MATCH_NONE; +} + +/** + * gtk_filter_list_model_new: + * @model: the model to sort + * @filter: (allow-none): filter or %NULL to not filter items + * + * Creates a new #GtkFilterListModel that will filter @model using the given + * @filter. + * + * Returns: a new #GtkFilterListModel + **/ +GtkFilterListModel * +gtk_filter_list_model_new (GListModel *model, + GtkFilter *filter) +{ + 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, + "filter", filter, + NULL); + + 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 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: + * @self: a #GtkFilterListModel + * @filter: (allow-none) (transfer none): filter to use or %NULL to not filter items + * + * Sets the filter used to filter items. + **/ +void +gtk_filter_list_model_set_filter (GtkFilterListModel *self, + GtkFilter *filter) +{ + g_return_if_fail (GTK_IS_FILTER_LIST_MODEL (self)); + g_return_if_fail (filter == NULL || GTK_IS_FILTER (filter)); + + if (self->filter == filter) + return; + + gtk_filter_list_model_clear_filter (self); + + if (filter) + { + self->filter = g_object_ref (filter); + g_signal_connect (filter, "changed", G_CALLBACK (gtk_filter_list_model_filter_changed_cb), self); + gtk_filter_list_model_filter_changed_cb (filter, GTK_FILTER_CHANGE_DIFFERENT, self); + } + else + { + gtk_filter_list_model_update_strictness_and_refilter (self); + } + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_FILTER]); +} + +/** + * gtk_filter_list_model_get_filter: + * @self: a #GtkFilterListModel + * + * Gets the #GtkFilter currently set on @self. + * + * Returns: (nullable) (transfer none): The filter currently in use + * or %NULL if the list isn't filtered + **/ +GtkFilter * +gtk_filter_list_model_get_filter (GtkFilterListModel *self) +{ + g_return_val_if_fail (GTK_IS_FILTER_LIST_MODEL (self), FALSE); + + return self->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 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 (removed == 0) + { + self->strictness = GTK_FILTER_MATCH_NONE; + gtk_filter_list_model_update_strictness_and_refilter (self); + added = 0; + } + else 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 + { + self->strictness = GTK_FILTER_MATCH_NONE; + 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; +} + +static 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_rb_tree_get_first (self->items); + node != NULL; + i++, node = gtk_rb_tree_node_get_next (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_rb_tree_node_mark_dirty (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/src/gtklistmodels/gtkfilterlistmodel.h b/src/gtklistmodels/gtkfilterlistmodel.h new file mode 100644 index 0000000..bc4bd7f --- /dev/null +++ b/src/gtklistmodels/gtkfilterlistmodel.h @@ -0,0 +1,59 @@ +/* + * 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 . + * + * Authors: Benjamin Otte + */ + +#ifndef __GTK_FILTER_LIST_MODEL_H__ +#define __GTK_FILTER_LIST_MODEL_H__ + + +#define GTK_COMPILATION +#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION) +#error "Only can be included directly." +#endif + +#include +#include "gtkfilter.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) + +GDK_AVAILABLE_IN_ALL +GtkFilterListModel * gtk_filter_list_model_new (GListModel *model, + GtkFilter *filter); +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 (GtkFilterListModel *self, + GtkFilter *filter); +GDK_AVAILABLE_IN_ALL +GtkFilter * gtk_filter_list_model_get_filter (GtkFilterListModel *self); +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); + +G_END_DECLS + +#endif /* __GTK_FILTER_LIST_MODEL_H__ */ diff --git a/src/gtklistmodels/gtkflattenlistmodel.c b/src/gtklistmodels/gtkflattenlistmodel.c new file mode 100644 index 0000000..bb6d614 --- /dev/null +++ b/src/gtklistmodels/gtkflattenlistmodel.c @@ -0,0 +1,541 @@ +/* + * 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 . + * + * Authors: Benjamin Otte + */ + +/* #include "config.h" */ + +#include "gtkflattenlistmodel.h" + +#include "gtkrbtreeprivate.h" +#include "gtkintl.h" +#include "gtkprivate.h" + +/** + * SECTION:gtkflattenlistmodel + * @title: GtkFlattenListModel + * @short_description: A list model that flattens a list of lists + * @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; + GtkRbTree *items; /* NULL if model == NULL */ +}; + +struct _GtkFlattenListModelClass +{ + GObjectClass parent_class; +}; + +static GParamSpec *properties[NUM_PROPERTIES] = { NULL, }; + +static FlattenNode * +gtk_flatten_list_model_get_nth (GtkRbTree *tree, + guint position, + guint *model_position) +{ + FlattenNode *node, *tmp; + guint model_n_items; + + node = gtk_rb_tree_get_root (tree); + + while (node) + { + tmp = gtk_rb_tree_node_get_left (node); + if (tmp) + { + FlattenAugment *aug = gtk_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_rb_tree_node_get_right (node); + } + + if (model_position) + *model_position = node ? position : 0; + + return node; +} + +static FlattenNode * +gtk_flatten_list_model_get_nth_model (GtkRbTree *tree, + guint position, + guint *items_before) +{ + FlattenNode *node, *tmp; + guint before; + + node = gtk_rb_tree_get_root (tree); + before = 0; + + while (node) + { + tmp = gtk_rb_tree_node_get_left (node); + if (tmp) + { + FlattenAugment *aug = gtk_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_rb_tree_node_get_right (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_rb_tree_get_root (self->items); + if (node == NULL) + return 0; + + aug = gtk_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, *left; + GtkFlattenListModel *self = node->list; + guint real_position; + + gtk_rb_tree_node_mark_dirty (node); + real_position = position; + + left = gtk_rb_tree_node_get_left (node); + if (left) + { + FlattenAugment *aug = gtk_rb_tree_get_augment (self->items, left); + real_position += aug->n_items; + } + + for (; + (parent = gtk_rb_tree_node_get_parent (node)) != NULL; + node = parent) + { + left = gtk_rb_tree_node_get_left (parent); + if (left != node) + { + if (left) + { + FlattenAugment *aug = gtk_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 (GtkRbTree *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_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_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_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_rb_tree_node_get_next (node); + real_removed += g_list_model_get_n_items (node->model); + gtk_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_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 = 0; + + 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_type_is_a (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_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/src/gtklistmodels/gtkflattenlistmodel.h b/src/gtklistmodels/gtkflattenlistmodel.h new file mode 100644 index 0000000..59f0d61 --- /dev/null +++ b/src/gtklistmodels/gtkflattenlistmodel.h @@ -0,0 +1,51 @@ +/* + * 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 . + * + * Authors: Benjamin Otte + */ + +#ifndef __GTK_FLATTEN_LIST_MODEL_H__ +#define __GTK_FLATTEN_LIST_MODEL_H__ + + +#define GTK_COMPILATION +#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION) +#error "Only can be included directly." +#endif + +#include + + +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/src/gtklistmodels/gtkintl.h b/src/gtklistmodels/gtkintl.h new file mode 100644 index 0000000..4931cb6 --- /dev/null +++ b/src/gtklistmodels/gtkintl.h @@ -0,0 +1,22 @@ +#ifndef __GTKINTL_H__ +#define __GTKINTL_H__ + +#ifndef GETTEXT_PACKAGE +# define GETTEXT_PACKAGE +#endif +#include + +#ifndef G_GNUC_FALLTHROUGH +#define G_GNUC_FALLTHROUGH __attribute__((fallthrough)) +#endif + +#ifdef ENABLE_NLS +#define P_(String) g_dgettext(GETTEXT_PACKAGE "-properties",String) +#else +#define P_(String) (String) +#endif + +/* not really I18N-related, but also a string marker macro */ +#define I_(string) g_intern_static_string (string) + +#endif diff --git a/src/gtklistmodels/gtkprivate.h b/src/gtklistmodels/gtkprivate.h new file mode 100644 index 0000000..8dc9513 --- /dev/null +++ b/src/gtklistmodels/gtkprivate.h @@ -0,0 +1,38 @@ +/* GTK - The GIMP Toolkit + * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald + * + * 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 . + */ + +/* + * Modified by the GTK+ Team and others 1997-2000. See the AUTHORS + * file for a list of people on the GTK+ Team. See the ChangeLog + * files for a list of changes. These files are distributed with + * GTK+ at ftp://ftp.gtk.org/pub/gtk/. + */ + +#ifndef __GTK_PRIVATE_H__ +#define __GTK_PRIVATE_H__ + +#include + +G_BEGIN_DECLS + +#define GTK_PARAM_READABLE G_PARAM_READABLE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB +#define GTK_PARAM_WRITABLE G_PARAM_WRITABLE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB +#define GTK_PARAM_READWRITE G_PARAM_READWRITE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB + +G_END_DECLS + +#endif /* __GTK_PRIVATE_H__ */ diff --git a/src/gtklistmodels/gtkrbtree.c b/src/gtklistmodels/gtkrbtree.c new file mode 100644 index 0000000..fd96de9 --- /dev/null +++ b/src/gtklistmodels/gtkrbtree.c @@ -0,0 +1,800 @@ +/* gtkrbtree.c + * Copyright (C) 2000 Red Hat, Inc., Jonathan Blandford + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library. If not, see . + */ + +/* #include "config.h" */ + +#include "gtkrbtreeprivate.h" + +/* #include "gtkdebug.h" */ + +/* Define the following to print adds and removals to stdout. + * The format of the printout will be suitable for addition as a new test to + * testsuite/gtk/rbtree-crash.c + * by just grepping the printouts from the relevant rbtree. + * + * This is meant to be a trivial way to add rbtree tests to the testsuite. + */ +#undef DUMP_MODIFICATION + +typedef struct _GtkRbNode GtkRbNode; + +struct _GtkRbTree +{ + guint ref_count; + + gsize element_size; + gsize augment_size; + GtkRbTreeAugmentFunc augment_func; + GDestroyNotify clear_func; + GDestroyNotify clear_augment_func; + + GtkRbNode *root; +}; + +struct _GtkRbNode +{ + guint red :1; + guint dirty :1; + + GtkRbNode *left; + GtkRbNode *right; + /* The difference between tree and parent here is that we OR the tree with 1 and because + * pointers are always multiples of 4, we can know if we've stored a parent or the tree here */ + union { + gpointer parent_or_tree; + GtkRbNode *parent; + GtkRbTree *tree; + }; +}; + +#define NODE_FROM_POINTER(ptr) ((GtkRbNode *) ((ptr) ? (((guchar *) (ptr)) - sizeof (GtkRbNode)) : NULL)) +#define NODE_TO_POINTER(node) ((gpointer) ((node) ? (((guchar *) (node)) + sizeof (GtkRbNode)) : NULL)) +#define NODE_TO_AUG_POINTER(tree, node) ((gpointer) ((node) ? (((guchar *) (node)) + sizeof (GtkRbNode) + (tree)->element_size) : NULL)) + +static inline gboolean +is_root (GtkRbNode *node) +{ + return GPOINTER_TO_SIZE (node->parent_or_tree) & 1 ? TRUE : FALSE; +} + +static inline GtkRbNode * +parent (GtkRbNode *node) +{ + if (is_root (node)) + return NULL; + else + return node->parent; +} + +static GtkRbTree * +tree (GtkRbNode *node) +{ + while (!is_root (node)) + node = parent (node); + + return GSIZE_TO_POINTER (GPOINTER_TO_SIZE (node->tree) & ~1); +} + +static void +set_parent (GtkRbTree *tree, + GtkRbNode *node, + GtkRbNode *new_parent) +{ + + if (new_parent != NULL) + { + node->parent = new_parent; + } + else + { + node->tree = GSIZE_TO_POINTER (GPOINTER_TO_SIZE (tree) | 1); + tree->root = node; + } +} + +static inline gsize +gtk_rb_node_get_size (GtkRbTree *tree) +{ + return sizeof (GtkRbNode) + tree->element_size + tree->augment_size; +} + +static GtkRbNode * +gtk_rb_node_new (GtkRbTree *tree) +{ + GtkRbNode *result; + + result = g_slice_alloc0 (gtk_rb_node_get_size (tree)); + + result->red = TRUE; + result->dirty = TRUE; + + return result; +} + +static void +gtk_rb_node_free (GtkRbTree *tree, + GtkRbNode *node) +{ + if (tree->clear_func) + tree->clear_func (NODE_TO_POINTER (node)); + if (tree->clear_augment_func) + tree->clear_augment_func (NODE_TO_AUG_POINTER (tree, node)); + + g_slice_free1 (gtk_rb_node_get_size (tree), node); +} + +static void +gtk_rb_node_free_deep (GtkRbTree *tree, + GtkRbNode *node) +{ + GtkRbNode *right = node->right; + + if (node->left) + gtk_rb_node_free_deep (tree, node->left); + + gtk_rb_node_free (tree, node); + + if (right) + gtk_rb_node_free_deep (tree, right); +} + +static void +gtk_rb_node_mark_dirty (GtkRbNode *node, + gboolean mark_parent) +{ + if (node->dirty) + return; + + node->dirty = TRUE; + + if (mark_parent && parent (node)) + gtk_rb_node_mark_dirty (parent (node), TRUE); +} + +static void +gtk_rb_node_clean (GtkRbTree *tree, + GtkRbNode *node) +{ + if (!node->dirty) + return; + + node->dirty = FALSE; + if (tree->augment_func) + tree->augment_func (tree, + NODE_TO_AUG_POINTER (tree, node), + NODE_TO_POINTER (node), + NODE_TO_POINTER (node->left), + NODE_TO_POINTER (node->right)); +} + +static GtkRbNode * +gtk_rb_node_get_first (GtkRbNode *node) +{ + while (node->left) + node = node->left; + + return node; +} + +static GtkRbNode * +gtk_rb_node_get_last (GtkRbNode *node) +{ + while (node->right) + node = node->right; + + return node; +} + +static GtkRbNode * +gtk_rb_node_get_previous (GtkRbNode *node) +{ + GtkRbNode *p; + + if (node->left) + return gtk_rb_node_get_last (node->left); + + for (p = parent (node); p != NULL; p = parent (node)) + { + if (p->right == node) + return p; + + node = p; + } + + return NULL; +} + +static GtkRbNode * +gtk_rb_node_get_next (GtkRbNode *node) +{ + GtkRbNode *p; + + if (node->right) + return gtk_rb_node_get_first (node->right); + + for (p = parent (node); p != NULL; p = parent (node)) + { + if (p->left == node) + return p; + + node = p; + } + + return NULL; +} + +#ifdef DUMP_MODIFICATION +static guint +position (GtkRbTree *tree, + GtkRbNode *node) +{ + GtkRbNode *n; + guint i; + + i = 0; + for (n = gtk_rb_node_get_first (tree->root); + n != node; + n = gtk_rb_node_get_next (n)) + i++; + + return i; +} +#endif + +static void +gtk_rb_node_rotate_left (GtkRbTree *tree, + GtkRbNode *node) +{ + GtkRbNode *right, *p; + + right = node->right; + p = parent (node); + + node->right = right->left; + if (right->left) + set_parent (tree, right->left, node); + + set_parent (tree, right, p); + if (p) + { + if (node == p->left) + p->left = right; + else + p->right = right; + } + + right->left = node; + set_parent (tree, node, right); + + gtk_rb_node_mark_dirty (node, FALSE); + gtk_rb_node_mark_dirty (right, FALSE); +} + +static void +gtk_rb_node_rotate_right (GtkRbTree *tree, + GtkRbNode *node) +{ + GtkRbNode *left, *p; + + left = node->left; + p = parent (node); + + node->left = left->right; + if (left->right) + set_parent (tree, left->right, node); + + set_parent (tree, left, p); + if (p) + { + if (node == p->right) + p->right = left; + else + p->left = left; + } + + /* link node and left */ + left->right = node; + set_parent (tree, node, left); + + gtk_rb_node_mark_dirty (node, FALSE); + gtk_rb_node_mark_dirty (left, FALSE); +} + +static gboolean +is_red (GtkRbNode *node_or_null) +{ + if (node_or_null == NULL) + return FALSE; + else + return node_or_null->red; +} + +static inline gboolean +is_black (GtkRbNode *node_or_null) +{ + return !is_red (node_or_null); +} + +static void +set_black (GtkRbNode *node_or_null) +{ + if (node_or_null == NULL) + return; + + node_or_null->red = FALSE; +} + +static void +set_red (GtkRbNode *node_or_null) +{ + if (node_or_null == NULL) + return; + + node_or_null->red = TRUE; +} + +static void +gtk_rb_tree_insert_fixup (GtkRbTree *tree, + GtkRbNode *node) +{ + GtkRbNode *p; + + /* check Red-Black properties */ + for (p = parent (node); + p && is_red (p); + p = parent (node)) + { + GtkRbNode *pp = parent (p); + + /* we have a violation */ + g_assert (pp); + + if (p == pp->left) + { + GtkRbNode *uncle = pp->right; + + if (is_red (uncle)) + { + /* uncle is red */ + set_black (p); + set_black (uncle); + set_red (pp); + node = pp; + } + else + { + /* uncle is black */ + if (node == p->right) + { + /* make node a left child */ + node = p; + gtk_rb_node_rotate_left (tree, node); + p = parent (node); + pp = parent (p); + } + /* recolor and rotate */ + set_black (p); + set_red (pp); + gtk_rb_node_rotate_right (tree, pp); + } + } + else + { + /* mirror image of above code */ + GtkRbNode *uncle = pp->left; + + if (is_red (uncle)) + { + /* uncle is red */ + set_black (p); + set_black (uncle); + set_red (pp); + node = pp; + } + else + { + /* uncle is black */ + if (node == p->left) + { + node = p; + gtk_rb_node_rotate_right (tree, node); + p = parent (node); + pp = parent (p); + } + set_black (p); + set_red (pp); + gtk_rb_node_rotate_left (tree, pp); + } + } + } + + set_black (tree->root); +} + +static void +gtk_rb_tree_remove_node_fixup (GtkRbTree *tree, + GtkRbNode *node, + GtkRbNode *p) +{ + while (node != tree->root && is_black (node)) + { + if (node == p->left) + { + GtkRbNode *w = p->right; + + if (is_red (w)) + { + set_black (w); + set_red (p); + gtk_rb_node_rotate_left (tree, p); + w = p->right; + } + if (is_black (w->left) && is_black (w->right)) + { + set_red (w); + node = p; + } + else + { + if (is_black (w->right)) + { + set_black (w->left); + set_red (w); + gtk_rb_node_rotate_right (tree, w); + w = p->right; + } + w->red = p->red; + set_black (p); + set_black (w->right); + gtk_rb_node_rotate_left (tree, p); + node = tree->root; + } + } + else + { + GtkRbNode *w = p->left; + if (is_red (w)) + { + set_black (w); + set_red (p); + gtk_rb_node_rotate_right (tree, p); + w = p->left; + } + if (is_black (w->right) && is_black (w->left)) + { + set_red (w); + node = p; + } + else + { + if (is_black (w->left)) + { + set_black (w->right); + set_red (w); + gtk_rb_node_rotate_left (tree, w); + w = p->left; + } + w->red = p->red; + set_black (p); + set_black (w->left); + gtk_rb_node_rotate_right (tree, p); + node = tree->root; + } + } + + p = parent (node); + } + + set_black (node); +} + +GtkRbTree * +gtk_rb_tree_new_for_size (gsize element_size, + gsize augment_size, + GtkRbTreeAugmentFunc augment_func, + GDestroyNotify clear_func, + GDestroyNotify clear_augment_func) +{ + GtkRbTree *tree; + + tree = g_slice_new0 (GtkRbTree); + tree->ref_count = 1; + + tree->element_size = element_size; + tree->augment_size = augment_size; + tree->augment_func = augment_func; + tree->clear_func = clear_func; + tree->clear_augment_func = clear_augment_func; + + return tree; +} + +GtkRbTree * +gtk_rb_tree_ref (GtkRbTree *tree) +{ + tree->ref_count++; + + return tree; +} + +void +gtk_rb_tree_unref (GtkRbTree *tree) +{ + tree->ref_count--; + if (tree->ref_count > 0) + return; + + if (tree->root) + gtk_rb_node_free_deep (tree, tree->root); + + g_slice_free (GtkRbTree, tree); +} + +gpointer +gtk_rb_tree_get_first (GtkRbTree *tree) +{ + if (tree->root == NULL) + return NULL; + + return NODE_TO_POINTER (gtk_rb_node_get_first (tree->root)); +} + +gpointer +gtk_rb_tree_get_last (GtkRbTree *tree) +{ + if (tree->root == NULL) + return NULL; + + return NODE_TO_POINTER (gtk_rb_node_get_last (tree->root)); +} + +gpointer +gtk_rb_tree_node_get_previous (gpointer node) +{ + return NODE_TO_POINTER (gtk_rb_node_get_previous (NODE_FROM_POINTER (node))); +} + +gpointer +gtk_rb_tree_node_get_next (gpointer node) +{ + return NODE_TO_POINTER (gtk_rb_node_get_next (NODE_FROM_POINTER (node))); +} + +gpointer +gtk_rb_tree_get_root (GtkRbTree *tree) +{ + return NODE_TO_POINTER (tree->root); +} + +gpointer +gtk_rb_tree_node_get_parent (gpointer node) +{ + return NODE_TO_POINTER (parent (NODE_FROM_POINTER (node))); +} + +gpointer +gtk_rb_tree_node_get_left (gpointer node) +{ + return NODE_TO_POINTER (NODE_FROM_POINTER (node)->left); +} + +gpointer +gtk_rb_tree_node_get_right (gpointer node) +{ + return NODE_TO_POINTER (NODE_FROM_POINTER (node)->right); +} + +gpointer +gtk_rb_tree_get_augment (GtkRbTree *tree, + gpointer node) +{ + GtkRbNode *rbnode = NODE_FROM_POINTER (node); + + gtk_rb_node_clean (tree, rbnode); + + return NODE_TO_AUG_POINTER (tree, rbnode); +} + +GtkRbTree * +gtk_rb_tree_node_get_tree (gpointer node) +{ + return tree (NODE_FROM_POINTER (node)); +} + +void +gtk_rb_tree_node_mark_dirty (gpointer node) +{ + gtk_rb_node_mark_dirty (NODE_FROM_POINTER (node), TRUE); +} + +gpointer +gtk_rb_tree_insert_before (GtkRbTree *tree, + gpointer node) +{ + GtkRbNode *result; + + + if (tree->root == NULL) + { +#ifdef DUMP_MODIFICATION + g_print ("add (tree, 0); /* 0x%p */\n", tree); +#endif /* DUMP_MODIFICATION */ + + g_assert (node == NULL); + + result = gtk_rb_node_new (tree); + tree->root = result; + } + else if (node == NULL) + { + return gtk_rb_tree_insert_after (tree, gtk_rb_tree_get_last (tree)); + } + else + { + GtkRbNode *current = NODE_FROM_POINTER (node); + +#ifdef DUMP_MODIFICATION + g_print ("add (tree, %u); /* 0x%p */\n", position (tree, current), tree); +#endif /* DUMP_MODIFICATION */ + + /* setup new node */ + result = gtk_rb_node_new (tree); + + if (current->left) + { + current = gtk_rb_node_get_last (current->left); + current->right = result; + } + else + { + current->left = result; + } + set_parent (tree, result, current); + gtk_rb_node_mark_dirty (current, TRUE); + } + + gtk_rb_tree_insert_fixup (tree, result); + + return NODE_TO_POINTER (result); +} + +gpointer +gtk_rb_tree_insert_after (GtkRbTree *tree, + gpointer node) +{ + GtkRbNode *current, *result; + + if (node == NULL) + return gtk_rb_tree_insert_before (tree, gtk_rb_tree_get_first (tree)); + + current = NODE_FROM_POINTER (node); + +#ifdef DUMP_MODIFICATION + g_print ("add (tree, %u); /* 0x%p */\n", position (tree, current) + 1, tree); +#endif /* DUMP_MODIFICATION */ + + /* setup new node */ + result = gtk_rb_node_new (tree); + + if (current->right) + { + current = gtk_rb_node_get_first (current->right); + current->left = result; + } + else + { + current->right = result; + } + set_parent (tree, result, current); + gtk_rb_node_mark_dirty (current, TRUE); + + gtk_rb_tree_insert_fixup (tree, result); + + return NODE_TO_POINTER (result); +} + +void +gtk_rb_tree_remove (GtkRbTree *tree, + gpointer node) +{ + GtkRbNode *x, *y, *p, *real_node; + + real_node = NODE_FROM_POINTER (node); + +#ifdef DUMP_MODIFICATION + g_print ("delete (tree, %u); /* 0x%p */\n", position (tree, real_node), tree); +#endif /* DUMP_MODIFICATION */ + + y = real_node; + if (y->left && y->right) + { + y = y->right; + + while (y->left) + y = y->left; + } + + /* x is y's only child, or nil */ + if (y->left) + x = y->left; + else + x = y->right; + + /* remove y from the parent chain */ + p = parent (y); + if (x != NULL) + set_parent (tree, x, p); + if (p) + { + if (y == p->left) + p->left = x; + else + p->right = x; + gtk_rb_node_mark_dirty (p, TRUE); + } + else + { + if (x == NULL) + tree->root = NULL; + } + + /* We need to clean up the validity of the tree. + */ + if (is_black (y)) + gtk_rb_tree_remove_node_fixup (tree, x, p); + + if (y != real_node) + { + /* Move the node over */ + if (is_red (real_node) != is_red (y)) + y->red = !y->red; + + y->left = real_node->left; + if (y->left) + set_parent (tree, y->left, y); + y->right = real_node->right; + if (y->right) + set_parent (tree, y->right, y); + p = parent (real_node); + set_parent (tree, y, p); + if (p) + { + if (p->left == real_node) + p->left = y; + else + p->right = y; + gtk_rb_node_mark_dirty (p, TRUE); + } + gtk_rb_node_mark_dirty (y, TRUE); + } + + gtk_rb_node_free (tree, real_node); +} + +void +gtk_rb_tree_remove_all (GtkRbTree *tree) +{ +#ifdef DUMP_MODIFICATION + g_print ("delete_all (tree); /* 0x%p */\n", tree); +#endif /* DUMP_MODIFICATION */ + + if (tree->root) + gtk_rb_node_free_deep (tree, tree->root); + + tree->root = NULL; +} + diff --git a/src/gtklistmodels/gtkrbtreeprivate.h b/src/gtklistmodels/gtkrbtreeprivate.h new file mode 100644 index 0000000..45aba5c --- /dev/null +++ b/src/gtklistmodels/gtkrbtreeprivate.h @@ -0,0 +1,75 @@ +/* gtkrbtree.h + * Copyright (C) 2000 Red Hat, Inc., Jonathan Blandford + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library. If not, see . + */ + +/* A Red-Black Tree implementation used specifically by GtkTreeView. + */ +#ifndef __GTK_RB_TREE_H__ +#define __GTK_RB_TREE_H__ + +#include + + +G_BEGIN_DECLS + + +typedef struct _GtkRbTree GtkRbTree; + +typedef void (* GtkRbTreeAugmentFunc) (GtkRbTree *tree, + gpointer node_augment, + gpointer node, + gpointer left, + gpointer right); + +GtkRbTree * gtk_rb_tree_new_for_size (gsize element_size, + gsize augment_size, + GtkRbTreeAugmentFunc augment_func, + GDestroyNotify clear_func, + GDestroyNotify clear_augment_func); +#define gtk_rb_tree_new(type, augment_type, augment_func, clear_func, clear_augment_func) \ + gtk_rb_tree_new_for_size (sizeof (type), sizeof (augment_type), (augment_func), (clear_func), (clear_augment_func)) + +GtkRbTree * gtk_rb_tree_ref (GtkRbTree *tree); +void gtk_rb_tree_unref (GtkRbTree *tree); + +gpointer gtk_rb_tree_get_root (GtkRbTree *tree); +gpointer gtk_rb_tree_get_first (GtkRbTree *tree); +gpointer gtk_rb_tree_get_last (GtkRbTree *tree); + +gpointer gtk_rb_tree_node_get_previous (gpointer node); +gpointer gtk_rb_tree_node_get_next (gpointer node); +gpointer gtk_rb_tree_node_get_parent (gpointer node); +gpointer gtk_rb_tree_node_get_left (gpointer node); +gpointer gtk_rb_tree_node_get_right (gpointer node); +GtkRbTree * gtk_rb_tree_node_get_tree (gpointer node); +void gtk_rb_tree_node_mark_dirty (gpointer node); + +gpointer gtk_rb_tree_get_augment (GtkRbTree *tree, + gpointer node); + +gpointer gtk_rb_tree_insert_before (GtkRbTree *tree, + gpointer node); +gpointer gtk_rb_tree_insert_after (GtkRbTree *tree, + gpointer node); +void gtk_rb_tree_remove (GtkRbTree *tree, + gpointer node); +void gtk_rb_tree_remove_all (GtkRbTree *tree); + + +G_END_DECLS + + +#endif /* __GTK_RB_TREE_H__ */ diff --git a/src/gtklistmodels/gtkslicelistmodel.c b/src/gtklistmodels/gtkslicelistmodel.c new file mode 100644 index 0000000..fca2a68 --- /dev/null +++ b/src/gtklistmodels/gtkslicelistmodel.c @@ -0,0 +1,529 @@ +/* + * 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 . + * + * Authors: Benjamin Otte + */ + +/* #include "config.h" */ + +#include "gtkslicelistmodel.h" + +#include "gtkintl.h" +#include "gtkprivate.h" + +/** + * SECTION:gtkslicelistmodel + * @title: GtkSliceListModel + * @short_description: A list model that presents a slice out of a larger list + * @see_also: #GListModel + * + * #GtkSliceListModel is a list model that takes a list model and presents a slice of + * that model. + * + * This is useful when implementing paging by setting the size to the number of elements + * per page and updating the offset whenever a different page is opened. + */ + +#define DEFAULT_SIZE 10 + +enum { + PROP_0, + PROP_ITEM_TYPE, + PROP_MODEL, + PROP_OFFSET, + PROP_SIZE, + NUM_PROPERTIES +}; + +struct _GtkSliceListModel +{ + GObject parent_instance; + + GType item_type; + GListModel *model; + guint offset; + guint size; + + guint n_items; +}; + +struct _GtkSliceListModelClass +{ + GObjectClass parent_class; +}; + +static GParamSpec *properties[NUM_PROPERTIES] = { NULL, }; + +static GType +gtk_slice_list_model_get_item_type (GListModel *list) +{ + GtkSliceListModel *self = GTK_SLICE_LIST_MODEL (list); + + return self->item_type; +} + +static guint +gtk_slice_list_model_get_n_items (GListModel *list) +{ + GtkSliceListModel *self = GTK_SLICE_LIST_MODEL (list); + guint n_items; + + if (self->model == NULL) + return 0; + + /* XXX: This can be done without calling g_list_model_get_n_items() on the parent model + * by checking if model.get_item(offset + size) != NULL */ + n_items = g_list_model_get_n_items (self->model); + if (n_items <= self->offset) + return 0; + + n_items -= self->offset; + return MIN (n_items, self->size); +} + +static gpointer +gtk_slice_list_model_get_item (GListModel *list, + guint position) +{ + GtkSliceListModel *self = GTK_SLICE_LIST_MODEL (list); + + if (self->model == NULL) + return NULL; + + if (position >= self->size) + return NULL; + + return g_list_model_get_item (self->model, position + self->offset); +} + +static void +gtk_slice_list_model_model_init (GListModelInterface *iface) +{ + iface->get_item_type = gtk_slice_list_model_get_item_type; + iface->get_n_items = gtk_slice_list_model_get_n_items; + iface->get_item = gtk_slice_list_model_get_item; +} + +G_DEFINE_TYPE_WITH_CODE (GtkSliceListModel, gtk_slice_list_model, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, gtk_slice_list_model_model_init)) + +static void +gtk_slice_list_model_items_changed_cb (GListModel *model, + guint position, + guint removed, + guint added, + GtkSliceListModel *self) +{ + if (position >= self->offset + self->size) + return; + + if (position < self->offset) + { + guint skip = MIN (removed, added); + skip = MIN (skip, self->offset - position); + + position += skip; + removed -= skip; + added -= skip; + } + + if (removed == added) + { + guint changed = removed; + + if (changed == 0) + return; + + g_assert (position >= self->offset); + position -= self->offset; + changed = MIN (changed, self->size - position); + + g_list_model_items_changed (G_LIST_MODEL (self), position, changed, changed); + } + else + { + guint n_after, n_before; + guint skip; + + if (position > self->offset) + skip = position - self->offset; + else + skip = 0; + + n_after = g_list_model_get_n_items (self->model); + n_before = n_after - added + removed; + n_after = CLAMP (n_after, self->offset, self->offset + self->size) - self->offset; + n_before = CLAMP (n_before, self->offset, self->offset + self->size) - self->offset; + + g_list_model_items_changed (G_LIST_MODEL (self), skip, n_before - skip, n_after - skip); + } +} + +static void +gtk_slice_list_model_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GtkSliceListModel *self = GTK_SLICE_LIST_MODEL (object); + + switch (prop_id) + { + case PROP_ITEM_TYPE: + self->item_type = g_value_get_gtype (value); + break; + + case PROP_MODEL: + gtk_slice_list_model_set_model (self, g_value_get_object (value)); + break; + + case PROP_OFFSET: + gtk_slice_list_model_set_offset (self, g_value_get_uint (value)); + break; + + case PROP_SIZE: + gtk_slice_list_model_set_size (self, g_value_get_uint (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gtk_slice_list_model_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GtkSliceListModel *self = GTK_SLICE_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; + + case PROP_OFFSET: + g_value_set_uint (value, self->offset); + break; + + case PROP_SIZE: + g_value_set_uint (value, self->size); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gtk_slice_list_model_clear_model (GtkSliceListModel *self) +{ + if (self->model == NULL) + return; + + g_signal_handlers_disconnect_by_func (self->model, gtk_slice_list_model_items_changed_cb, self); + g_clear_object (&self->model); +} + +static void +gtk_slice_list_model_dispose (GObject *object) +{ + GtkSliceListModel *self = GTK_SLICE_LIST_MODEL (object); + + gtk_slice_list_model_clear_model (self); + + G_OBJECT_CLASS (gtk_slice_list_model_parent_class)->dispose (object); +}; + +static void +gtk_slice_list_model_class_init (GtkSliceListModelClass *class) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (class); + + gobject_class->set_property = gtk_slice_list_model_set_property; + gobject_class->get_property = gtk_slice_list_model_get_property; + gobject_class->dispose = gtk_slice_list_model_dispose; + + /** + * GtkSliceListModel: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); + + /** + * GtkSliceListModel:model: + * + * Child model to take slice from + */ + properties[PROP_MODEL] = + g_param_spec_object ("model", + P_("Model"), + P_("Child model to take slice from"), + G_TYPE_LIST_MODEL, + GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); + + /** + * GtkSliceListModel:offset: + * + * Offset of slice + */ + properties[PROP_OFFSET] = + g_param_spec_uint ("offset", + P_("Offset"), + P_("Offset of slice"), + 0, G_MAXUINT, 0, + GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); + + /** + * GtkSliceListModel:size: + * + * Maximum size of slice + */ + properties[PROP_SIZE] = + g_param_spec_uint ("size", + P_("Size"), + P_("Maximum size of slice"), + 0, G_MAXUINT, DEFAULT_SIZE, + GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); + + g_object_class_install_properties (gobject_class, NUM_PROPERTIES, properties); +} + +static void +gtk_slice_list_model_init (GtkSliceListModel *self) +{ + self->size = DEFAULT_SIZE; +} + +/** + * gtk_slice_list_model_new: + * @model: (transfer none): The model to use + * @offset: the offset of the slice + * @size: maximum size of the slice + * + * Creates a new slice model that presents the slice from @offset to + * @offset + @size our of the given @model. + * + * Returns: A new #GtkSliceListModel + **/ +GtkSliceListModel * +gtk_slice_list_model_new (GListModel *model, + guint offset, + guint size) +{ + g_return_val_if_fail (G_IS_LIST_MODEL (model), NULL); + + return g_object_new (GTK_TYPE_SLICE_LIST_MODEL, + "item-type", g_list_model_get_item_type (model), + "model", model, + "offset", offset, + "size", size, + NULL); +} + +/** + * gtk_slice_list_model_new_for_type: + * @item_type: the type of items + * + * Creates a new empty #GtkSliceListModel for the given @item_type that + * can be set up later. + * + * Returns: a new empty #GtkSliceListModel + **/ +GtkSliceListModel * +gtk_slice_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_SLICE_LIST_MODEL, + "item-type", item_type, + NULL); +} + +/** + * gtk_slice_list_model_set_model: + * @self: a #GtkSliceListModel + * @model: (allow-none): The model to be sliced + * + * Sets the model to show a slice of. The model's item type must conform + * to @self's item type. + * + **/ +void +gtk_slice_list_model_set_model (GtkSliceListModel *self, + GListModel *model) +{ + guint removed, added; + + g_return_if_fail (GTK_IS_SLICE_LIST_MODEL (self)); + g_return_if_fail (model == NULL || G_IS_LIST_MODEL (model)); + + if (self->model == model) + return; + + removed = g_list_model_get_n_items (G_LIST_MODEL (self)); + gtk_slice_list_model_clear_model (self); + + if (model) + { + self->model = g_object_ref (model); + g_signal_connect (model, "items-changed", G_CALLBACK (gtk_slice_list_model_items_changed_cb), self); + added = g_list_model_get_n_items (G_LIST_MODEL (self)); + } + 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_slice_list_model_get_model: + * @self: a #GtkSliceListModel + * + * Gets the model that is curently being used or %NULL if none. + * + * Returns: (nullable) (transfer none): The model in use + **/ +GListModel * +gtk_slice_list_model_get_model (GtkSliceListModel *self) +{ + g_return_val_if_fail (GTK_IS_SLICE_LIST_MODEL (self), NULL); + + return self->model; +} + +/** + * gtk_slice_list_model_set_offset: + * @self: a #GtkSliceListModel + * @offset: the new offset to use + * + * Sets the offset into the original model for this slice. + * + * If the offset is too large for the sliced model, + * @self will end up empty. + **/ +void +gtk_slice_list_model_set_offset (GtkSliceListModel *self, + guint offset) +{ + guint before, after; + + g_return_if_fail (GTK_IS_SLICE_LIST_MODEL (self)); + + if (self->offset == offset) + return; + + before = g_list_model_get_n_items (G_LIST_MODEL (self)); + + self->offset = offset; + + after = g_list_model_get_n_items (G_LIST_MODEL (self)); + + if (before > 0 || after > 0) + g_list_model_items_changed (G_LIST_MODEL (self), 0, before, after); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_OFFSET]); +} + +/** + * gtk_slice_list_model_get_offset: + * @self: a #GtkSliceListModel + * + * Gets the offset set via gtk_slice_list_model_set_offset() + * + * Returns: The offset + **/ +guint +gtk_slice_list_model_get_offset (GtkSliceListModel *self) +{ + g_return_val_if_fail (GTK_IS_SLICE_LIST_MODEL (self), 0); + + return self->offset; +} + +/** + * gtk_slice_list_model_set_size: + * @self: a #GtkSliceListModel + * @size: the maximum size + * + * Sets the maximum size. @self will never have more items + * than @size. + * + * It can however have fewer items if the offset is too large or + * the model sliced from doesn't have enough items. + */ +void +gtk_slice_list_model_set_size (GtkSliceListModel *self, + guint size) +{ + guint before, after; + + g_return_if_fail (GTK_IS_SLICE_LIST_MODEL (self)); + + if (self->size == size) + return; + + before = g_list_model_get_n_items (G_LIST_MODEL (self)); + + self->size = size; + + after = g_list_model_get_n_items (G_LIST_MODEL (self)); + + if (before > after) + g_list_model_items_changed (G_LIST_MODEL (self), after, before - after, 0); + else if (before < after) + g_list_model_items_changed (G_LIST_MODEL (self), before, 0, after - before); + /* else nothing */ + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SIZE]); +} + +/** + * gtk_slice_list_model_get_size: + * @self: a #GtkSliceListModel + * + * Gets the size set via gtk_slice_list_model_set_size(). + * + * Returns: The size + **/ +guint +gtk_slice_list_model_get_size (GtkSliceListModel *self) +{ + g_return_val_if_fail (GTK_IS_SLICE_LIST_MODEL (self), DEFAULT_SIZE); + + return self->size; +} + + diff --git a/src/gtklistmodels/gtkslicelistmodel.h b/src/gtklistmodels/gtkslicelistmodel.h new file mode 100644 index 0000000..45d698f --- /dev/null +++ b/src/gtklistmodels/gtkslicelistmodel.h @@ -0,0 +1,65 @@ +/* + * 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 . + * + * Authors: Benjamin Otte + */ + +#ifndef __GTK_SLICE_LIST_MODEL_H__ +#define __GTK_SLICE_LIST_MODEL_H__ + + +#define GTK_COMPILATION +#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION) +#error "Only can be included directly." +#endif + +#include +#include + + +G_BEGIN_DECLS + +#define GTK_TYPE_SLICE_LIST_MODEL (gtk_slice_list_model_get_type ()) + +GDK_AVAILABLE_IN_ALL +G_DECLARE_FINAL_TYPE (GtkSliceListModel, gtk_slice_list_model, GTK, SLICE_LIST_MODEL, GObject) + +GDK_AVAILABLE_IN_ALL +GtkSliceListModel * gtk_slice_list_model_new (GListModel *model, + guint offset, + guint size); +GDK_AVAILABLE_IN_ALL +GtkSliceListModel * gtk_slice_list_model_new_for_type (GType item_type); + +GDK_AVAILABLE_IN_ALL +void gtk_slice_list_model_set_model (GtkSliceListModel *self, + GListModel *model); +GDK_AVAILABLE_IN_ALL +GListModel * gtk_slice_list_model_get_model (GtkSliceListModel *self); +GDK_AVAILABLE_IN_ALL +void gtk_slice_list_model_set_offset (GtkSliceListModel *self, + guint offset); +GDK_AVAILABLE_IN_ALL +guint gtk_slice_list_model_get_offset (GtkSliceListModel *self); +GDK_AVAILABLE_IN_ALL +void gtk_slice_list_model_set_size (GtkSliceListModel *self, + guint size); +GDK_AVAILABLE_IN_ALL +guint gtk_slice_list_model_get_size (GtkSliceListModel *self); + +G_END_DECLS + +#endif /* __GTK_SLICE_LIST_MODEL_H__ */ diff --git a/src/gtklistmodels/gtksorter.c b/src/gtklistmodels/gtksorter.c new file mode 100644 index 0000000..96176cb --- /dev/null +++ b/src/gtklistmodels/gtksorter.c @@ -0,0 +1,207 @@ +/* + * Copyright © 2019 Matthias Clasen + * + * 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 . + * + * Authors: Matthias Clasen + */ + +/* #include "config.h" */ + +#include "gtksorter.h" + +#include "gtkintl.h" +#include "gtktypebuiltins.h" + +/** + * SECTION:gtksorter + * @title: GtkSorter + * @Short_description: Sorting items + * @See_also: #GtkSortListModel + * + * #GtkSorter is the way to describe sorting criteria. + * Its primary user is #GtkSortListModel. + * + * The model will use a sorter to determine the order in which its items should appear + * by calling gtk_sorter_compare() for pairs of items. + * + * Sorters may change their sorting behavior through their lifetime. In that case, + * they call gtk_sorter_changed(), which will emit the #GtkSorter::changed signal to + * notify that the sort order is no longer valid and should be updated by calling + * gtk_sorter_compare() again. + * + * GTK provides various pre-made sorter implementations for common sorting operations. + * #GtkColumnView has built-in support for sorting lists via the #GtkColumnViewColumn:sorter + * property, where the user can change the sorting by clicking on list headers. + * + * Of course, in particular for large lists, it is also possible to subclass #GtkSorter + * and provide one's own sorter. + */ + +enum { + CHANGED, + LAST_SIGNAL +}; + +G_DEFINE_TYPE (GtkSorter, gtk_sorter, G_TYPE_OBJECT) + +static guint signals[LAST_SIGNAL] = { 0 }; + +static GtkOrdering +gtk_sorter_default_compare (GtkSorter *self, + gpointer item1, + gpointer item2) +{ + g_critical ("Sorter of type '%s' does not implement GtkSorter::compare", G_OBJECT_TYPE_NAME (self)); + + return GTK_ORDERING_EQUAL; +} + +static GtkSorterOrder +gtk_sorter_default_get_order (GtkSorter *self) +{ + return GTK_SORTER_ORDER_PARTIAL; +} + +static void +gtk_sorter_class_init (GtkSorterClass *class) +{ + class->compare = gtk_sorter_default_compare; + class->get_order = gtk_sorter_default_get_order; + + /** + * GtkSorter::changed: + * @self: The #GtkSorter + * @change: how the sorter changed + * + * This signal is emitted whenever the sorter changed. Users of the sorter + * should then update the sort order again via gtk_sorter_compare(). + * + * #GtkSortListModel handles this signal automatically. + * + * Depending on the @change parameter, it may be possible to update + * the sort order without a full resorting. Refer to the #GtkSorterChange + * documentation for details. + */ + signals[CHANGED] = + g_signal_new (I_("changed"), + G_TYPE_FROM_CLASS (class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__ENUM, + G_TYPE_NONE, 1, + GTK_TYPE_SORTER_CHANGE); + g_signal_set_va_marshaller (signals[CHANGED], + G_TYPE_FROM_CLASS (class), + g_cclosure_marshal_VOID__ENUMv); +} + +static void +gtk_sorter_init (GtkSorter *self) +{ +} + +/** + * gtk_sorter_compare: + * @self: a #GtkSorter + * @item1: (type GObject) (transfer none): first item to compare + * @item2: (type GObject) (transfer none): second item to compare + * + * Compares two given items according to the sort order implemented + * by the sorter. + * + * Sorters implement a partial order: + * * It is reflexive, ie a = a + * * It is antisymmetric, ie if a < b and b < a, then a = b + * * It is transitive, ie given any 3 items with a ≤ b and b ≤ c, + * then a ≤ c + * + * The sorter may signal it conforms to additional constraints + * via the return value of gtk_sorter_get_order(). + * + * Returns: %GTK_ORDERING_EQUAL if @item1 == @item2, + * %GTK_ORDERING_SMALLER if @item1 < @item2, + * %GTK_ORDERING_LARGER if @item1 > @item2 + */ +GtkOrdering +gtk_sorter_compare (GtkSorter *self, + gpointer item1, + gpointer item2) +{ + GtkOrdering result; + + g_return_val_if_fail (GTK_IS_SORTER (self), GTK_ORDERING_EQUAL); + g_return_val_if_fail (item1 && item2, GTK_ORDERING_EQUAL); + + if (item1 == item2) + return GTK_ORDERING_EQUAL; + + result = GTK_SORTER_GET_CLASS (self)->compare (self, item1, item2); + +#ifdef G_ENABLE_DEBUG + if (result < -1 || result > 1) + { + g_critical ("A sorter of type \"%s\" returned %d, which is not a valid GtkOrdering result.\n" + "Did you forget to call gtk_ordering_from_cmpfunc()?", + G_OBJECT_TYPE_NAME (self), (int) result); + } +#endif + + return result; +} + +/** + * gtk_sorter_get_order: + * @self: a #GtkSorter + * + * Gets the order that @self conforms to. See #GtkSorterOrder for details + * of the possible return values. + * + * This function is intended to allow optimizations. + * + * Returns: The order + **/ +GtkSorterOrder +gtk_sorter_get_order (GtkSorter *self) +{ + g_return_val_if_fail (GTK_IS_SORTER (self), GTK_SORTER_ORDER_PARTIAL); + + return GTK_SORTER_GET_CLASS (self)->get_order (self); +} + +/** + * gtk_sorter_changed: + * @self: a #GtkSorter + * @change: How the sorter changed + * + * Emits the #GtkSorter::changed signal to notify all users of the sorter + * that it has changed. Users of the sorter should then update the sort + * order via gtk_sorter_compare(). + * + * Depending on the @change parameter, it may be possible to update + * the sort order without a full resorting. Refer to the #GtkSorterChange + * documentation for details. + * + * This function is intended for implementors of #GtkSorter subclasses and + * should not be called from other functions. + */ +void +gtk_sorter_changed (GtkSorter *self, + GtkSorterChange change) +{ + g_return_if_fail (GTK_IS_SORTER (self)); + + g_signal_emit (self, signals[CHANGED], 0, change); +} diff --git a/src/gtklistmodels/gtksorter.h b/src/gtklistmodels/gtksorter.h new file mode 100644 index 0000000..36da4f9 --- /dev/null +++ b/src/gtklistmodels/gtksorter.h @@ -0,0 +1,131 @@ +/* + * Copyright © 2019 Matthias Clasen + * + * 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 . + * + * Authors: Matthias Clasen + */ + +#ifndef __GTK_SORTER_H__ +#define __GTK_SORTER_H__ + +#define GTK_COMPILATION +#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION) +#error "Only can be included directly." +#endif + +#include +#include + +G_BEGIN_DECLS + +/** + * GtkSorterOrder: + * @GTK_SORTER_ORDER_PARTIAL: A partial order. And #GtkOrdering is possible. + * @GTK_SORTER_ORDER_INVALID: An invalid order. gtk_sorter_compare() will + * always return %GTK_ORDERING_INVALID if both items are unequal. + * @GTK_SORTER_ORDER_NONE: No order, all elements are considered equal. + * gtk_sorter_compare() will only return %GTK_ORDERING_EQUAL or + * %GTK_ORDERING_INVALID. + * @GTK_SORTER_ORDER_TOTAL: A total order. gtk_sorter_compare() will only + * return %GTK_ORDERING_EQUAL if an item is compared with itself. Two + * different items will never cause this value to be returned. + * + * Describes the type of order that a #GtkSorter may describe. + */ +typedef enum { + GTK_SORTER_ORDER_PARTIAL, + GTK_SORTER_ORDER_NONE, + GTK_SORTER_ORDER_TOTAL +} GtkSorterOrder; + +typedef enum { + GTK_ORDERING_SMALLER = -1, + GTK_ORDERING_EQUAL = 0, + GTK_ORDERING_LARGER = 1 +} GtkOrdering; + +/** + * GtkSorterChange: + * @GTK_SORTER_CHANGE_DIFFERENT: The sorter change cannot be described + * by any of the other enumeration values + * @GTK_SORTER_CHANGE_INVERTED: The sort order was inverted. Comparisons + * that returned %GTK_ORDERING_SMALLER now return %GTK_ORDERING_LARGER + * and vice versa. Other comparisons return the same values as before. + * @GTK_SORTER_CHANGE_LESS_STRICT: The sorter is less strict: Comparisons + * may now return %GTK_ORDERING_EQUAL that did not do so before. + * @GTK_SORTER_CHANGE_MORE_STRICT: The sorter is more strict: Comparisons + * that did return %GTK_ORDERING_EQUAL may not do so anymore. + * + * Describes changes in a sorter in more detail and allows users + * to optimize resorting. + */ +typedef enum { + GTK_SORTER_CHANGE_DIFFERENT, + GTK_SORTER_CHANGE_INVERTED, + GTK_SORTER_CHANGE_LESS_STRICT, + GTK_SORTER_CHANGE_MORE_STRICT +} GtkSorterChange; + +#define GTK_TYPE_SORTER (gtk_sorter_get_type ()) + +GDK_AVAILABLE_IN_ALL +G_DECLARE_DERIVABLE_TYPE (GtkSorter, gtk_sorter, GTK, SORTER, GObject) + +/** + * GtkSorterClass + * @compare: Compare two items. See gtk_sorter_compare() for details. + * @get_order: Get the #GtkSorderOrder that applies to the current sorter. + * If unimplemented, it returns %GTK_SORTER_ORDER_PARTIAL. + * + * The virtual table for #GtkSorter. + */ +struct _GtkSorterClass +{ + GObjectClass parent_class; + + GtkOrdering (* compare) (GtkSorter *self, + gpointer item1, + gpointer item2); + + /* optional */ + GtkSorterOrder (* get_order) (GtkSorter *self); + + /* Padding for future expansion */ + void (*_gtk_reserved1) (void); + void (*_gtk_reserved2) (void); + void (*_gtk_reserved3) (void); + void (*_gtk_reserved4) (void); + void (*_gtk_reserved5) (void); + void (*_gtk_reserved6) (void); + void (*_gtk_reserved7) (void); + void (*_gtk_reserved8) (void); +}; + +GDK_AVAILABLE_IN_ALL +GtkOrdering gtk_sorter_compare (GtkSorter *self, + gpointer item1, + gpointer item2); +GDK_AVAILABLE_IN_ALL +GtkSorterOrder gtk_sorter_get_order (GtkSorter *self); + +/* for sorter implementations */ +GDK_AVAILABLE_IN_ALL +void gtk_sorter_changed (GtkSorter *self, + GtkSorterChange change); + +G_END_DECLS + +#endif /* __GTK_SORTER_H__ */ + diff --git a/src/gtklistmodels/gtksortlistmodel.c b/src/gtklistmodels/gtksortlistmodel.c new file mode 100644 index 0000000..bf030e1 --- /dev/null +++ b/src/gtklistmodels/gtksortlistmodel.c @@ -0,0 +1,595 @@ +/* + * 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 . + * + * Authors: Benjamin Otte + */ + +/* #include "config.h" */ + +#include "gtksortlistmodel.h" + +#include "gtkintl.h" +#include "gtkprivate.h" + +/** + * SECTION:gtksortlistmodel + * @title: GtkSortListModel + * @short_description: A list model that sorts its items + * @see_also: #GListModel, #GtkSorter + * + * #GtkSortListModel is a list model that takes a list model and + * sorts its elements according to a #GtkSorter. + * + * #GtkSortListModel is a generic model and because of that it + * cannot take advantage of any external knowledge when sorting. + * If you run into performance issues with #GtkSortListModel, it + * is strongly recommended that you write your own sorting list + * model. + */ + +enum { + PROP_0, + PROP_ITEM_TYPE, + PROP_MODEL, + PROP_SORTER, + NUM_PROPERTIES +}; + +typedef struct _GtkSortListEntry GtkSortListEntry; + +struct _GtkSortListModel +{ + GObject parent_instance; + + GType item_type; + GListModel *model; + GtkSorter *sorter; + + GSequence *sorted; /* NULL if known unsorted */ + GSequence *unsorted; /* NULL if known unsorted */ +}; + +struct _GtkSortListModelClass +{ + GObjectClass parent_class; +}; + +struct _GtkSortListEntry +{ + GSequenceIter *sorted_iter; + GSequenceIter *unsorted_iter; + gpointer item; /* holds ref */ +}; + +static GParamSpec *properties[NUM_PROPERTIES] = { NULL, }; + +static void +gtk_sort_list_entry_free (gpointer data) +{ + GtkSortListEntry *entry = data; + + g_object_unref (entry->item); + + g_slice_free (GtkSortListEntry, entry); +} + +static GType +gtk_sort_list_model_get_item_type (GListModel *list) +{ + GtkSortListModel *self = GTK_SORT_LIST_MODEL (list); + + return self->item_type; +} + +static guint +gtk_sort_list_model_get_n_items (GListModel *list) +{ + GtkSortListModel *self = GTK_SORT_LIST_MODEL (list); + + if (self->model == NULL) + return 0; + + return g_list_model_get_n_items (self->model); +} + +static gpointer +gtk_sort_list_model_get_item (GListModel *list, + guint position) +{ + GtkSortListModel *self = GTK_SORT_LIST_MODEL (list); + GSequenceIter *iter; + GtkSortListEntry *entry; + + if (self->model == NULL) + return NULL; + + if (self->unsorted == NULL) + return g_list_model_get_item (self->model, position); + + iter = g_sequence_get_iter_at_pos (self->sorted, position); + if (g_sequence_iter_is_end (iter)) + return NULL; + + entry = g_sequence_get (iter); + + return g_object_ref (entry->item); +} + +static void +gtk_sort_list_model_model_init (GListModelInterface *iface) +{ + iface->get_item_type = gtk_sort_list_model_get_item_type; + iface->get_n_items = gtk_sort_list_model_get_n_items; + iface->get_item = gtk_sort_list_model_get_item; +} + +G_DEFINE_TYPE_WITH_CODE (GtkSortListModel, gtk_sort_list_model, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, gtk_sort_list_model_model_init)) + +static void +gtk_sort_list_model_remove_items (GtkSortListModel *self, + guint position, + guint n_items, + guint *unmodified_start, + guint *unmodified_end) +{ + GSequenceIter *unsorted_iter; + guint i, pos, start, end, length_before; + + start = end = length_before = g_sequence_get_length (self->sorted); + unsorted_iter = g_sequence_get_iter_at_pos (self->unsorted, position); + + for (i = 0; i < n_items ; i++) + { + GtkSortListEntry *entry; + GSequenceIter *next; + + next = g_sequence_iter_next (unsorted_iter); + + entry = g_sequence_get (unsorted_iter); + pos = g_sequence_iter_get_position (entry->sorted_iter); + start = MIN (start, pos); + end = MIN (end, length_before - i - 1 - pos); + + g_sequence_remove (entry->unsorted_iter); + g_sequence_remove (entry->sorted_iter); + + unsorted_iter = next; + } + + *unmodified_start = start; + *unmodified_end = end; +} + +static int +_sort_func (gconstpointer item1, + gconstpointer item2, + gpointer data) +{ + GtkSortListEntry *entry1 = (GtkSortListEntry *) item1; + GtkSortListEntry *entry2 = (GtkSortListEntry *) item2; + GtkOrdering result; + + result = gtk_sorter_compare (GTK_SORTER (data), entry1->item, entry2->item); + + if (result == GTK_ORDERING_EQUAL) + result = g_sequence_iter_compare (entry1->unsorted_iter, entry2->unsorted_iter); + + return result; +} + +static void +gtk_sort_list_model_add_items (GtkSortListModel *self, + guint position, + guint n_items, + guint *unmodified_start, + guint *unmodified_end) +{ + GSequenceIter *unsorted_end; + guint i, pos, start, end, length_before; + + unsorted_end = g_sequence_get_iter_at_pos (self->unsorted, position); + start = end = length_before = g_sequence_get_length (self->sorted); + + for (i = 0; i < n_items; i++) + { + GtkSortListEntry *entry = g_slice_new0 (GtkSortListEntry); + + entry->item = g_list_model_get_item (self->model, position + i); + entry->unsorted_iter = g_sequence_insert_before (unsorted_end, entry); + entry->sorted_iter = g_sequence_insert_sorted (self->sorted, entry, _sort_func, self->sorter); + if (unmodified_start != NULL || unmodified_end != NULL) + { + pos = g_sequence_iter_get_position (entry->sorted_iter); + start = MIN (start, pos); + end = MIN (end, length_before + i - pos); + } + } + + if (unmodified_start) + *unmodified_start = start; + if (unmodified_end) + *unmodified_end = end; +} + +static void +gtk_sort_list_model_items_changed_cb (GListModel *model, + guint position, + guint removed, + guint added, + GtkSortListModel *self) +{ + guint n_items, start, end, start2, end2; + + if (removed == 0 && added == 0) + return; + + if (self->sorted == NULL) + { + g_list_model_items_changed (G_LIST_MODEL (self), position, removed, added); + return; + } + + gtk_sort_list_model_remove_items (self, position, removed, &start, &end); + gtk_sort_list_model_add_items (self, position, added, &start2, &end2); + start = MIN (start, start2); + end = MIN (end, end2); + + n_items = g_sequence_get_length (self->sorted) - start - end; + g_list_model_items_changed (G_LIST_MODEL (self), start, n_items - added + removed, n_items); +} + +static void +gtk_sort_list_model_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GtkSortListModel *self = GTK_SORT_LIST_MODEL (object); + + switch (prop_id) + { + case PROP_ITEM_TYPE: + self->item_type = g_value_get_gtype (value); + break; + + case PROP_MODEL: + gtk_sort_list_model_set_model (self, g_value_get_object (value)); + break; + + case PROP_SORTER: + gtk_sort_list_model_set_sorter (self, g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gtk_sort_list_model_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GtkSortListModel *self = GTK_SORT_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; + + case PROP_SORTER: + g_value_set_object (value, self->sorter); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void gtk_sort_list_model_resort (GtkSortListModel *self); + +static void +gtk_sort_list_model_sorter_changed_cb (GtkSorter *sorter, + int change, + GtkSortListModel *self) +{ + gtk_sort_list_model_resort (self); +} + +static void +gtk_sort_list_model_clear_model (GtkSortListModel *self) +{ + if (self->model == NULL) + return; + + g_signal_handlers_disconnect_by_func (self->model, gtk_sort_list_model_items_changed_cb, self); + g_clear_object (&self->model); + g_clear_pointer (&self->sorted, g_sequence_free); + g_clear_pointer (&self->unsorted, g_sequence_free); +} + +static void +gtk_sort_list_model_clear_sorter (GtkSortListModel *self) +{ + if (self->sorter == NULL) + return; + + g_signal_handlers_disconnect_by_func (self->sorter, gtk_sort_list_model_sorter_changed_cb, self); + g_clear_object (&self->sorter); +} + +static void +gtk_sort_list_model_dispose (GObject *object) +{ + GtkSortListModel *self = GTK_SORT_LIST_MODEL (object); + + gtk_sort_list_model_clear_model (self); + gtk_sort_list_model_clear_sorter (self); + + G_OBJECT_CLASS (gtk_sort_list_model_parent_class)->dispose (object); +}; + +static void +gtk_sort_list_model_class_init (GtkSortListModelClass *class) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (class); + + gobject_class->set_property = gtk_sort_list_model_set_property; + gobject_class->get_property = gtk_sort_list_model_get_property; + gobject_class->dispose = gtk_sort_list_model_dispose; + + /** + * GtkSortListModel:sorter: + * + * The sorter for this model + */ + properties[PROP_SORTER] = + g_param_spec_object ("sorter", + P_("Sorter"), + P_("The sorter for this model"), + GTK_TYPE_SORTER, + GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); + + /** + * GtkSortListModel:item-type: + * + * The #GType for items of this model + */ + properties[PROP_ITEM_TYPE] = + g_param_spec_gtype ("item-type", + P_("Item type"), + P_("The type of items of this list"), + G_TYPE_OBJECT, + GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY); + + /** + * GtkSortListModel:model: + * + * The model being sorted + */ + properties[PROP_MODEL] = + g_param_spec_object ("model", + P_("Model"), + P_("The model being sorted"), + 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_sort_list_model_init (GtkSortListModel *self) +{ +} + +/** + * gtk_sort_list_model_new: + * @model: the model to sort + * @sorter: (allow-none): the #GtkSorter to sort @model with + * + * Creates a new sort list model that uses the @sorter to sort @model. + * + * Returns: a new #GtkSortListModel + **/ +GtkSortListModel * +gtk_sort_list_model_new (GListModel *model, + GtkSorter *sorter) +{ + GtkSortListModel *result; + + g_return_val_if_fail (G_IS_LIST_MODEL (model), NULL); + g_return_val_if_fail (sorter == NULL || GTK_IS_SORTER (sorter), NULL); + + result = g_object_new (GTK_TYPE_SORT_LIST_MODEL, + "item-type", g_list_model_get_item_type (model), + "model", model, + "sorter", sorter, + NULL); + + return result; +} + +/** + * gtk_sort_list_model_new_for_type: + * @item_type: the type of the items that will be returned + * + * Creates a new empty sort list model set up to return items of type @item_type. + * It is up to the application to set a proper sort function and model to ensure + * the item type is matched. + * + * Returns: a new #GtkSortListModel + **/ +GtkSortListModel * +gtk_sort_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_SORT_LIST_MODEL, + "item-type", item_type, + NULL); +} + +static void +gtk_sort_list_model_create_sequences (GtkSortListModel *self) +{ + if (self->sorter == NULL || self->model == NULL) + return; + + self->sorted = g_sequence_new (gtk_sort_list_entry_free); + self->unsorted = g_sequence_new (NULL); + + gtk_sort_list_model_add_items (self, 0, g_list_model_get_n_items (self->model), NULL, NULL); +} + +/** + * gtk_sort_list_model_set_model: + * @self: a #GtkSortListModel + * @model: (allow-none): The model to be sorted + * + * Sets the model to be sorted. The @model's item type must conform to + * the item type of @self. + **/ +void +gtk_sort_list_model_set_model (GtkSortListModel *self, + GListModel *model) +{ + guint removed, added; + + g_return_if_fail (GTK_IS_SORT_LIST_MODEL (self)); + g_return_if_fail (model == NULL || G_IS_LIST_MODEL (model)); + if (model) + { + g_return_if_fail (g_type_is_a (g_list_model_get_item_type (model), self->item_type)); + } + + if (self->model == model) + return; + + removed = g_list_model_get_n_items (G_LIST_MODEL (self)); + gtk_sort_list_model_clear_model (self); + + if (model) + { + self->model = g_object_ref (model); + g_signal_connect (model, "items-changed", G_CALLBACK (gtk_sort_list_model_items_changed_cb), self); + added = g_list_model_get_n_items (model); + + gtk_sort_list_model_create_sequences (self); + } + 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_sort_list_model_get_model: + * @self: a #GtkSortListModel + * + * Gets the model currently sorted or %NULL if none. + * + * Returns: (nullable) (transfer none): The model that gets sorted + **/ +GListModel * +gtk_sort_list_model_get_model (GtkSortListModel *self) +{ + g_return_val_if_fail (GTK_IS_SORT_LIST_MODEL (self), NULL); + + return self->model; +} + +static void +gtk_sort_list_model_resort (GtkSortListModel *self) +{ + guint n_items; + + g_return_if_fail (GTK_IS_SORT_LIST_MODEL (self)); + + if (self->sorted == NULL) + return; + + n_items = g_list_model_get_n_items (self->model); + if (n_items <= 1) + return; + + g_sequence_sort (self->sorted, _sort_func, self->sorter); + + g_list_model_items_changed (G_LIST_MODEL (self), 0, n_items, n_items); +} + +/** + * gtk_sort_list_model_set_sorter: + * @self: a #GtkSortListModel + * @sorter: (allow-none): the #GtkSorter to sort @model with + * + * Sets a new sorter on @self. + */ +void +gtk_sort_list_model_set_sorter (GtkSortListModel *self, + GtkSorter *sorter) +{ + guint n_items; + + g_return_if_fail (GTK_IS_SORT_LIST_MODEL (self)); + g_return_if_fail (sorter == NULL || GTK_IS_SORTER (sorter)); + + gtk_sort_list_model_clear_sorter (self); + + if (sorter) + { + self->sorter = g_object_ref (sorter); + g_signal_connect (sorter, "changed", G_CALLBACK (gtk_sort_list_model_sorter_changed_cb), self); + } + + g_clear_pointer (&self->unsorted, g_sequence_free); + g_clear_pointer (&self->sorted, g_sequence_free); + + gtk_sort_list_model_create_sequences (self); + + n_items = g_list_model_get_n_items (G_LIST_MODEL (self)); + if (n_items > 1) + g_list_model_items_changed (G_LIST_MODEL (self), 0, n_items, n_items); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SORTER]); +} + +/** + * gtk_sort_list_model_get_sorter: + * @self: a #GtkSortLisTModel + * + * Gets the sorter that is used to sort @self. + * + * Returns: (nullable) (transfer none): the sorter of #self + */ +GtkSorter * +gtk_sort_list_model_get_sorter (GtkSortListModel *self) +{ + g_return_val_if_fail (GTK_IS_SORT_LIST_MODEL (self), NULL); + + return self->sorter; +} diff --git a/src/gtklistmodels/gtksortlistmodel.h b/src/gtklistmodels/gtksortlistmodel.h new file mode 100644 index 0000000..17f6e9a --- /dev/null +++ b/src/gtklistmodels/gtksortlistmodel.h @@ -0,0 +1,61 @@ +/* + * 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 . + * + * Authors: Benjamin Otte + */ + +#ifndef __GTK_SORT_LIST_MODEL_H__ +#define __GTK_SORT_LIST_MODEL_H__ + + +#define GTK_COMPILATION +#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION) +#error "Only can be included directly." +#endif + +#include +#include +#include "gtksorter.h" + + +G_BEGIN_DECLS + +#define GTK_TYPE_SORT_LIST_MODEL (gtk_sort_list_model_get_type ()) + +GDK_AVAILABLE_IN_ALL +G_DECLARE_FINAL_TYPE (GtkSortListModel, gtk_sort_list_model, GTK, SORT_LIST_MODEL, GObject) + +GDK_AVAILABLE_IN_ALL +GtkSortListModel * gtk_sort_list_model_new (GListModel *model, + GtkSorter *sorter); +GDK_AVAILABLE_IN_ALL +GtkSortListModel * gtk_sort_list_model_new_for_type (GType item_type); + +GDK_AVAILABLE_IN_ALL +void gtk_sort_list_model_set_sorter (GtkSortListModel *self, + GtkSorter *sorter); +GDK_AVAILABLE_IN_ALL +GtkSorter * gtk_sort_list_model_get_sorter (GtkSortListModel *self); + +GDK_AVAILABLE_IN_ALL +void gtk_sort_list_model_set_model (GtkSortListModel *self, + GListModel *model); +GDK_AVAILABLE_IN_ALL +GListModel * gtk_sort_list_model_get_model (GtkSortListModel *self); + +G_END_DECLS + +#endif /* __GTK_SORT_LIST_MODEL_H__ */ diff --git a/src/gtklistmodels/gtktypebuiltins.c b/src/gtklistmodels/gtktypebuiltins.c new file mode 100644 index 0000000..fef1b28 --- /dev/null +++ b/src/gtklistmodels/gtktypebuiltins.c @@ -0,0 +1,89 @@ +#include +#include "gtkprivate.h" + +#include "gtktypebuiltins.h" + +/* enumerations from "gtksorter.h" */ +GType +gtk_sorter_order_get_type (void) +{ + static volatile gsize g_define_type_id__volatile = 0; + + if (g_once_init_enter (&g_define_type_id__volatile)) + { + static const GEnumValue values[] = { + { GTK_SORTER_ORDER_PARTIAL, "GTK_SORTER_ORDER_PARTIAL", "partial" }, + { GTK_SORTER_ORDER_NONE, "GTK_SORTER_ORDER_NONE", "none" }, + { GTK_SORTER_ORDER_TOTAL, "GTK_SORTER_ORDER_TOTAL", "total" }, + { 0, NULL, NULL } + }; + GType g_define_type_id = + g_enum_register_static (g_intern_static_string ("GtkSorterOrder"), values); + g_once_init_leave (&g_define_type_id__volatile, g_define_type_id); + } + + return g_define_type_id__volatile; +} +GType +gtk_sorter_change_get_type (void) +{ + static volatile gsize g_define_type_id__volatile = 0; + + if (g_once_init_enter (&g_define_type_id__volatile)) + { + static const GEnumValue values[] = { + { GTK_SORTER_CHANGE_DIFFERENT, "GTK_SORTER_CHANGE_DIFFERENT", "different" }, + { GTK_SORTER_CHANGE_INVERTED, "GTK_SORTER_CHANGE_INVERTED", "inverted" }, + { GTK_SORTER_CHANGE_LESS_STRICT, "GTK_SORTER_CHANGE_LESS_STRICT", "less-strict" }, + { GTK_SORTER_CHANGE_MORE_STRICT, "GTK_SORTER_CHANGE_MORE_STRICT", "more-strict" }, + { 0, NULL, NULL } + }; + GType g_define_type_id = + g_enum_register_static (g_intern_static_string ("GtkSorterChange"), values); + g_once_init_leave (&g_define_type_id__volatile, g_define_type_id); + } + + return g_define_type_id__volatile; +} + +/* enumerations from "gtkfilter.h" */ +GType +gtk_filter_match_get_type (void) +{ + static volatile gsize g_define_type_id__volatile = 0; + + if (g_once_init_enter (&g_define_type_id__volatile)) + { + static const GEnumValue values[] = { + { GTK_FILTER_MATCH_SOME, "GTK_FILTER_MATCH_SOME", "some" }, + { GTK_FILTER_MATCH_NONE, "GTK_FILTER_MATCH_NONE", "none" }, + { GTK_FILTER_MATCH_ALL, "GTK_FILTER_MATCH_ALL", "all" }, + { 0, NULL, NULL } + }; + GType g_define_type_id = + g_enum_register_static (g_intern_static_string ("GtkFilterMatch"), values); + g_once_init_leave (&g_define_type_id__volatile, g_define_type_id); + } + + return g_define_type_id__volatile; +} +GType +gtk_filter_change_get_type (void) +{ + static volatile gsize g_define_type_id__volatile = 0; + + if (g_once_init_enter (&g_define_type_id__volatile)) + { + static const GEnumValue values[] = { + { GTK_FILTER_CHANGE_DIFFERENT, "GTK_FILTER_CHANGE_DIFFERENT", "different" }, + { GTK_FILTER_CHANGE_LESS_STRICT, "GTK_FILTER_CHANGE_LESS_STRICT", "less-strict" }, + { GTK_FILTER_CHANGE_MORE_STRICT, "GTK_FILTER_CHANGE_MORE_STRICT", "more-strict" }, + { 0, NULL, NULL } + }; + GType g_define_type_id = + g_enum_register_static (g_intern_static_string ("GtkFilterChange"), values); + g_once_init_leave (&g_define_type_id__volatile, g_define_type_id); + } + + return g_define_type_id__volatile; +} diff --git a/src/gtklistmodels/gtktypebuiltins.h b/src/gtklistmodels/gtktypebuiltins.h new file mode 100644 index 0000000..a7d9bb2 --- /dev/null +++ b/src/gtklistmodels/gtktypebuiltins.h @@ -0,0 +1,18 @@ +#include +#include + +#include "gtksorter.h" +#include "gtkfilter.h" + +/* enumerations from "gtksorter.h" */ +GDK_AVAILABLE_IN_ALL GType gtk_sorter_order_get_type (void) G_GNUC_CONST; +#define GTK_TYPE_SORTER_ORDER (gtk_sorter_order_get_type ()) +GDK_AVAILABLE_IN_ALL GType gtk_sorter_change_get_type (void) G_GNUC_CONST; +#define GTK_TYPE_SORTER_CHANGE (gtk_sorter_change_get_type ()) + +/* enumerations from "gtkfilter.h" */ +GDK_AVAILABLE_IN_ALL GType gtk_filter_match_get_type (void) G_GNUC_CONST; +#define GTK_TYPE_FILTER_MATCH (gtk_filter_match_get_type ()) +GDK_AVAILABLE_IN_ALL GType gtk_filter_change_get_type (void) G_GNUC_CONST; +#define GTK_TYPE_FILTER_CHANGE (gtk_filter_change_get_type ()) +