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 | */ |
45 | struct _GtkMultiSorter |
46 | { |
47 | GtkSorter parent_instance; |
48 | |
49 | GtkSorters sorters; |
50 | }; |
51 | |
52 | typedef struct _GtkMultiSortKey GtkMultiSortKey; |
53 | typedef struct _GtkMultiSortKeys GtkMultiSortKeys; |
54 | |
55 | struct _GtkMultiSortKey |
56 | { |
57 | gsize offset; |
58 | GtkSortKeys *keys; |
59 | }; |
60 | |
61 | struct _GtkMultiSortKeys |
62 | { |
63 | GtkSortKeys parent_keys; |
64 | |
65 | guint n_keys; |
66 | GtkMultiSortKey keys[]; |
67 | }; |
68 | |
69 | static void |
70 | gtk_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 | |
81 | static int |
82 | gtk_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 | |
101 | static gboolean |
102 | gtk_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 | |
124 | static void |
125 | gtk_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 | |
137 | static void |
138 | gtk_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 | |
149 | static 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 | |
158 | static GtkSortKeys * |
159 | gtk_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: >K_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 | |
187 | static GType |
188 | gtk_multi_sorter_get_item_type (GListModel *list) |
189 | { |
190 | return GTK_TYPE_SORTER; |
191 | } |
192 | |
193 | static guint |
194 | gtk_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 | |
201 | static gpointer |
202 | gtk_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 | |
213 | static void |
214 | gtk_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 | |
221 | static GtkBuildableIface *parent_buildable_iface; |
222 | |
223 | static void |
224 | gtk_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 | |
235 | static void |
236 | gtk_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 | |
243 | G_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 | |
247 | static GtkOrdering |
248 | gtk_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 | |
268 | static GtkSorterOrder |
269 | gtk_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 | |
299 | static void |
300 | gtk_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 | |
330 | static void |
331 | gtk_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 | |
346 | static void |
347 | gtk_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 | |
358 | static void |
359 | gtk_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 | */ |
380 | GtkMultiSorter * |
381 | gtk_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 | */ |
396 | void |
397 | gtk_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 | */ |
421 | void |
422 | gtk_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 | |