1/*
2 * Copyright © 2019 Matthias Clasen
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16 *
17 * Authors: Matthias Clasen <mclasen@redhat.com>
18 */
19
20#include "config.h"
21
22#include "gtksorterprivate.h"
23
24#include "gtkintl.h"
25#include "gtktypebuiltins.h"
26
27/**
28 * GtkSorter:
29 *
30 * `GtkSorter` is an object to describe sorting criteria.
31 *
32 * Its primary user is [class@Gtk.SortListModel]
33 *
34 * The model will use a sorter to determine the order in which
35 * its items should appear by calling [method@Gtk.Sorter.compare]
36 * for pairs of items.
37 *
38 * Sorters may change their sorting behavior through their lifetime.
39 * In that case, they will emit the [signal@Gtk.Sorter::changed] signal
40 * to notify that the sort order is no longer valid and should be updated
41 * by calling gtk_sorter_compare() again.
42 *
43 * GTK provides various pre-made sorter implementations for common sorting
44 * operations. [class@Gtk.ColumnView] has built-in support for sorting lists
45 * via the [property@Gtk.ColumnViewColumn:sorter] property, where the user can
46 * change the sorting by clicking on list headers.
47 *
48 * Of course, in particular for large lists, it is also possible to subclass
49 * `GtkSorter` and provide one's own sorter.
50 */
51
52typedef struct _GtkSorterPrivate GtkSorterPrivate;
53typedef struct _GtkDefaultSortKeys GtkDefaultSortKeys;
54
55struct _GtkSorterPrivate
56{
57 GtkSortKeys *keys;
58};
59
60struct _GtkDefaultSortKeys
61{
62 GtkSortKeys keys;
63 GtkSorter *sorter;
64};
65
66enum {
67 CHANGED,
68 LAST_SIGNAL
69};
70
71G_DEFINE_TYPE_WITH_PRIVATE (GtkSorter, gtk_sorter, G_TYPE_OBJECT)
72
73static guint signals[LAST_SIGNAL] = { 0 };
74
75static GtkOrdering
76gtk_sorter_default_compare (GtkSorter *self,
77 gpointer item1,
78 gpointer item2)
79{
80 g_critical ("Sorter of type '%s' does not implement GtkSorter::compare", G_OBJECT_TYPE_NAME (self));
81
82 return GTK_ORDERING_EQUAL;
83}
84
85static GtkSorterOrder
86gtk_sorter_default_get_order (GtkSorter *self)
87{
88 return GTK_SORTER_ORDER_PARTIAL;
89}
90
91static void
92gtk_sorter_dispose (GObject *object)
93{
94 GtkSorter *self = GTK_SORTER (ptr: object);
95 GtkSorterPrivate *priv = gtk_sorter_get_instance_private (self);
96
97 g_clear_pointer (&priv->keys, gtk_sort_keys_unref);
98
99 G_OBJECT_CLASS (gtk_sorter_parent_class)->dispose (object);
100}
101
102static void
103gtk_sorter_class_init (GtkSorterClass *class)
104{
105 GObjectClass *object_class = G_OBJECT_CLASS (class);
106
107 object_class->dispose = gtk_sorter_dispose;
108
109 class->compare = gtk_sorter_default_compare;
110 class->get_order = gtk_sorter_default_get_order;
111
112 /**
113 * GtkSorter::changed:
114 * @self: The `GtkSorter`
115 * @change: how the sorter changed
116 *
117 * Emitted whenever the sorter changed.
118 *
119 * Users of the sorter should then update the sort order
120 * again via gtk_sorter_compare().
121 *
122 * [class@Gtk.SortListModel] handles this signal automatically.
123 *
124 * Depending on the @change parameter, it may be possible to update
125 * the sort order without a full resorting. Refer to the
126 * [enum@Gtk.SorterChange] documentation for details.
127 */
128 signals[CHANGED] =
129 g_signal_new (I_("changed"),
130 G_TYPE_FROM_CLASS (class),
131 signal_flags: G_SIGNAL_RUN_LAST,
132 class_offset: 0,
133 NULL, NULL,
134 c_marshaller: g_cclosure_marshal_VOID__ENUM,
135 G_TYPE_NONE, n_params: 1,
136 GTK_TYPE_SORTER_CHANGE);
137 g_signal_set_va_marshaller (signal_id: signals[CHANGED],
138 G_TYPE_FROM_CLASS (class),
139 va_marshaller: g_cclosure_marshal_VOID__ENUMv);
140}
141
142static void
143gtk_sorter_init (GtkSorter *self)
144{
145}
146
147/**
148 * gtk_sorter_compare:
149 * @self: a `GtkSorter`
150 * @item1: (type GObject) (transfer none): first item to compare
151 * @item2: (type GObject) (transfer none): second item to compare
152 *
153 * Compares two given items according to the sort order implemented
154 * by the sorter.
155 *
156 * Sorters implement a partial order:
157 *
158 * * It is reflexive, ie a = a
159 * * It is antisymmetric, ie if a < b and b < a, then a = b
160 * * It is transitive, ie given any 3 items with a ≤ b and b ≤ c,
161 * then a ≤ c
162 *
163 * The sorter may signal it conforms to additional constraints
164 * via the return value of [method@Gtk.Sorter.get_order].
165 *
166 * Returns: %GTK_ORDERING_EQUAL if @item1 == @item2,
167 * %GTK_ORDERING_SMALLER if @item1 < @item2,
168 * %GTK_ORDERING_LARGER if @item1 > @item2
169 */
170GtkOrdering
171gtk_sorter_compare (GtkSorter *self,
172 gpointer item1,
173 gpointer item2)
174{
175 GtkOrdering result;
176
177 /* We turn this off because gtk_sorter_compare() is called so much that it's too expensive */
178 /* g_return_val_if_fail (GTK_IS_SORTER (self), GTK_ORDERING_EQUAL); */
179 g_return_val_if_fail (item1 && item2, GTK_ORDERING_EQUAL);
180
181 if (item1 == item2)
182 return GTK_ORDERING_EQUAL;
183
184 result = GTK_SORTER_GET_CLASS (ptr: self)->compare (self, item1, item2);
185
186#ifdef G_ENABLE_DEBUG
187 if (result < -1 || result > 1)
188 {
189 g_critical ("A sorter of type \"%s\" returned %d, which is not a valid GtkOrdering result.\n"
190 "Did you forget to call gtk_ordering_from_cmpfunc()?",
191 G_OBJECT_TYPE_NAME (self), (int) result);
192 }
193#endif
194
195 return result;
196}
197
198/**
199 * gtk_sorter_get_order:
200 * @self: a `GtkSorter`
201 *
202 * Gets the order that @self conforms to.
203 *
204 * See [enum@Gtk.SorterOrder] for details
205 * of the possible return values.
206 *
207 * This function is intended to allow optimizations.
208 *
209 * Returns: The order
210 */
211GtkSorterOrder
212gtk_sorter_get_order (GtkSorter *self)
213{
214 g_return_val_if_fail (GTK_IS_SORTER (self), GTK_SORTER_ORDER_PARTIAL);
215
216 return GTK_SORTER_GET_CLASS (ptr: self)->get_order (self);
217}
218
219static int
220gtk_default_sort_keys_compare (gconstpointer a,
221 gconstpointer b,
222 gpointer data)
223{
224 GtkDefaultSortKeys *self = data;
225 gpointer *key_a = (gpointer *) a;
226 gpointer *key_b = (gpointer *) b;
227
228 return gtk_sorter_compare (self: self->sorter, item1: *key_a, item2: *key_b);
229}
230
231static void
232gtk_default_sort_keys_free (GtkSortKeys *keys)
233{
234 GtkDefaultSortKeys *self = (GtkDefaultSortKeys *) keys;
235
236 g_object_unref (object: self->sorter);
237
238 g_slice_free (GtkDefaultSortKeys, self);
239}
240
241static gboolean
242gtk_default_sort_keys_is_compatible (GtkSortKeys *keys,
243 GtkSortKeys *other)
244{
245 if (keys->klass != other->klass)
246 return FALSE;
247
248 return TRUE;
249}
250
251static void
252gtk_default_sort_keys_init_key (GtkSortKeys *self,
253 gpointer item,
254 gpointer key_memory)
255{
256 gpointer *key = (gpointer *) key_memory;
257
258 *key = g_object_ref (item);
259}
260
261static void
262gtk_default_sort_keys_clear_key (GtkSortKeys *self,
263 gpointer key_memory)
264{
265 gpointer *key = (gpointer *) key_memory;
266
267 g_object_unref (object: *key);
268}
269
270static const GtkSortKeysClass GTK_DEFAULT_SORT_KEYS_CLASS =
271{
272 gtk_default_sort_keys_free,
273 gtk_default_sort_keys_compare,
274 gtk_default_sort_keys_is_compatible,
275 gtk_default_sort_keys_init_key,
276 gtk_default_sort_keys_clear_key,
277};
278
279/*<private>
280 * gtk_sorter_get_keys:
281 * @self: a `GtkSorter`
282 *
283 * Gets a `GtkSortKeys` that can be used as an alternative to
284 * @self for faster sorting.
285 *
286 * The sort keys can change every time [signal@Gtk.Sorter::changed]
287 * is emitted. When the keys change, you should redo all comparisons
288 * with the new keys.
289 *
290 * When [method@Gtk.SortKeys.is_compatible] for the old and new keys
291 * returns %TRUE, you can reuse keys you generated previously.
292 *
293 * Returns: (transfer full): the sort keys to sort with
294 */
295GtkSortKeys *
296gtk_sorter_get_keys (GtkSorter *self)
297{
298 GtkSorterPrivate *priv = gtk_sorter_get_instance_private (self);
299 GtkDefaultSortKeys *fallback;
300
301 g_return_val_if_fail (GTK_IS_SORTER (self), NULL);
302
303 if (priv->keys)
304 return gtk_sort_keys_ref (self: priv->keys);
305
306 fallback = gtk_sort_keys_new (GtkDefaultSortKeys, &GTK_DEFAULT_SORT_KEYS_CLASS, sizeof (gpointer), sizeof (gpointer));
307 fallback->sorter = g_object_ref (self);
308
309 return (GtkSortKeys *) fallback;
310}
311
312/**
313 * gtk_sorter_changed:
314 * @self: a `GtkSorter`
315 * @change: How the sorter changed
316 *
317 * Notifies all users of the sorter that it has changed.
318 *
319 * This emits the [signal@Gtk.Sorter::changed] signal. Users
320 * of the sorter should then update the sort order via
321 * [method@Gtk.Sorter.compare].
322 *
323 * Depending on the @change parameter, it may be possible to
324 * update the sort order without a full resorting. Refer to
325 * the [enum@Gtk.SorterChange] documentation for details.
326 *
327 * This function is intended for implementors of `GtkSorter`
328 * subclasses and should not be called from other functions.
329 */
330void
331gtk_sorter_changed (GtkSorter *self,
332 GtkSorterChange change)
333{
334 g_return_if_fail (GTK_IS_SORTER (self));
335
336 g_signal_emit (instance: self, signal_id: signals[CHANGED], detail: 0, change);
337}
338
339/*<private>
340 * gtk_sorter_changed_with_keys:
341 * @self: a `GtkSorter`
342 * @change: How the sorter changed
343 * @keys: (not nullable) (transfer full): New keys to use
344 *
345 * Updates the sorter's keys to @keys and then calls gtk_sorter_changed().
346 *
347 * If you do not want to update the keys, call that function instead.
348 *
349 * This function should also be called in your_sorter_init() to initialize
350 * the keys to use with your sorter.
351 */
352void
353gtk_sorter_changed_with_keys (GtkSorter *self,
354 GtkSorterChange change,
355 GtkSortKeys *keys)
356{
357 GtkSorterPrivate *priv = gtk_sorter_get_instance_private (self);
358
359 g_return_if_fail (GTK_IS_SORTER (self));
360 g_return_if_fail (keys != NULL);
361
362 g_clear_pointer (&priv->keys, gtk_sort_keys_unref);
363 priv->keys = keys;
364
365 gtk_sorter_changed (self, change);
366}
367
368/* See the comment in gtkenums.h as to why we need to play
369 * games with the introspection scanner for static inline
370 * functions
371 */
372#ifdef __GI_SCANNER__
373/**
374 * gtk_ordering_from_cmpfunc:
375 * @cmpfunc_result: Result of a comparison function
376 *
377 * Converts the result of a `GCompareFunc` like strcmp() to a
378 * `GtkOrdering` value.
379 *
380 * Returns: the corresponding `GtkOrdering`
381 *
382 * Since: 4.2
383 **/
384GtkOrdering
385gtk_ordering_from_cmpfunc (int cmpfunc_result)
386{
387 return (GtkOrdering) ((cmpfunc_result > 0) - (cmpfunc_result < 0));
388}
389#endif
390

source code of gtk/gtk/gtksorter.c