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 "gtkstringsorter.h"
23
24#include "gtkintl.h"
25#include "gtksorterprivate.h"
26#include "gtktypebuiltins.h"
27
28/**
29 * GtkStringSorter:
30 *
31 * `GtkStringSorter` is a `GtkSorter` that compares strings.
32 *
33 * It does the comparison in a linguistically correct way using the
34 * current locale by normalizing Unicode strings and possibly case-folding
35 * them before performing the comparison.
36 *
37 * To obtain the strings to compare, this sorter evaluates a
38 * [class@Gtk.Expression].
39 */
40
41struct _GtkStringSorter
42{
43 GtkSorter parent_instance;
44
45 gboolean ignore_case;
46
47 GtkExpression *expression;
48};
49
50enum {
51 PROP_0,
52 PROP_EXPRESSION,
53 PROP_IGNORE_CASE,
54 NUM_PROPERTIES
55};
56
57G_DEFINE_TYPE (GtkStringSorter, gtk_string_sorter, GTK_TYPE_SORTER)
58
59static GParamSpec *properties[NUM_PROPERTIES] = { NULL, };
60
61static char *
62gtk_string_sorter_get_key (GtkExpression *expression,
63 gboolean ignore_case,
64 gpointer item1)
65{
66 GValue value = G_VALUE_INIT;
67 char *s;
68
69 if (expression == NULL)
70 return NULL;
71
72 if (!gtk_expression_evaluate (self: expression, this_: item1, value: &value))
73 return NULL;
74
75 /* If strings are NULL, order them before "". */
76 if (ignore_case)
77 {
78 char *t;
79
80 t = g_utf8_casefold (str: g_value_get_string (value: &value), len: -1);
81 s = g_utf8_collate_key (str: t, len: -1);
82 g_free (mem: t);
83 }
84 else
85 {
86 s = g_utf8_collate_key (str: g_value_get_string (value: &value), len: -1);
87 }
88
89 g_value_unset (value: &value);
90
91 return s;
92}
93
94static GtkOrdering
95gtk_string_sorter_compare (GtkSorter *sorter,
96 gpointer item1,
97 gpointer item2)
98{
99 GtkStringSorter *self = GTK_STRING_SORTER (ptr: sorter);
100 char *s1, *s2;
101 GtkOrdering result;
102
103 if (self->expression == NULL)
104 return GTK_ORDERING_EQUAL;
105
106 s1 = gtk_string_sorter_get_key (expression: self->expression, ignore_case: self->ignore_case, item1);
107 s2 = gtk_string_sorter_get_key (expression: self->expression, ignore_case: self->ignore_case, item1: item2);
108
109 result = gtk_ordering_from_cmpfunc (cmpfunc_result: g_strcmp0 (str1: s1, str2: s2));
110
111 g_free (mem: s1);
112 g_free (mem: s2);
113
114 return result;
115}
116
117static GtkSorterOrder
118gtk_string_sorter_get_order (GtkSorter *sorter)
119{
120 GtkStringSorter *self = GTK_STRING_SORTER (ptr: sorter);
121
122 if (self->expression == NULL)
123 return GTK_SORTER_ORDER_NONE;
124
125 return GTK_SORTER_ORDER_PARTIAL;
126}
127
128typedef struct _GtkStringSortKeys GtkStringSortKeys;
129struct _GtkStringSortKeys
130{
131 GtkSortKeys keys;
132
133 GtkExpression *expression;
134 gboolean ignore_case;
135};
136
137static void
138gtk_string_sort_keys_free (GtkSortKeys *keys)
139{
140 GtkStringSortKeys *self = (GtkStringSortKeys *) keys;
141
142 gtk_expression_unref (self: self->expression);
143 g_slice_free (GtkStringSortKeys, self);
144}
145
146static int
147gtk_string_sort_keys_compare (gconstpointer a,
148 gconstpointer b,
149 gpointer unused)
150{
151 const char *sa = *(const char **) a;
152 const char *sb = *(const char **) b;
153
154 if (sa == NULL)
155 return sb == NULL ? GTK_ORDERING_EQUAL : GTK_ORDERING_LARGER;
156 else if (sb == NULL)
157 return GTK_ORDERING_SMALLER;
158
159 return gtk_ordering_from_cmpfunc (cmpfunc_result: strcmp (s1: sa, s2: sb));
160}
161
162static gboolean
163gtk_string_sort_keys_is_compatible (GtkSortKeys *keys,
164 GtkSortKeys *other)
165{
166 return FALSE;
167}
168
169static void
170gtk_string_sort_keys_init_key (GtkSortKeys *keys,
171 gpointer item,
172 gpointer key_memory)
173{
174 GtkStringSortKeys *self = (GtkStringSortKeys *) keys;
175 char **key = (char **) key_memory;
176
177 *key = gtk_string_sorter_get_key (expression: self->expression, ignore_case: self->ignore_case, item1: item);
178}
179
180static void
181gtk_string_sort_keys_clear_key (GtkSortKeys *keys,
182 gpointer key_memory)
183{
184 char **key = (char **) key_memory;
185
186 g_free (mem: *key);
187}
188
189static const GtkSortKeysClass GTK_STRING_SORT_KEYS_CLASS =
190{
191 gtk_string_sort_keys_free,
192 gtk_string_sort_keys_compare,
193 gtk_string_sort_keys_is_compatible,
194 gtk_string_sort_keys_init_key,
195 gtk_string_sort_keys_clear_key,
196};
197
198static GtkSortKeys *
199gtk_string_sort_keys_new (GtkStringSorter *self)
200{
201 GtkStringSortKeys *result;
202
203 if (self->expression == NULL)
204 return gtk_sort_keys_new_equal ();
205
206 result = gtk_sort_keys_new (GtkStringSortKeys,
207 &GTK_STRING_SORT_KEYS_CLASS,
208 sizeof (char *),
209 sizeof (char *));
210
211 result->expression = gtk_expression_ref (self: self->expression);
212 result->ignore_case = self->ignore_case;
213
214 return (GtkSortKeys *) result;
215}
216
217static void
218gtk_string_sorter_set_property (GObject *object,
219 guint prop_id,
220 const GValue *value,
221 GParamSpec *pspec)
222{
223 GtkStringSorter *self = GTK_STRING_SORTER (ptr: object);
224
225 switch (prop_id)
226 {
227 case PROP_EXPRESSION:
228 gtk_string_sorter_set_expression (self, expression: gtk_value_get_expression (value));
229 break;
230
231 case PROP_IGNORE_CASE:
232 gtk_string_sorter_set_ignore_case (self, ignore_case: g_value_get_boolean (value));
233 break;
234
235 default:
236 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
237 break;
238 }
239}
240
241static void
242gtk_string_sorter_get_property (GObject *object,
243 guint prop_id,
244 GValue *value,
245 GParamSpec *pspec)
246{
247 GtkStringSorter *self = GTK_STRING_SORTER (ptr: object);
248
249 switch (prop_id)
250 {
251 case PROP_EXPRESSION:
252 gtk_value_set_expression (value, expression: self->expression);
253 break;
254
255 case PROP_IGNORE_CASE:
256 g_value_set_boolean (value, v_boolean: self->ignore_case);
257 break;
258
259 default:
260 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
261 break;
262 }
263}
264
265static void
266gtk_string_sorter_dispose (GObject *object)
267{
268 GtkStringSorter *self = GTK_STRING_SORTER (ptr: object);
269
270 g_clear_pointer (&self->expression, gtk_expression_unref);
271
272 G_OBJECT_CLASS (gtk_string_sorter_parent_class)->dispose (object);
273}
274
275static void
276gtk_string_sorter_class_init (GtkStringSorterClass *class)
277{
278 GtkSorterClass *sorter_class = GTK_SORTER_CLASS (ptr: class);
279 GObjectClass *object_class = G_OBJECT_CLASS (class);
280
281 sorter_class->compare = gtk_string_sorter_compare;
282 sorter_class->get_order = gtk_string_sorter_get_order;
283
284 object_class->get_property = gtk_string_sorter_get_property;
285 object_class->set_property = gtk_string_sorter_set_property;
286 object_class->dispose = gtk_string_sorter_dispose;
287
288 /**
289 * GtkStringSorter:expression: (type GtkExpression) (attributes org.gtk.Property.get=gtk_string_sorter_get_expression org.gtk.Property.set=gtk_string_sorter_set_expression)
290 *
291 * The expression to evaluate on item to get a string to compare with.
292 */
293 properties[PROP_EXPRESSION] =
294 gtk_param_spec_expression (name: "expression",
295 P_("Expression"),
296 P_("Expression to compare with"),
297 flags: G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
298
299 /**
300 * GtkStringSorter:ignore-case: (attributes org.gtk.Property.get=gtk_string_sorter_get_ignore_case org.gtk.Property.set=gtk_string_sorter_set_ignore_case)
301 *
302 * If matching is case sensitive.
303 */
304 properties[PROP_IGNORE_CASE] =
305 g_param_spec_boolean (name: "ignore-case",
306 P_("Ignore case"),
307 P_("If matching is case sensitive"),
308 TRUE,
309 flags: G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
310
311 g_object_class_install_properties (oclass: object_class, n_pspecs: NUM_PROPERTIES, pspecs: properties);
312
313}
314
315static void
316gtk_string_sorter_init (GtkStringSorter *self)
317{
318 self->ignore_case = TRUE;
319
320 gtk_sorter_changed_with_keys (self: GTK_SORTER (ptr: self),
321 change: GTK_SORTER_CHANGE_DIFFERENT,
322 keys: gtk_string_sort_keys_new (self));
323}
324
325/**
326 * gtk_string_sorter_new:
327 * @expression: (transfer full) (nullable): The expression to evaluate
328 *
329 * Creates a new string sorter that compares items using the given
330 * @expression.
331 *
332 * Unless an expression is set on it, this sorter will always
333 * compare items as invalid.
334 *
335 * Returns: a new `GtkStringSorter`
336 */
337GtkStringSorter *
338gtk_string_sorter_new (GtkExpression *expression)
339{
340 GtkStringSorter *result;
341
342 result = g_object_new (GTK_TYPE_STRING_SORTER,
343 first_property_name: "expression", expression,
344 NULL);
345
346 g_clear_pointer (&expression, gtk_expression_unref);
347
348 return result;
349}
350
351/**
352 * gtk_string_sorter_get_expression: (attributes org.gtk.Method.get_property=expression)
353 * @self: a `GtkStringSorter`
354 *
355 * Gets the expression that is evaluated to obtain strings from items.
356 *
357 * Returns: (transfer none) (nullable): a `GtkExpression`
358 */
359GtkExpression *
360gtk_string_sorter_get_expression (GtkStringSorter *self)
361{
362 g_return_val_if_fail (GTK_IS_STRING_SORTER (self), NULL);
363
364 return self->expression;
365}
366
367/**
368 * gtk_string_sorter_set_expression: (attributes org.gtk.Method.set_property=expression)
369 * @self: a `GtkStringSorter`
370 * @expression: (nullable) (transfer none): a `GtkExpression`
371 *
372 * Sets the expression that is evaluated to obtain strings from items.
373 *
374 * The expression must have the type %G_TYPE_STRING.
375 */
376void
377gtk_string_sorter_set_expression (GtkStringSorter *self,
378 GtkExpression *expression)
379{
380 g_return_if_fail (GTK_IS_STRING_SORTER (self));
381 g_return_if_fail (expression == NULL || gtk_expression_get_value_type (expression) == G_TYPE_STRING);
382
383 if (self->expression == expression)
384 return;
385
386 g_clear_pointer (&self->expression, gtk_expression_unref);
387 if (expression)
388 self->expression = gtk_expression_ref (self: expression);
389
390 gtk_sorter_changed_with_keys (self: GTK_SORTER (ptr: self),
391 change: GTK_SORTER_CHANGE_DIFFERENT,
392 keys: gtk_string_sort_keys_new (self));
393
394 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_EXPRESSION]);
395}
396
397/**
398 * gtk_string_sorter_get_ignore_case: (attributes org.gtk.Method.get_property=ignore-case)
399 * @self: a `GtkStringSorter`
400 *
401 * Gets whether the sorter ignores case differences.
402 *
403 * Returns: %TRUE if @self is ignoring case differences
404 */
405gboolean
406gtk_string_sorter_get_ignore_case (GtkStringSorter *self)
407{
408 g_return_val_if_fail (GTK_IS_STRING_SORTER (self), TRUE);
409
410 return self->ignore_case;
411}
412
413/**
414 * gtk_string_sorter_set_ignore_case: (attributes org.gtk.Method.set_property=ignore-case)
415 * @self: a `GtkStringSorter`
416 * @ignore_case: %TRUE to ignore case differences
417 *
418 * Sets whether the sorter will ignore case differences.
419 */
420void
421gtk_string_sorter_set_ignore_case (GtkStringSorter *self,
422 gboolean ignore_case)
423{
424 g_return_if_fail (GTK_IS_STRING_SORTER (self));
425
426 if (self->ignore_case == ignore_case)
427 return;
428
429 self->ignore_case = ignore_case;
430
431 gtk_sorter_changed_with_keys (self: GTK_SORTER (ptr: self),
432 change: ignore_case ? GTK_SORTER_CHANGE_LESS_STRICT : GTK_SORTER_CHANGE_MORE_STRICT,
433 keys: gtk_string_sort_keys_new (self));
434
435 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_IGNORE_CASE]);
436}
437

source code of gtk/gtk/gtkstringsorter.c