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 | |
52 | typedef struct _GtkSorterPrivate GtkSorterPrivate; |
53 | typedef struct _GtkDefaultSortKeys GtkDefaultSortKeys; |
54 | |
55 | struct _GtkSorterPrivate |
56 | { |
57 | GtkSortKeys *keys; |
58 | }; |
59 | |
60 | struct _GtkDefaultSortKeys |
61 | { |
62 | GtkSortKeys keys; |
63 | GtkSorter *sorter; |
64 | }; |
65 | |
66 | enum { |
67 | CHANGED, |
68 | LAST_SIGNAL |
69 | }; |
70 | |
71 | G_DEFINE_TYPE_WITH_PRIVATE (GtkSorter, gtk_sorter, G_TYPE_OBJECT) |
72 | |
73 | static guint signals[LAST_SIGNAL] = { 0 }; |
74 | |
75 | static GtkOrdering |
76 | gtk_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 | |
85 | static GtkSorterOrder |
86 | gtk_sorter_default_get_order (GtkSorter *self) |
87 | { |
88 | return GTK_SORTER_ORDER_PARTIAL; |
89 | } |
90 | |
91 | static void |
92 | gtk_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 | |
102 | static void |
103 | gtk_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 | |
142 | static void |
143 | gtk_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 | */ |
170 | GtkOrdering |
171 | gtk_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 | */ |
211 | GtkSorterOrder |
212 | gtk_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 | |
219 | static int |
220 | gtk_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 | |
231 | static void |
232 | gtk_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 | |
241 | static gboolean |
242 | gtk_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 | |
251 | static void |
252 | gtk_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 | |
261 | static void |
262 | gtk_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 | |
270 | static 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 | */ |
295 | GtkSortKeys * |
296 | gtk_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, >K_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 | */ |
330 | void |
331 | gtk_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 | */ |
352 | void |
353 | gtk_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 | **/ |
384 | GtkOrdering |
385 | gtk_ordering_from_cmpfunc (int cmpfunc_result) |
386 | { |
387 | return (GtkOrdering) ((cmpfunc_result > 0) - (cmpfunc_result < 0)); |
388 | } |
389 | #endif |
390 | |