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