2019-12-03 04:43:14 +00:00
|
|
|
/*
|
|
|
|
* 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"
|
|
|
|
|
2020-07-15 18:17:55 +00:00
|
|
|
#include "gtksorterprivate.h"
|
2019-12-03 04:43:14 +00:00
|
|
|
|
|
|
|
#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,
|
2020-07-17 01:45:11 +00:00
|
|
|
* they 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.
|
2019-12-03 04:43:14 +00:00
|
|
|
*
|
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
|
2020-07-15 18:17:55 +00:00
|
|
|
typedef struct _GtkSorterPrivate GtkSorterPrivate;
|
|
|
|
typedef struct _GtkDefaultSortKeys GtkDefaultSortKeys;
|
|
|
|
|
|
|
|
struct _GtkSorterPrivate
|
|
|
|
{
|
|
|
|
GtkSortKeys *keys;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct _GtkDefaultSortKeys
|
|
|
|
{
|
|
|
|
GtkSortKeys keys;
|
|
|
|
GtkSorter *sorter;
|
|
|
|
};
|
|
|
|
|
2019-12-03 04:43:14 +00:00
|
|
|
enum {
|
|
|
|
CHANGED,
|
|
|
|
LAST_SIGNAL
|
|
|
|
};
|
|
|
|
|
2020-07-15 18:17:55 +00:00
|
|
|
G_DEFINE_TYPE_WITH_PRIVATE (GtkSorter, gtk_sorter, G_TYPE_OBJECT)
|
2019-12-03 04:43:14 +00:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2020-07-15 18:17:55 +00:00
|
|
|
static void
|
|
|
|
gtk_sorter_dispose (GObject *object)
|
|
|
|
{
|
|
|
|
GtkSorter *self = GTK_SORTER (object);
|
|
|
|
GtkSorterPrivate *priv = gtk_sorter_get_instance_private (self);
|
|
|
|
|
|
|
|
g_clear_pointer (&priv->keys, gtk_sort_keys_unref);
|
|
|
|
|
|
|
|
G_OBJECT_CLASS (gtk_sorter_parent_class)->dispose (object);
|
|
|
|
}
|
|
|
|
|
2019-12-03 04:43:14 +00:00
|
|
|
static void
|
|
|
|
gtk_sorter_class_init (GtkSorterClass *class)
|
|
|
|
{
|
2020-07-15 18:17:55 +00:00
|
|
|
GObjectClass *object_class = G_OBJECT_CLASS (class);
|
|
|
|
|
|
|
|
object_class->dispose = gtk_sorter_dispose;
|
|
|
|
|
2019-12-03 04:43:14 +00:00
|
|
|
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;
|
|
|
|
|
2020-07-07 17:02:29 +00:00
|
|
|
/* We turn this off because gtk_sorter_compare() is called so much that it's too expensive */
|
|
|
|
/* g_return_val_if_fail (GTK_IS_SORTER (self), GTK_ORDERING_EQUAL); */
|
2019-12-03 04:43:14 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2020-07-15 18:17:55 +00:00
|
|
|
static int
|
|
|
|
gtk_default_sort_keys_compare (gconstpointer a,
|
|
|
|
gconstpointer b,
|
|
|
|
gpointer data)
|
|
|
|
{
|
|
|
|
GtkDefaultSortKeys *self = data;
|
|
|
|
gpointer *key_a = (gpointer *) a;
|
|
|
|
gpointer *key_b = (gpointer *) b;
|
|
|
|
|
|
|
|
return gtk_sorter_compare (self->sorter, *key_a, *key_b);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
gtk_default_sort_keys_free (GtkSortKeys *keys)
|
|
|
|
{
|
|
|
|
GtkDefaultSortKeys *self = (GtkDefaultSortKeys *) keys;
|
|
|
|
|
|
|
|
g_object_unref (self->sorter);
|
|
|
|
|
|
|
|
g_slice_free (GtkDefaultSortKeys, self);
|
|
|
|
}
|
|
|
|
|
|
|
|
static gboolean
|
|
|
|
gtk_default_sort_keys_is_compatible (GtkSortKeys *keys,
|
|
|
|
GtkSortKeys *other)
|
|
|
|
{
|
|
|
|
if (keys->klass != other->klass)
|
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
gtk_default_sort_keys_init_key (GtkSortKeys *self,
|
|
|
|
gpointer item,
|
|
|
|
gpointer key_memory)
|
|
|
|
{
|
|
|
|
gpointer *key = (gpointer *) key_memory;
|
|
|
|
|
|
|
|
*key = g_object_ref (item);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
gtk_default_sort_keys_clear_key (GtkSortKeys *self,
|
|
|
|
gpointer key_memory)
|
|
|
|
{
|
|
|
|
gpointer *key = (gpointer *) key_memory;
|
|
|
|
|
|
|
|
g_object_unref (*key);
|
|
|
|
}
|
|
|
|
|
|
|
|
static const GtkSortKeysClass GTK_DEFAULT_SORT_KEYS_CLASS =
|
|
|
|
{
|
|
|
|
gtk_default_sort_keys_free,
|
|
|
|
gtk_default_sort_keys_compare,
|
|
|
|
gtk_default_sort_keys_is_compatible,
|
|
|
|
gtk_default_sort_keys_init_key,
|
|
|
|
gtk_default_sort_keys_clear_key,
|
|
|
|
};
|
|
|
|
|
|
|
|
/*<private>
|
|
|
|
* gtk_sorter_get_keys:
|
|
|
|
* @self: a #GtkSorter
|
|
|
|
*
|
|
|
|
* Gets a #GtkSortKeys that can be used as an alternative to
|
|
|
|
* @self for faster sorting.
|
|
|
|
*
|
|
|
|
* The sort keys can change every time #GtkSorter::changed is emitted.
|
|
|
|
* When the keys change, you should redo all comparisons with the new
|
|
|
|
* keys.
|
|
|
|
* When gtk_sort_keys_is_compatible() for the old and new keys returns
|
|
|
|
* %TRUE, you can reuse keys you generated previously.
|
|
|
|
*
|
|
|
|
* Returns: (transfer full): the sort keys to sort with
|
|
|
|
**/
|
|
|
|
GtkSortKeys *
|
|
|
|
gtk_sorter_get_keys (GtkSorter *self)
|
|
|
|
{
|
|
|
|
GtkSorterPrivate *priv = gtk_sorter_get_instance_private (self);
|
|
|
|
GtkDefaultSortKeys *fallback;
|
|
|
|
|
|
|
|
g_return_val_if_fail (GTK_IS_SORTER (self), NULL);
|
|
|
|
|
|
|
|
if (priv->keys)
|
|
|
|
return gtk_sort_keys_ref (priv->keys);
|
|
|
|
|
|
|
|
fallback = gtk_sort_keys_new (GtkDefaultSortKeys, >K_DEFAULT_SORT_KEYS_CLASS, sizeof (gpointer), sizeof (gpointer));
|
|
|
|
fallback->sorter = g_object_ref (self);
|
|
|
|
|
|
|
|
return (GtkSortKeys *) fallback;
|
|
|
|
}
|
|
|
|
|
2019-12-03 04:43:14 +00:00
|
|
|
/**
|
|
|
|
* 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);
|
|
|
|
}
|
2020-07-15 18:17:55 +00:00
|
|
|
|
|
|
|
/*<private>
|
|
|
|
* gtk_sorter_changed_with_keys
|
|
|
|
* @self: a #GtkSorter
|
|
|
|
* @change: How the sorter changed
|
|
|
|
* @keys: (not nullable) (transfer full): New keys to use
|
|
|
|
*
|
|
|
|
* Updates the sorter's keys to @keys and then calls gtk_sorter_changed().
|
|
|
|
* If you do not want to update the keys, call that function instead.
|
|
|
|
*
|
|
|
|
* This function should also be called in your_sorter_init() to initialize
|
|
|
|
* the keys to use with your sorter.
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
gtk_sorter_changed_with_keys (GtkSorter *self,
|
|
|
|
GtkSorterChange change,
|
|
|
|
GtkSortKeys *keys)
|
|
|
|
{
|
|
|
|
GtkSorterPrivate *priv = gtk_sorter_get_instance_private (self);
|
|
|
|
|
|
|
|
g_return_if_fail (GTK_IS_SORTER (self));
|
|
|
|
g_return_if_fail (keys != NULL);
|
|
|
|
|
|
|
|
g_clear_pointer (&priv->keys, gtk_sort_keys_unref);
|
|
|
|
priv->keys = keys;
|
|
|
|
|
|
|
|
gtk_sorter_changed (self, change);
|
|
|
|
}
|