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 "gtkmultisorter.h"
23
24#include "gtkbuildable.h"
25#include "gtkintl.h"
26#include "gtksorterprivate.h"
27#include "gtktypebuiltins.h"
28
29#define GDK_ARRAY_TYPE_NAME GtkSorters
30#define GDK_ARRAY_NAME gtk_sorters
31#define GDK_ARRAY_ELEMENT_TYPE GtkSorter *
32#define GDK_ARRAY_FREE_FUNC g_object_unref
33
34#include "gdk/gdkarrayimpl.c"
35
36/**
37 * GtkMultiSorter:
38 *
39 * `GtkMultiSorter` combines multiple sorters by trying them
40 * in turn.
41 *
42 * If the first sorter compares two items as equal,
43 * the second is tried next, and so on.
44 */
45struct _GtkMultiSorter
46{
47 GtkSorter parent_instance;
48
49 GtkSorters sorters;
50};
51
52typedef struct _GtkMultiSortKey GtkMultiSortKey;
53typedef struct _GtkMultiSortKeys GtkMultiSortKeys;
54
55struct _GtkMultiSortKey
56{
57 gsize offset;
58 GtkSortKeys *keys;
59};
60
61struct _GtkMultiSortKeys
62{
63 GtkSortKeys parent_keys;
64
65 guint n_keys;
66 GtkMultiSortKey keys[];
67};
68
69static void
70gtk_multi_sort_keys_free (GtkSortKeys *keys)
71{
72 GtkMultiSortKeys *self = (GtkMultiSortKeys *) keys;
73 gsize i;
74
75 for (i = 0; i < self->n_keys; i++)
76 gtk_sort_keys_unref (self: self->keys[i].keys);
77
78 g_slice_free1 (block_size: sizeof (GtkMultiSortKeys) + self->n_keys * sizeof (GtkMultiSortKey), mem_block: self);
79}
80
81static int
82gtk_multi_sort_keys_compare (gconstpointer a,
83 gconstpointer b,
84 gpointer data)
85{
86 GtkMultiSortKeys *self = (GtkMultiSortKeys *) data;
87 gsize i;
88
89 for (i = 0; i < self->n_keys; i++)
90 {
91 GtkOrdering result = gtk_sort_keys_compare (self: self->keys[i].keys,
92 a: ((const char *) a) + self->keys[i].offset,
93 b: ((const char *) b) + self->keys[i].offset);
94 if (result != GTK_ORDERING_EQUAL)
95 return result;
96 }
97
98 return GTK_ORDERING_EQUAL;
99}
100
101static gboolean
102gtk_multi_sort_keys_is_compatible (GtkSortKeys *keys,
103 GtkSortKeys *other)
104{
105 GtkMultiSortKeys *self = (GtkMultiSortKeys *) keys;
106 GtkMultiSortKeys *compare = (GtkMultiSortKeys *) other;
107 gsize i;
108
109 if (keys->klass != other->klass)
110 return FALSE;
111
112 if (self->n_keys != compare->n_keys)
113 return FALSE;
114
115 for (i = 0; i < self->n_keys; i++)
116 {
117 if (!gtk_sort_keys_is_compatible (self: self->keys[i].keys, other: compare->keys[i].keys))
118 return FALSE;
119 }
120
121 return TRUE;
122}
123
124static void
125gtk_multi_sort_keys_init_key (GtkSortKeys *keys,
126 gpointer item,
127 gpointer key_memory)
128{
129 GtkMultiSortKeys *self = (GtkMultiSortKeys *) keys;
130 char *key = (char *) key_memory;
131 gsize i;
132
133 for (i = 0; i < self->n_keys; i++)
134 gtk_sort_keys_init_key (self: self->keys[i].keys, item, key_memory: key + self->keys[i].offset);
135}
136
137static void
138gtk_multi_sort_keys_clear_key (GtkSortKeys *keys,
139 gpointer key_memory)
140{
141 GtkMultiSortKeys *self = (GtkMultiSortKeys *) keys;
142 char *key = (char *) key_memory;
143 gsize i;
144
145 for (i = 0; i < self->n_keys; i++)
146 gtk_sort_keys_clear_key (self: self->keys[i].keys, key_memory: key + self->keys[i].offset);
147}
148
149static const GtkSortKeysClass GTK_MULTI_SORT_KEYS_CLASS =
150{
151 gtk_multi_sort_keys_free,
152 gtk_multi_sort_keys_compare,
153 gtk_multi_sort_keys_is_compatible,
154 gtk_multi_sort_keys_init_key,
155 gtk_multi_sort_keys_clear_key,
156};
157
158static GtkSortKeys *
159gtk_multi_sort_keys_new (GtkMultiSorter *self)
160{
161 GtkMultiSortKeys *result;
162 GtkSortKeys *keys;
163 gsize i;
164
165 if (gtk_sorters_get_size (self: &self->sorters) == 0)
166 return gtk_sort_keys_new_equal ();
167 else if (gtk_sorters_get_size (self: &self->sorters) == 1)
168 return gtk_sorter_get_keys (self: gtk_sorters_get (self: &self->sorters, pos: 0));
169
170 keys = gtk_sort_keys_alloc (klass: &GTK_MULTI_SORT_KEYS_CLASS,
171 size: sizeof (GtkMultiSortKeys) + gtk_sorters_get_size (self: &self->sorters) * sizeof (GtkMultiSortKey),
172 key_size: 0, key_align: 1);
173 result = (GtkMultiSortKeys *) keys;
174
175 result->n_keys = gtk_sorters_get_size (self: &self->sorters);
176 for (i = 0; i < result->n_keys; i++)
177 {
178 result->keys[i].keys = gtk_sorter_get_keys (self: gtk_sorters_get (self: &self->sorters, pos: i));
179 result->keys[i].offset = GTK_SORT_KEYS_ALIGN (keys->key_size, gtk_sort_keys_get_key_align (result->keys[i].keys));
180 keys->key_size = result->keys[i].offset + gtk_sort_keys_get_key_size (self: result->keys[i].keys);
181 keys->key_align = MAX (keys->key_align, gtk_sort_keys_get_key_align (result->keys[i].keys));
182 }
183
184 return keys;
185}
186
187static GType
188gtk_multi_sorter_get_item_type (GListModel *list)
189{
190 return GTK_TYPE_SORTER;
191}
192
193static guint
194gtk_multi_sorter_get_n_items (GListModel *list)
195{
196 GtkMultiSorter *self = GTK_MULTI_SORTER (ptr: list);
197
198 return gtk_sorters_get_size (self: &self->sorters);
199}
200
201static gpointer
202gtk_multi_sorter_get_item (GListModel *list,
203 guint position)
204{
205 GtkMultiSorter *self = GTK_MULTI_SORTER (ptr: list);
206
207 if (position < gtk_sorters_get_size (self: &self->sorters))
208 return g_object_ref (gtk_sorters_get (&self->sorters, position));
209 else
210 return NULL;
211}
212
213static void
214gtk_multi_sorter_list_model_init (GListModelInterface *iface)
215{
216 iface->get_item_type = gtk_multi_sorter_get_item_type;
217 iface->get_n_items = gtk_multi_sorter_get_n_items;
218 iface->get_item = gtk_multi_sorter_get_item;
219}
220
221static GtkBuildableIface *parent_buildable_iface;
222
223static void
224gtk_multi_sorter_buildable_add_child (GtkBuildable *buildable,
225 GtkBuilder *builder,
226 GObject *child,
227 const char *type)
228{
229 if (GTK_IS_SORTER (ptr: child))
230 gtk_multi_sorter_append (self: GTK_MULTI_SORTER (ptr: buildable), g_object_ref (GTK_SORTER (child)));
231 else
232 parent_buildable_iface->add_child (buildable, builder, child, type);
233}
234
235static void
236gtk_multi_sorter_buildable_init (GtkBuildableIface *iface)
237{
238 parent_buildable_iface = g_type_interface_peek_parent (g_iface: iface);
239
240 iface->add_child = gtk_multi_sorter_buildable_add_child;
241}
242
243G_DEFINE_TYPE_WITH_CODE (GtkMultiSorter, gtk_multi_sorter, GTK_TYPE_SORTER,
244 G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, gtk_multi_sorter_list_model_init)
245 G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, gtk_multi_sorter_buildable_init))
246
247static GtkOrdering
248gtk_multi_sorter_compare (GtkSorter *sorter,
249 gpointer item1,
250 gpointer item2)
251{
252 GtkMultiSorter *self = GTK_MULTI_SORTER (ptr: sorter);
253 GtkOrdering result = GTK_ORDERING_EQUAL;
254 guint i;
255
256 for (i = 0; i < gtk_sorters_get_size (self: &self->sorters); i++)
257 {
258 GtkSorter *child = gtk_sorters_get (self: &self->sorters, pos: i);
259
260 result = gtk_sorter_compare (self: child, item1, item2);
261 if (result != GTK_ORDERING_EQUAL)
262 break;
263 }
264
265 return result;
266}
267
268static GtkSorterOrder
269gtk_multi_sorter_get_order (GtkSorter *sorter)
270{
271 GtkMultiSorter *self = GTK_MULTI_SORTER (ptr: sorter);
272 GtkSorterOrder result = GTK_SORTER_ORDER_NONE;
273 guint i;
274
275 for (i = 0; i < gtk_sorters_get_size (self: &self->sorters); i++)
276 {
277 GtkSorter *child = gtk_sorters_get (self: &self->sorters, pos: i);
278 GtkSorterOrder child_order;
279
280 child_order = gtk_sorter_get_order (self: child);
281 switch (child_order)
282 {
283 case GTK_SORTER_ORDER_PARTIAL:
284 result = GTK_SORTER_ORDER_PARTIAL;
285 break;
286 case GTK_SORTER_ORDER_NONE:
287 break;
288 case GTK_SORTER_ORDER_TOTAL:
289 return GTK_SORTER_ORDER_TOTAL;
290 default:
291 g_assert_not_reached ();
292 break;
293 }
294 }
295
296 return result;
297}
298
299static void
300gtk_multi_sorter_changed_cb (GtkSorter *sorter,
301 GtkSorterChange change,
302 GtkMultiSorter *self)
303{
304 /* Using an enum on purpose, so gcc complains about this case if
305 * new values are added to the enum
306 */
307 switch (change)
308 {
309 case GTK_SORTER_CHANGE_INVERTED:
310 /* This could do a lot better with change handling, in particular in
311 * cases where self->n_sorters == 1 or if sorter == self->sorters[0]
312 */
313 change = GTK_SORTER_CHANGE_DIFFERENT;
314 break;
315
316 case GTK_SORTER_CHANGE_DIFFERENT:
317 case GTK_SORTER_CHANGE_LESS_STRICT:
318 case GTK_SORTER_CHANGE_MORE_STRICT:
319 break;
320
321 default:
322 g_assert_not_reached ();
323 change = GTK_SORTER_CHANGE_DIFFERENT;
324 }
325 gtk_sorter_changed_with_keys (self: GTK_SORTER (ptr: self),
326 change,
327 keys: gtk_multi_sort_keys_new (self));
328}
329
330static void
331gtk_multi_sorter_dispose (GObject *object)
332{
333 GtkMultiSorter *self = GTK_MULTI_SORTER (ptr: object);
334 guint i;
335
336 for (i = 0; i < gtk_sorters_get_size (self: &self->sorters); i++)
337 {
338 GtkSorter *sorter = gtk_sorters_get (self: &self->sorters, pos: i);
339 g_signal_handlers_disconnect_by_func (sorter, gtk_multi_sorter_changed_cb, self);
340 }
341 gtk_sorters_clear (self: &self->sorters);
342
343 G_OBJECT_CLASS (gtk_multi_sorter_parent_class)->dispose (object);
344}
345
346static void
347gtk_multi_sorter_class_init (GtkMultiSorterClass *class)
348{
349 GtkSorterClass *sorter_class = GTK_SORTER_CLASS (ptr: class);
350 GObjectClass *object_class = G_OBJECT_CLASS (class);
351
352 sorter_class->compare = gtk_multi_sorter_compare;
353 sorter_class->get_order = gtk_multi_sorter_get_order;
354
355 object_class->dispose = gtk_multi_sorter_dispose;
356}
357
358static void
359gtk_multi_sorter_init (GtkMultiSorter *self)
360{
361 gtk_sorters_init (self: &self->sorters);
362
363 gtk_sorter_changed_with_keys (self: GTK_SORTER (ptr: self),
364 change: GTK_SORTER_CHANGE_DIFFERENT,
365 keys: gtk_multi_sort_keys_new (self));
366}
367
368/**
369 * gtk_multi_sorter_new:
370 *
371 * Creates a new multi sorter.
372 *
373 * This sorter compares items by trying each of the sorters
374 * in turn, until one returns non-zero. In particular, if
375 * no sorter has been added to it, it will always compare
376 * items as equal.
377 *
378 * Returns: a new `GtkMultiSorter`
379 */
380GtkMultiSorter *
381gtk_multi_sorter_new (void)
382{
383 return g_object_new (GTK_TYPE_MULTI_SORTER, NULL);
384}
385
386/**
387 * gtk_multi_sorter_append:
388 * @self: a `GtkMultiSorter`
389 * @sorter: (transfer full): a sorter to add
390 *
391 * Add @sorter to @self to use for sorting at the end.
392 *
393 * @self will consult all existing sorters before it will
394 * sort with the given @sorter.
395 */
396void
397gtk_multi_sorter_append (GtkMultiSorter *self,
398 GtkSorter *sorter)
399{
400 g_return_if_fail (GTK_IS_MULTI_SORTER (self));
401 g_return_if_fail (GTK_IS_SORTER (sorter));
402
403 g_signal_connect (sorter, "changed", G_CALLBACK (gtk_multi_sorter_changed_cb), self);
404 gtk_sorters_append (self: &self->sorters, value: sorter);
405
406 gtk_sorter_changed_with_keys (self: GTK_SORTER (ptr: self),
407 change: GTK_SORTER_CHANGE_MORE_STRICT,
408 keys: gtk_multi_sort_keys_new (self));
409}
410
411/**
412 * gtk_multi_sorter_remove:
413 * @self: a `GtkMultiSorter`
414 * @position: position of sorter to remove
415 *
416 * Removes the sorter at the given @position from the list of sorter
417 * used by @self.
418 *
419 * If @position is larger than the number of sorters, nothing happens.
420 */
421void
422gtk_multi_sorter_remove (GtkMultiSorter *self,
423 guint position)
424{
425 guint length;
426 GtkSorter *sorter;
427
428 g_return_if_fail (GTK_IS_MULTI_SORTER (self));
429
430 length = gtk_sorters_get_size (self: &self->sorters);
431 if (position >= length)
432 return;
433
434 sorter = gtk_sorters_get (self: &self->sorters, pos: position);
435 g_signal_handlers_disconnect_by_func (sorter, gtk_multi_sorter_changed_cb, self);
436 gtk_sorters_splice (self: &self->sorters, pos: position, removed: 1, FALSE, NULL, added: 0);
437
438 gtk_sorter_changed_with_keys (self: GTK_SORTER (ptr: self),
439 change: GTK_SORTER_CHANGE_LESS_STRICT,
440 keys: gtk_multi_sort_keys_new (self));
441}
442

source code of gtk/gtk/gtkmultisorter.c