1
0
Fork 0
mirror of https://gitlab.gnome.org/GNOME/calls.git synced 2024-06-08 12:59:36 +00:00

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
This commit is contained in:
Evangelos Ribeiro Tzaras 2022-07-06 02:21:17 +02:00
parent 4188af73af
commit 01aa8c04c2
23 changed files with 4941 additions and 0 deletions

8
src/gtklistmodels/gtk.h Normal file
View file

@ -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"

View file

@ -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 <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
/* #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);
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GTK_CUSTOM_FILTER_H__
#define __GTK_CUSTOM_FILTER_H__
#define GTK_COMPILATION
#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
#error "Only <gtk/gtk.h> 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__ */

View file

@ -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 <http://www.gnu.org/licenses/>.
*
* Authors: Matthias Clasen <mclasen@redhat.com>
*/
/* #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);
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*
* Authors: Matthias Clasen <mclasen@redhat.com>
*/
#ifndef __GTK_CUSTOM_SORTER_H__
#define __GTK_CUSTOM_SORTER_H__
#define GTK_COMPILATION
#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
#error "Only <gtk/gtk.h> can be included directly."
#endif
/* #include <gtk/gtkexpression.h> */
#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__ */

View file

@ -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 <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
/* #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);
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GTK_FILTER_H__
#define __GTK_FILTER_H__
#define GTK_COMPILATION
#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
#error "Only <gtk/gtk.h> can be included directly."
#endif
#include <gdk/gdk.h>
G_BEGIN_DECLS
/**
* 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__ */

View file

@ -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 <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
/* #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);
}
/*<private>
* 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);
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#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 <gtk/gtk.h> can be included directly."
#endif
#include <gio/gio.h>
#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__ */

View file

@ -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 <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
/* #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;
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#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 <gtk/gtk.h> can be included directly."
#endif
#include <gdk/gdk.h>
G_BEGIN_DECLS
#define GTK_TYPE_FLATTEN_LIST_MODEL (gtk_flatten_list_model_get_type ())
GDK_AVAILABLE_IN_ALL
G_DECLARE_FINAL_TYPE (GtkFlattenListModel, gtk_flatten_list_model, GTK, FLATTEN_LIST_MODEL, GObject)
GDK_AVAILABLE_IN_ALL
GtkFlattenListModel * gtk_flatten_list_model_new (GType item_type,
GListModel *model);
GDK_AVAILABLE_IN_ALL
void gtk_flatten_list_model_set_model (GtkFlattenListModel *self,
GListModel *model);
GDK_AVAILABLE_IN_ALL
GListModel * gtk_flatten_list_model_get_model (GtkFlattenListModel *self);
G_END_DECLS
#endif /* __GTK_FLATTEN_LIST_MODEL_H__ */

View file

@ -0,0 +1,22 @@
#ifndef __GTKINTL_H__
#define __GTKINTL_H__
#ifndef GETTEXT_PACKAGE
# define GETTEXT_PACKAGE
#endif
#include <glib/gi18n-lib.h>
#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

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
/*
* 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 <glib-object.h>
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__ */

View file

@ -0,0 +1,800 @@
/* gtkrbtree.c
* Copyright (C) 2000 Red Hat, Inc., Jonathan Blandford <jrb@redhat.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
/* #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;
}

View file

@ -0,0 +1,75 @@
/* gtkrbtree.h
* Copyright (C) 2000 Red Hat, Inc., Jonathan Blandford <jrb@redhat.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
/* A Red-Black Tree implementation used specifically by GtkTreeView.
*/
#ifndef __GTK_RB_TREE_H__
#define __GTK_RB_TREE_H__
#include <glib.h>
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__ */

View file

@ -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 <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
/* #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;
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#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 <gtk/gtk.h> can be included directly."
#endif
#include <gio/gio.h>
#include <gtk/gtkwidget.h>
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__ */

View file

@ -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 <http://www.gnu.org/licenses/>.
*
* Authors: Matthias Clasen <mclasen@redhat.com>
*/
/* #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);
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*
* Authors: Matthias Clasen <mclasen@redhat.com>
*/
#ifndef __GTK_SORTER_H__
#define __GTK_SORTER_H__
#define GTK_COMPILATION
#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
#error "Only <gtk/gtk.h> can be included directly."
#endif
#include <gdk/gdk.h>
#include <gtk/gtkenums.h>
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__ */

View file

@ -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 <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
/* #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;
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#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 <gtk/gtk.h> can be included directly."
#endif
#include <gio/gio.h>
#include <gtk/gtkwidget.h>
#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__ */

View file

@ -0,0 +1,89 @@
#include <gtk/gtk.h>
#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;
}

View file

@ -0,0 +1,18 @@
#include <glib-object.h>
#include <gdk/gdk.h>
#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 ())