/*
* Copyright © 2019 Matthias Clasen
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see .
*
* Authors: Matthias Clasen
*/
#include "config.h"
#include "gtksorterprivate.h"
#include "gtkintl.h"
#include "gtktypebuiltins.h"
/**
* GtkSorter:
*
* `GtkSorter` is an object to describe sorting criteria.
*
* Its primary user is [class@Gtk.SortListModel]
*
* The model will use a sorter to determine the order in which
* its items should appear by calling [method@Gtk.Sorter.compare]
* for pairs of items.
*
* Sorters may change their sorting behavior through their lifetime.
* In that case, they will emit the [signal@Gtk.Sorter::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. [class@Gtk.ColumnView] has built-in support for sorting lists
* via the [property@Gtk.ColumnViewColumn: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.
*/
typedef struct _GtkSorterPrivate GtkSorterPrivate;
typedef struct _GtkDefaultSortKeys GtkDefaultSortKeys;
struct _GtkSorterPrivate
{
GtkSortKeys *keys;
};
struct _GtkDefaultSortKeys
{
GtkSortKeys keys;
GtkSorter *sorter;
};
enum {
CHANGED,
LAST_SIGNAL
};
G_DEFINE_TYPE_WITH_PRIVATE (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_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);
}
static void
gtk_sorter_class_init (GtkSorterClass *class)
{
GObjectClass *object_class = G_OBJECT_CLASS (class);
object_class->dispose = gtk_sorter_dispose;
class->compare = gtk_sorter_default_compare;
class->get_order = gtk_sorter_default_get_order;
/**
* GtkSorter::changed:
* @self: The `GtkSorter`
* @change: how the sorter changed
*
* Emitted whenever the sorter changed.
*
* Users of the sorter should then update the sort order
* again via gtk_sorter_compare().
*
* [class@Gtk.SortListModel] 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
* [enum@Gtk.SorterChange] 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 [method@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;
/* 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); */
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 [enum@Gtk.SorterOrder] 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);
}
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,
};
/*
* 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 [signal@Gtk.Sorter::changed]
* is emitted. When the keys change, you should redo all comparisons
* with the new keys.
*
* When [method@Gtk.SortKeys.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;
}
/**
* gtk_sorter_changed:
* @self: a `GtkSorter`
* @change: How the sorter changed
*
* Notifies all users of the sorter that it has changed.
*
* This emits the [signal@Gtk.Sorter::changed] signal. Users
* of the sorter should then update the sort order via
* [method@Gtk.Sorter.compare].
*
* Depending on the @change parameter, it may be possible to
* update the sort order without a full resorting. Refer to
* the [enum@Gtk.SorterChange] 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);
}
/*
* 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);
}
/* See the comment in gtkenums.h as to why we need to play
* games with the introspection scanner for static inline
* functions
*/
#ifdef __GI_SCANNER__
/**
* gtk_ordering_from_cmpfunc:
* @cmpfunc_result: Result of a comparison function
*
* Converts the result of a `GCompareFunc` like strcmp() to a
* `GtkOrdering` value.
*
* Returns: the corresponding `GtkOrdering`
**/
GtkOrdering
gtk_ordering_from_cmpfunc (int cmpfunc_result)
{
return (GtkOrdering) ((cmpfunc_result > 0) - (cmpfunc_result < 0));
}
#endif