1 /* GIMP - The GNU Image Manipulation Program
2 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
3 *
4 * gimptag.c
5 * Copyright (C) 2008 Aurimas Juška <aurisj@svn.gnome.org>
6 *
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <https://www.gnu.org/licenses/>.
19 */
20
21 #include "config.h"
22
23 #include <glib-object.h>
24 #include <string.h>
25
26 #include "core-types.h"
27
28 #include "gimptag.h"
29
30
31 #define GIMP_TAG_INTERNAL_PREFIX "gimp:"
32
33
G_DEFINE_TYPE(GimpTag,gimp_tag,G_TYPE_OBJECT)34 G_DEFINE_TYPE (GimpTag, gimp_tag, G_TYPE_OBJECT)
35
36 #define parent_class gimp_tag_parent_class
37
38
39 static void
40 gimp_tag_class_init (GimpTagClass *klass)
41 {
42 }
43
44 static void
gimp_tag_init(GimpTag * tag)45 gimp_tag_init (GimpTag *tag)
46 {
47 tag->tag = 0;
48 tag->collate_key = 0;
49 tag->internal = FALSE;
50 }
51
52 /**
53 * gimp_tag_new:
54 * @tag_string: a tag name.
55 *
56 * If given tag name is not valid, an attempt will be made to fix it.
57 *
58 * Return value: a new #GimpTag object, or NULL if tag string is invalid and
59 * cannot be fixed.
60 **/
61 GimpTag *
gimp_tag_new(const char * tag_string)62 gimp_tag_new (const char *tag_string)
63 {
64 GimpTag *tag;
65 gchar *tag_name;
66 gchar *case_folded;
67 gchar *collate_key;
68
69 g_return_val_if_fail (tag_string != NULL, NULL);
70
71 tag_name = gimp_tag_string_make_valid (tag_string);
72 if (! tag_name)
73 return NULL;
74
75 tag = g_object_new (GIMP_TYPE_TAG, NULL);
76
77 tag->tag = g_quark_from_string (tag_name);
78
79 case_folded = g_utf8_casefold (tag_name, -1);
80 collate_key = g_utf8_collate_key (case_folded, -1);
81 tag->collate_key = g_quark_from_string (collate_key);
82 g_free (collate_key);
83 g_free (case_folded);
84 g_free (tag_name);
85
86 return tag;
87 }
88
89 /**
90 * gimp_tag_try_new:
91 * @tag_string: a tag name.
92 *
93 * Similar to gimp_tag_new(), but returns NULL if tag is surely not equal
94 * to any of currently created tags. It is useful for tag querying to avoid
95 * unneeded comparisons. If tag is created, however, it does not mean that
96 * it would necessarily match with some other tag.
97 *
98 * Return value: new #GimpTag object, or NULL if tag will not match with any
99 * other #GimpTag.
100 **/
101 GimpTag *
gimp_tag_try_new(const char * tag_string)102 gimp_tag_try_new (const char *tag_string)
103 {
104 GimpTag *tag;
105 gchar *tag_name;
106 gchar *case_folded;
107 gchar *collate_key;
108 GQuark tag_quark;
109 GQuark collate_key_quark;
110
111 tag_name = gimp_tag_string_make_valid (tag_string);
112 if (! tag_name)
113 return NULL;
114
115 case_folded = g_utf8_casefold (tag_name, -1);
116 collate_key = g_utf8_collate_key (case_folded, -1);
117 collate_key_quark = g_quark_try_string (collate_key);
118 g_free (collate_key);
119 g_free (case_folded);
120
121 if (! collate_key_quark)
122 {
123 g_free (tag_name);
124 return NULL;
125 }
126
127 tag_quark = g_quark_from_string (tag_name);
128 g_free (tag_name);
129 if (! tag_quark)
130 return NULL;
131
132 tag = g_object_new (GIMP_TYPE_TAG, NULL);
133 tag->tag = tag_quark;
134 tag->collate_key = collate_key_quark;
135
136 return tag;
137 }
138
139 /**
140 * gimp_tag_get_internal:
141 * @tag: a gimp tag.
142 *
143 * Retrieve internal status of the tag.
144 *
145 * Return value: internal status of tag. Internal tags are not saved.
146 **/
147 gboolean
gimp_tag_get_internal(GimpTag * tag)148 gimp_tag_get_internal (GimpTag *tag)
149 {
150 g_return_val_if_fail (GIMP_IS_TAG (tag), FALSE);
151
152 return tag->internal;
153 }
154
155 /**
156 * gimp_tag_set_internal:
157 * @tag: a gimp tag.
158 * @internal: desired tag internal status
159 *
160 * Set internal status of the tag. Internal tags are usually automatically
161 * generated and will not be saved into users tag cache.
162 *
163 **/
164 void
gimp_tag_set_internal(GimpTag * tag,gboolean internal)165 gimp_tag_set_internal (GimpTag *tag, gboolean internal)
166 {
167 g_return_if_fail (GIMP_IS_TAG (tag));
168
169 tag->internal = internal;
170 }
171
172
173 /**
174 * gimp_tag_get_name:
175 * @tag: a gimp tag.
176 *
177 * Retrieve name of the tag.
178 *
179 * Return value: name of tag.
180 **/
181 const gchar *
gimp_tag_get_name(GimpTag * tag)182 gimp_tag_get_name (GimpTag *tag)
183 {
184 g_return_val_if_fail (GIMP_IS_TAG (tag), NULL);
185
186 return g_quark_to_string (tag->tag);
187 }
188
189 /**
190 * gimp_tag_get_hash:
191 * @tag: a gimp tag.
192 *
193 * Hashing function which is useful, for example, to store #GimpTag in
194 * a #GHashTable.
195 *
196 * Return value: hash value for tag.
197 **/
198 guint
gimp_tag_get_hash(GimpTag * tag)199 gimp_tag_get_hash (GimpTag *tag)
200 {
201 g_return_val_if_fail (GIMP_IS_TAG (tag), -1);
202
203 return tag->collate_key;
204 }
205
206 /**
207 * gimp_tag_equals:
208 * @tag: a gimp tag.
209 * @other: another gimp tag to compare with.
210 *
211 * Compares tags for equality according to tag comparison rules.
212 *
213 * Return value: TRUE if tags are equal, FALSE otherwise.
214 **/
215 gboolean
gimp_tag_equals(GimpTag * tag,GimpTag * other)216 gimp_tag_equals (GimpTag *tag,
217 GimpTag *other)
218 {
219 g_return_val_if_fail (GIMP_IS_TAG (tag), FALSE);
220 g_return_val_if_fail (GIMP_IS_TAG (other), FALSE);
221
222 return tag->collate_key == other->collate_key;
223 }
224
225 /**
226 * gimp_tag_compare_func:
227 * @p1: pointer to left-hand #GimpTag object.
228 * @p2: pointer to right-hand #GimpTag object.
229 *
230 * Compares tags according to tag comparison rules. Useful for sorting
231 * functions.
232 *
233 * Return value: meaning of return value is the same as in strcmp().
234 **/
235 int
gimp_tag_compare_func(const void * p1,const void * p2)236 gimp_tag_compare_func (const void *p1,
237 const void *p2)
238 {
239 GimpTag *t1 = GIMP_TAG (p1);
240 GimpTag *t2 = GIMP_TAG (p2);
241
242 return g_strcmp0 (g_quark_to_string (t1->collate_key),
243 g_quark_to_string (t2->collate_key));
244 }
245
246 /**
247 * gimp_tag_compare_with_string:
248 * @tag: a #GimpTag object.
249 * @tag_string: the string to compare to.
250 *
251 * Compares tag and a string according to tag comparison rules. Similar to
252 * gimp_tag_compare_func(), but can be used without creating temporary tag
253 * object.
254 *
255 * Return value: meaning of return value is the same as in strcmp().
256 **/
257 gint
gimp_tag_compare_with_string(GimpTag * tag,const gchar * tag_string)258 gimp_tag_compare_with_string (GimpTag *tag,
259 const gchar *tag_string)
260 {
261 gchar *case_folded;
262 const gchar *collate_key;
263 gchar *collate_key2;
264 gint result;
265
266 g_return_val_if_fail (GIMP_IS_TAG (tag), 0);
267 g_return_val_if_fail (tag_string != NULL, 0);
268
269 collate_key = g_quark_to_string (tag->collate_key);
270 case_folded = g_utf8_casefold (tag_string, -1);
271 collate_key2 = g_utf8_collate_key (case_folded, -1);
272 result = g_strcmp0 (collate_key, collate_key2);
273 g_free (collate_key2);
274 g_free (case_folded);
275
276 return result;
277 }
278
279 /**
280 * gimp_tag_has_prefix:
281 * @tag: a #GimpTag object.
282 * @prefix_string: the prefix to compare to.
283 *
284 * Compares tag and a prefix according to tag comparison rules. Similar to
285 * gimp_tag_compare_with_string(), but does not work on the collate key
286 * because that can't be matched partially.
287 *
288 * Return value: wheher #tag starts with @prefix_string.
289 **/
290 gboolean
gimp_tag_has_prefix(GimpTag * tag,const gchar * prefix_string)291 gimp_tag_has_prefix (GimpTag *tag,
292 const gchar *prefix_string)
293 {
294 gchar *case_folded1;
295 gchar *case_folded2;
296 gboolean has_prefix;
297
298 g_return_val_if_fail (GIMP_IS_TAG (tag), FALSE);
299 g_return_val_if_fail (prefix_string != NULL, FALSE);
300
301 case_folded1 = g_utf8_casefold (g_quark_to_string (tag->tag), -1);
302 case_folded2 = g_utf8_casefold (prefix_string, -1);
303
304 has_prefix = g_str_has_prefix (case_folded1, case_folded2);
305
306 g_free (case_folded1);
307 g_free (case_folded2);
308
309 g_printerr ("'%s' has prefix '%s': %d\n",
310 g_quark_to_string (tag->tag), prefix_string, has_prefix);
311
312 return has_prefix;
313 }
314
315 /**
316 * gimp_tag_string_make_valid:
317 * @tag_string: a text string.
318 *
319 * Tries to create a valid tag string from given @tag_string.
320 *
321 * Return value: a newly allocated tag string in case given @tag_string was
322 * valid or could be fixed, otherwise NULL. Allocated value should be freed
323 * using g_free().
324 **/
325 gchar *
gimp_tag_string_make_valid(const gchar * tag_string)326 gimp_tag_string_make_valid (const gchar *tag_string)
327 {
328 gchar *tag;
329 GString *buffer;
330 gchar *tag_cursor;
331 gunichar c;
332
333 g_return_val_if_fail (tag_string, NULL);
334
335 tag = g_utf8_normalize (tag_string, -1, G_NORMALIZE_ALL);
336 if (! tag)
337 return NULL;
338
339 tag = g_strstrip (tag);
340 if (! *tag)
341 {
342 g_free (tag);
343 return NULL;
344 }
345
346 buffer = g_string_new ("");
347 tag_cursor = tag;
348 if (g_str_has_prefix (tag_cursor, GIMP_TAG_INTERNAL_PREFIX))
349 {
350 tag_cursor += strlen (GIMP_TAG_INTERNAL_PREFIX);
351 }
352 do
353 {
354 c = g_utf8_get_char (tag_cursor);
355 tag_cursor = g_utf8_next_char (tag_cursor);
356 if (g_unichar_isprint (c)
357 && ! gimp_tag_is_tag_separator (c))
358 {
359 g_string_append_unichar (buffer, c);
360 }
361 } while (c);
362
363 g_free (tag);
364 tag = g_string_free (buffer, FALSE);
365 tag = g_strstrip (tag);
366
367 if (! *tag)
368 {
369 g_free (tag);
370 return NULL;
371 }
372
373 return tag;
374 }
375
376 /**
377 * gimp_tag_is_tag_separator:
378 * @c: Unicode character.
379 *
380 * Defines a set of characters that are considered tag separators. The
381 * tag separators are hand-picked from the set of characters with the
382 * Terminal_Punctuation property as specified in the version 5.1.0 of
383 * the Unicode Standard.
384 *
385 * Return value: %TRUE if the character is a tag separator.
386 */
387 gboolean
gimp_tag_is_tag_separator(gunichar c)388 gimp_tag_is_tag_separator (gunichar c)
389 {
390 switch (c)
391 {
392 case 0x002C: /* COMMA */
393 case 0x060C: /* ARABIC COMMA */
394 case 0x07F8: /* NKO COMMA */
395 case 0x1363: /* ETHIOPIC COMMA */
396 case 0x1802: /* MONGOLIAN COMMA */
397 case 0x1808: /* MONGOLIAN MANCHU COMMA */
398 case 0x3001: /* IDEOGRAPHIC COMMA */
399 case 0xA60D: /* VAI COMMA */
400 case 0xFE50: /* SMALL COMMA */
401 case 0xFF0C: /* FULLWIDTH COMMA */
402 case 0xFF64: /* HALFWIDTH IDEOGRAPHIC COMMA */
403 return TRUE;
404
405 default:
406 return FALSE;
407 }
408 }
409
410 /**
411 * gimp_tag_or_null_ref:
412 * @tag: a #GimpTag
413 *
414 * A simple wrapper around g_object_ref() that silently accepts #NULL.
415 **/
416 void
gimp_tag_or_null_ref(GimpTag * tag_or_null)417 gimp_tag_or_null_ref (GimpTag *tag_or_null)
418 {
419 if (tag_or_null)
420 {
421 g_return_if_fail (GIMP_IS_TAG (tag_or_null));
422
423 g_object_ref (tag_or_null);
424 }
425 }
426
427 /**
428 * gimp_tag_or_null_unref:
429 * @tag: a #GimpTag
430 *
431 * A simple wrapper around g_object_unref() that silently accepts #NULL.
432 **/
433 void
gimp_tag_or_null_unref(GimpTag * tag_or_null)434 gimp_tag_or_null_unref (GimpTag *tag_or_null)
435 {
436 if (tag_or_null)
437 {
438 g_return_if_fail (GIMP_IS_TAG (tag_or_null));
439
440 g_object_unref (tag_or_null);
441 }
442 }
443