Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
tags.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2010-2011 Henrik Andersson.
4 Copyright (C) 2010-2012 johannes hanika.
5 Copyright (C) 2011 Simon Spannagel.
6 Copyright (C) 2011-2014, 2016-2017 Tobias Ellinghaus.
7 Copyright (C) 2012 Ivan Tarozzi.
8 Copyright (C) 2012 James C. McPherson.
9 Copyright (C) 2012 José Carlos García Sogo.
10 Copyright (C) 2012 Richard Wonka.
11 Copyright (C) 2013 Dennis Gnad.
12 Copyright (C) 2013 Jérémy Rosen.
13 Copyright (C) 2013-2015, 2019-2021 Pascal Obry.
14 Copyright (C) 2013-2014, 2016 Roman Lebedev.
15 Copyright (C) 2016-2017 Peter Budai.
16 Copyright (C) 2016 piterdias.
17 Copyright (C) 2019 Edgardo Hoszowski.
18 Copyright (C) 2019-2020 Heiko Bauke.
19 Copyright (C) 2019-2022 Philippe Weyland.
20 Copyright (C) 2020-2021 Aldric Renaudin.
21 Copyright (C) 2020 David-Tillmann Schaefer.
22 Copyright (C) 2020-2021 Hubert Kowalski.
23 Copyright (C) 2021 luzpaz.
24 Copyright (C) 2021 Ralf Brown.
25 Copyright (C) 2022-2023, 2025-2026 Aurélien PIERRE.
26 Copyright (C) 2022 Martin Bařinka.
27 Copyright (C) 2022 Miloš Komarčević.
28
29 darktable is free software: you can redistribute it and/or modify
30 it under the terms of the GNU General Public License as published by
31 the Free Software Foundation, either version 3 of the License, or
32 (at your option) any later version.
33
34 darktable is distributed in the hope that it will be useful,
35 but WITHOUT ANY WARRANTY; without even the implied warranty of
36 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
37 GNU General Public License for more details.
38
39 You should have received a copy of the GNU General Public License
40 along with darktable. If not, see <http://www.gnu.org/licenses/>.
41*/
42#include <glib/gstdio.h>
43#include "common/act_on.h"
44#include "metadata/notify.h"
46#include "common/utility.h"
47#include "metadata/tags.h"
48#include "system/macros.h"
49#include "system/mem_alloc.h"
50#include "common/image.h"
52#include "common/grouping.h"
53#include "common/selection.h"
54#include "common/undo.h"
55#include "common/conf.h"
56#include <glib.h>
57#if defined (_WIN32)
58#include "win/getdelim.h"
59#endif // defined (_WIN32)
60
61
62typedef struct dt_undo_tags_t
63{
64 int32_t imgid;
65 GList *before; // list of tagid before
66 GList *after; // list of tagid after
68
69static gchar *_get_tb_removed_tag_string_values(GList *before, GList *after)
70{
71 GList *a = after;
72 gchar *tag_list = NULL;
73 for(GList *b = before; b; b = g_list_next(b))
74 {
75 if(!g_list_find(a, b->data))
76 {
77 tag_list = dt_util_dstrcat(tag_list, "%d,", GPOINTER_TO_INT(b->data));
78 }
79 }
80 if(tag_list) tag_list[strlen(tag_list) - 1] = '\0';
81 return tag_list;
82}
83
84static gchar *_get_tb_added_tag_string_values(const int img, GList *before, GList *after)
85{
86 GList *b = before;
87 gchar *tag_list = NULL;
88 for(GList *a = after; a; a = g_list_next(a))
89 {
90 if(!g_list_find(b, a->data))
91 {
92 // clang-format off
93 tag_list = dt_util_dstrcat(tag_list,
94 "(%d,%d,"
95 " (SELECT (IFNULL(MAX(position),0) & 0xFFFFFFFF00000000) + (1 << 32)"
96 " FROM main.tagged_images)"
97 "),",
98 GPOINTER_TO_INT(img),
99 GPOINTER_TO_INT(a->data));
100 // clang-format on
101 }
102 }
103 if(tag_list) tag_list[strlen(tag_list) - 1] = '\0';
104 return tag_list;
105}
106
107static void _bulk_remove_tags(const int img, const gchar *tag_list)
108{
109 dt_tag_repository_detach_batch(img, tag_list);
110}
111
112static void _bulk_add_tags(const gchar *tag_list)
113{
115}
116
117static void _pop_undo_execute(const int32_t imgid, GList *before, GList *after)
118{
119 gchar *tobe_removed_list = _get_tb_removed_tag_string_values(before, after);
120 gchar *tobe_added_list = _get_tb_added_tag_string_values(imgid, before, after);
121
122 _bulk_remove_tags(imgid, tobe_removed_list);
123 _bulk_add_tags(tobe_added_list);
124
125 dt_free(tobe_removed_list);
126 dt_free(tobe_added_list);
127}
128
129static void _pop_undo(gpointer user_data, dt_undo_type_t type, dt_undo_data_t data, dt_undo_action_t action, GList **imgs)
130{
131 if(type == DT_UNDO_TAGS)
132 {
133 for(GList *list = (GList *)data; list; list = g_list_next(list))
134 {
135 dt_undo_tags_t *undotags = (dt_undo_tags_t *)list->data;
136
137 GList *before = (action == DT_ACTION_UNDO) ? undotags->after : undotags->before;
138 GList *after = (action == DT_ACTION_UNDO) ? undotags->before : undotags->after;
139 _pop_undo_execute(undotags->imgid, before, after);
140 *imgs = g_list_prepend(*imgs, GINT_TO_POINTER(undotags->imgid));
141 }
142
144 }
145}
146
147static void _undo_tags_free(gpointer data)
148{
149 dt_undo_tags_t *undotags = (dt_undo_tags_t *)data;
150 g_list_free(undotags->before);
151 undotags->before = NULL;
152 g_list_free(undotags->after);
153 undotags->after = NULL;
154 dt_free(undotags);
155}
156
157static void _tags_undo_data_free(gpointer data)
158{
159 GList *l = (GList *)data;
160 g_list_free_full(l, _undo_tags_free);
161 l = NULL;
162}
163
164gboolean dt_tag_new(const char *name, guint *tagid)
165{
166 if(IS_NULL_PTR(name) || name[0] == '\0') return FALSE; // no tagid name.
167
169 if(id)
170 {
171 // tagid already exists.
172 if(!IS_NULL_PTR(tagid)) *tagid = id;
173 return TRUE;
174 }
175
177
178 if(id && g_strstr_len(name, -1, "darktable|") == name)
180
181 if(!IS_NULL_PTR(tagid))
182 *tagid = id;
183
184 return TRUE;
185}
186
187gboolean dt_tag_new_from_gui(const char *name, guint *tagid)
188{
189 const gboolean ret = dt_tag_new(name, tagid);
190 /* if everything went fine, raise signal of tags change to refresh keywords module in GUI */
191 if(ret) dt_metadata_tags_changed();
192 return ret;
193}
194
195guint dt_tag_remove(const guint tagid, gboolean final)
196{
197 const int count = dt_tag_repository_count_attachments(tagid);
198
199 if(final == TRUE)
201
202 return count;
203}
204
205void dt_tag_delete_tag_batch(const char *flatlist)
206{
209}
210
211guint dt_tag_remove_list(GList *tag_list)
212{
213 if (!tag_list) return 0;
214
215 char *flatlist = NULL;
216 guint count = 0;
217 guint tcount = 0;
218 for (GList *taglist = tag_list; taglist ; taglist = g_list_next(taglist))
219 {
220 const guint tagid = ((dt_tag_t *)taglist->data)->id;
221 flatlist = dt_util_dstrcat(flatlist, "%u,", tagid);
222 count++;
223 if(flatlist && count > 1000)
224 {
225 flatlist[strlen(flatlist)-1] = '\0';
226 dt_tag_delete_tag_batch(flatlist);
227 dt_free(flatlist);
228 tcount = tcount + count;
229 count = 0;
230 }
231 }
232 if(flatlist)
233 {
234 flatlist[strlen(flatlist)-1] = '\0';
235 dt_tag_delete_tag_batch(flatlist);
236 dt_free(flatlist);
237 tcount = tcount + count;
238 }
239 return tcount;
240}
241
242gchar *dt_tag_get_name(const guint tagid)
243{
244 return dt_tag_repository_get_name(tagid);
245}
246
247void dt_tag_rename(const guint tagid, const gchar *new_tagname)
248{
249 if(IS_NULL_PTR(new_tagname) || !new_tagname[0]) return;
250 if(dt_tag_exists(new_tagname, NULL)) return;
251
252 dt_tag_repository_rename(tagid, new_tagname);
253}
254
255gboolean dt_tag_exists(const char *name, guint *tagid)
256{
257 const guint id = dt_tag_repository_find_by_name(name);
258
259 if(id)
260 {
261 if(!IS_NULL_PTR(tagid)) *tagid = id;
262 return TRUE;
263 }
264
265 if(!IS_NULL_PTR(tagid)) *tagid = -1;
266 return FALSE;
267}
268
269static gboolean _tag_add_tags_to_list(GList **list, const GList *tags)
270{
271 gboolean res = FALSE;
272 for(const GList *t = tags; t; t = g_list_next(t))
273 {
274 if(!g_list_find(*list, t->data))
275 {
276 *list = g_list_prepend(*list, t->data);
277 res = TRUE;
278 }
279 }
280 return res;
281}
282
283static gboolean _tag_remove_tags_from_list(GList **list, const GList *tags)
284{
285 const int nb_ini = g_list_length(*list);
286 for(const GList *t = tags; t; t = g_list_next(t))
287 {
288 *list = g_list_remove(*list, t->data);
289 }
290 return (g_list_length(*list) != nb_ini);
291}
292
299
307
308static GList *_tag_get_tags(const int32_t imgid, const dt_tag_type_t type);
309
310static gboolean _tag_execute(const GList *tags, const GList *imgs, GList **undo, const gboolean undo_on,
311 const gint action)
312{
313 gboolean res = FALSE;
314 for(const GList *images = imgs; images; images = g_list_next(images))
315 {
316 const int32_t image_id = GPOINTER_TO_INT(images->data);
317 dt_undo_tags_t *undotags = (dt_undo_tags_t *)malloc(sizeof(dt_undo_tags_t));
318 undotags->imgid = image_id;
319 undotags->before = _tag_get_tags(image_id, DT_TAG_TYPE_ALL);
320 switch(action)
321 {
322 case DT_TA_ATTACH:
323 undotags->after = g_list_copy(undotags->before);
324 if(_tag_add_tags_to_list(&undotags->after, tags)) res = TRUE;
325 break;
326 case DT_TA_DETACH:
327 undotags->after = g_list_copy(undotags->before);
328 if(_tag_remove_tags_from_list(&undotags->after, tags)) res = TRUE;
329 break;
330 case DT_TA_SET:
331 undotags->after = g_list_copy((GList *)tags);
332 // preserve dt tags
333 GList *dttags = _tag_get_tags(image_id, DT_TAG_TYPE_DT);
334 if(dttags) undotags->after = g_list_concat(undotags->after, dttags);
335 res = TRUE;
336 break;
337 case DT_TA_SET_ALL:
338 undotags->after = g_list_copy((GList *)tags);
339 res = TRUE;
340 break;
341 default:
342 undotags->after = g_list_copy(undotags->before);
343 res = FALSE;
344 break;
345 }
346 _pop_undo_execute(image_id, undotags->before, undotags->after);
347 if(undo_on)
348 *undo = g_list_append(*undo, undotags);
349 else
350 _undo_tags_free(undotags);
351 }
352 return res;
353}
354
355gboolean dt_tag_attach_images(const guint tagid, const GList *img, const gboolean undo_on)
356{
357 if(IS_NULL_PTR(img)) return FALSE;
358 GList *undo = NULL;
359 GList *tags = NULL;
360
361 tags = g_list_prepend(tags, GINT_TO_POINTER(tagid));
363
364 const gboolean res = _tag_execute(tags, img, &undo, undo_on, DT_TA_ATTACH);
365
366 g_list_free(tags);
367 tags = NULL;
368 if(undo_on)
369 {
372 }
373
374 return res;
375}
376
377gboolean dt_tag_attach(const guint tagid, const int32_t imgid, const gboolean undo_on, const gboolean group_on)
378{
379 gboolean res = FALSE;
380 if(imgid == UNKNOWN_IMAGE)
381 {
382 GList *imgs = dt_act_on_get_images();
383 res = dt_tag_attach_images(tagid, imgs, undo_on);
384 g_list_free(imgs);
385 imgs = NULL;
386 }
387 else
388 {
389 if(dt_is_tag_attached(tagid, imgid)) return FALSE;
390 GList *imgs = g_list_append(NULL, GINT_TO_POINTER(imgid));
391 res = dt_tag_attach_images(tagid, imgs, undo_on);
392 g_list_free(imgs);
393 imgs = NULL;
394 }
395 return res;
396}
397
398gboolean dt_tag_set_tags(const GList *tags, const GList *img, const gboolean ignore_dt_tags,
399 const gboolean clear_on, const gboolean undo_on)
400{
401 if(img)
402 {
403 GList *undo = NULL;
405
406 const gboolean res = _tag_execute(tags, img, &undo, undo_on,
407 clear_on ? ignore_dt_tags ? DT_TA_SET : DT_TA_SET_ALL : DT_TA_ATTACH);
408 if(undo_on)
409 {
412 }
413 return res;
414 }
415 return FALSE;
416}
417
418gboolean dt_tag_attach_string_list(const gchar *tags, const GList *img, const gboolean undo_on)
419{
420 // tags may not exist yet
421 // undo only undoes the tags attachments. it doesn't remove created tags.
422 gchar **tokens = g_strsplit(tags, ",", 0);
423 gboolean res = FALSE;
424 if(tokens)
425 {
426 // tag(s) creation
427 GList *tagl = NULL;
428 gchar **entry = tokens;
429 while(*entry)
430 {
431 char *e = g_strstrip(*entry);
432 if(*e)
433 {
434 guint tagid = 0;
435 dt_tag_new(e, &tagid);
436 tagl = g_list_prepend(tagl, GINT_TO_POINTER(tagid));
437 }
438 entry++;
439 }
440
441 // attach newly created tags
442 if(img)
443 {
444 GList *undo = NULL;
446
447 res = _tag_execute(tagl, img, &undo, undo_on, DT_TA_ATTACH);
448
449 if(undo_on)
450 {
453 }
454 }
455 g_list_free(tagl);
456 tagl = NULL;
457 }
458 g_strfreev(tokens);
459 return res;
460}
461
462gboolean dt_tag_detach_images(const guint tagid, const GList *img, const gboolean undo_on)
463{
464 if(img)
465 {
466 GList *tags = NULL;
467 tags = g_list_prepend(tags, GINT_TO_POINTER(tagid));
468 GList *undo = NULL;
470
471 const gboolean res = _tag_execute(tags, img, &undo, undo_on, DT_TA_DETACH);
472
473 g_list_free(tags);
474 tags = NULL;
475 if(undo_on)
476 {
479 }
480 return res;
481 }
482 return FALSE;
483}
484
485gboolean dt_tag_detach(const guint tagid, const int32_t imgid, const gboolean undo_on, const gboolean group_on)
486{
487 GList *imgs = NULL;
488 if(imgid == UNKNOWN_IMAGE)
489 imgs = dt_act_on_get_images();
490 else
491 imgs = g_list_prepend(imgs, GINT_TO_POINTER(imgid));
492 if(group_on) dt_grouping_add_grouped_images(&imgs);
493
494 const gboolean res = dt_tag_detach_images(tagid, imgs, undo_on);
495 g_list_free(imgs);
496 imgs = NULL;
497 return res;
498}
499
500gboolean dt_tag_detach_by_string(const char *name, const int32_t imgid, const gboolean undo_on,
501 const gboolean group_on)
502{
503 if(IS_NULL_PTR(name) || !name[0]) return FALSE;
504 guint tagid = 0;
505 if(!dt_tag_exists(name, &tagid)) return FALSE;
506
507 return dt_tag_detach(tagid, imgid, undo_on, group_on);
508}
509
514
515uint32_t dt_tag_get_attached(const int32_t imgid, GList **result, const gboolean ignore_dt_tags)
516{
517 uint32_t nb_selected = 0;
518 if(imgid > 0)
519 {
520 nb_selected = 1;
521 }
522 else
523 {
525 }
526
527 uint32_t count = 0;
528 if(imgid > 0 || nb_selected > 0)
529 {
530 GList *tags = dt_tag_repository_get_attached(imgid, ignore_dt_tags);
531
532 // Create result
533 *result = NULL;
534 for(GList *l = tags; l; l = g_list_next(l))
535 {
536 dt_tag_t *t = (dt_tag_t *)l->data;
537 t->leave = g_strrstr(t->tag, "|");
538 t->leave = t->leave ? t->leave + 1 : t->tag;
539 const uint32_t imgnb = t->count;
540 t->select = (nb_selected == 0) ? DT_TS_NO_IMAGE :
541 (imgnb == nb_selected) ? DT_TS_ALL_IMAGES :
542 (imgnb == 0) ? DT_TS_NO_IMAGE : DT_TS_SOME_IMAGES;
543 *result = g_list_append(*result, t);
544 count++;
545 }
546 g_list_free(tags); // the elements moved into *result
547 }
548 return count;
549}
550
551static uint32_t _tag_get_attached_export(const int32_t imgid, GList **result)
552{
553 if(!(imgid > 0)) return 0;
554
556
557 // Create result
558 uint32_t count = 0;
559 for(GList *l = tags; l; l = g_list_next(l))
560 {
561 dt_tag_t *t = (dt_tag_t *)l->data;
562 t->leave = g_strrstr(t->tag, "|");
563 t->leave = t->leave ? t->leave + 1 : t->tag;
564 *result = g_list_append(*result, t);
565 count++;
566 }
567 g_list_free(tags); // the elements moved into *result
568
569 return count;
570}
571
572static gint sort_tag_by_path(gconstpointer a, gconstpointer b)
573{
574 const dt_tag_t *tuple_a = (const dt_tag_t *)a;
575 const dt_tag_t *tuple_b = (const dt_tag_t *)b;
576
577 return g_strcmp0(tuple_a->tag, tuple_b->tag);
578}
579
580static gint sort_tag_by_leave(gconstpointer a, gconstpointer b)
581{
582 const dt_tag_t *tuple_a = (const dt_tag_t *)a;
583 const dt_tag_t *tuple_b = (const dt_tag_t *)b;
584
585 return g_strcmp0(tuple_a->leave, tuple_b->leave);
586}
587
588static gint sort_tag_by_count(gconstpointer a, gconstpointer b)
589{
590 const dt_tag_t *tuple_a = (const dt_tag_t *)a;
591 const dt_tag_t *tuple_b = (const dt_tag_t *)b;
592
593 return (tuple_b->count - tuple_a->count);
594}
595// sort_type 0 = path, 1 = leave other = count
596GList *dt_sort_tag(GList *tags, gint sort_type)
597{
598 GList *sorted_tags;
599 if (sort_type <= 1)
600 {
601 for(GList *taglist = tags; taglist; taglist = g_list_next(taglist))
602 {
603 // order such that sub tags are coming directly behind their parent
604 gchar *tag = ((dt_tag_t *)taglist->data)->tag;
605 for(char *letter = tag; *letter; letter++)
606 if(*letter == '|') *letter = '\1';
607 }
608 sorted_tags = g_list_sort(tags, !sort_type ? sort_tag_by_path : sort_tag_by_leave);
609 for(GList *taglist = sorted_tags; taglist; taglist = g_list_next(taglist))
610 {
611 gchar *tag = ((dt_tag_t *)taglist->data)->tag;
612 for(char *letter = tag; *letter; letter++)
613 if(*letter == '\1') *letter = '|';
614 }
615 }
616 else
617 {
618 sorted_tags = g_list_sort(tags, sort_tag_by_count);
619 }
620 return sorted_tags;
621}
622
623GList *dt_tag_get_list(int32_t imgid)
624{
625 GList *taglist = NULL;
626 GList *tags = NULL;
627
628 gboolean omit_tag_hierarchy = dt_conf_get_bool("omit_tag_hierarchy");
629
630 uint32_t count = dt_tag_get_attached(imgid, &taglist, TRUE);
631
632 if(count < 1) return NULL;
633
634 for(GList *tag_iter = taglist; tag_iter; tag_iter = g_list_next(tag_iter))
635 {
636 dt_tag_t *t = (dt_tag_t *)tag_iter->data;
637 gchar *value = t->tag;
638
639 gchar **pch = g_strsplit(value, "|", -1);
640
641 if(!IS_NULL_PTR(pch))
642 {
643 if(omit_tag_hierarchy)
644 {
645 char **iter = pch;
646 for(; *iter && *(iter + 1); iter++);
647 if(*iter) tags = g_list_prepend(tags, g_strdup(*iter));
648 }
649 else
650 {
651 size_t j = 0;
652 while(!IS_NULL_PTR(pch[j]))
653 {
654 tags = g_list_prepend(tags, g_strdup(pch[j]));
655 j++;
656 }
657 }
658 g_strfreev(pch);
659 }
660 }
661
662 dt_tag_free_result(&taglist);
663
664 return dt_util_glist_uniq(tags);
665}
666
667GList *dt_tag_get_hierarchical(int32_t imgid)
668{
669 GList *taglist = NULL;
670 GList *tags = NULL;
671
672 int count = dt_tag_get_attached(imgid, &taglist, TRUE);
673
674 if(count < 1) return NULL;
675
676 for(GList *tag_iter = taglist; tag_iter; tag_iter = g_list_next(tag_iter))
677 {
678 dt_tag_t *t = (dt_tag_t *)tag_iter->data;
679 tags = g_list_prepend(tags, g_strdup(t->tag));
680 }
681
682 dt_tag_free_result(&taglist);
683
684 tags = g_list_reverse(tags); // list was built in reverse order, so un-reverse it
685 return tags;
686}
687
688static GList *_tag_get_tags(const int32_t imgid, const dt_tag_type_t type)
689{
690 char *images = NULL;
691 if(imgid > 0)
692 images = g_strdup_printf("%d", imgid);
693 else
694 {
695 // we get the query used to retrieve the list of select images
697 }
698
702 GList *tags = dt_tag_repository_get_ids_for_images(images, kind);
703 dt_free(images);
704 return tags;
705}
706
707GList *dt_tag_get_tags(const int32_t imgid, const gboolean ignore_dt_tags)
708{
709 return _tag_get_tags(imgid, ignore_dt_tags ? DT_TAG_TYPE_USER : DT_TAG_TYPE_ALL);
710}
711
712static gint _is_not_exportable_tag(gconstpointer a, gconstpointer b)
713{
714 dt_tag_t *ta = (dt_tag_t *)a;
715 dt_tag_t *tb = (dt_tag_t *)b;
716 return ((g_strcmp0(ta->tag, tb->tag) == 0) &&
717 ((ta->flags) & (DT_TF_CATEGORY | DT_TF_PRIVATE))) ? 0 : -1;
718}
719
720GList *dt_tag_get_list_export(int32_t imgid, int32_t flags)
721{
722 GList *taglist = NULL;
723 GList *tags = NULL;
724
725 gboolean omit_tag_hierarchy = flags & DT_META_OMIT_HIERARCHY;
726 gboolean export_private_tags = flags & DT_META_PRIVATE_TAG;
727 gboolean export_tag_synonyms = flags & DT_META_SYNONYMS_TAG;
728
729 uint32_t count = _tag_get_attached_export(imgid, &taglist);
730
731 if(count < 1) return NULL;
732 GList *sorted_tags = dt_sort_tag(taglist, 0);
733 sorted_tags = g_list_reverse(sorted_tags);
734
735 // reset private if export private
736 if(export_private_tags)
737 {
738 for(GList *tagt = sorted_tags; tagt; tagt = g_list_next(tagt))
739 {
740 dt_tag_t *t = (dt_tag_t *)tagt->data;
741 t->flags &= ~DT_TF_PRIVATE;
742 }
743 }
744 for(GList *sorted_iter = sorted_tags; sorted_iter; sorted_iter = g_list_next(sorted_iter))
745 {
746 dt_tag_t *t = (dt_tag_t *)sorted_iter->data;
747 if ((export_private_tags || !(t->flags & DT_TF_PRIVATE))
748 && !(t->flags & DT_TF_CATEGORY))
749 {
750 gchar *tagname = t->leave;
751 tags = g_list_prepend(tags, g_strdup(tagname));
752
753 // if not "omit tag hierarchy" the path elements are added
754 // unless otherwise stated (defined as category or private)
755 if(!omit_tag_hierarchy)
756 {
757 GList *next = g_list_next(sorted_iter);
758 gchar *end = g_strrstr(t->tag, "|");
759 while (end)
760 {
761 end[0] = '\0';
762 end = g_strrstr(t->tag, "|");
763 if (IS_NULL_PTR(next) ||
764 !g_list_find_custom(next, t, (GCompareFunc)_is_not_exportable_tag))
765 {
766 const gchar *tag = end ? end + 1 : t->tag;
767 tags = g_list_prepend(tags, g_strdup(tag));
768 }
769 }
770 }
771
772 // add synonyms as necessary
773 if (export_tag_synonyms)
774 {
775 gchar *synonyms = t->synonym;
776 if (synonyms && synonyms[0])
777 {
778 gchar **tokens = g_strsplit(synonyms, ",", 0);
779 if(tokens)
780 {
781 gchar **entry = tokens;
782 while(*entry)
783 {
784 char *e = *entry;
785 if (*e == ' ') e++;
786 tags = g_list_append(tags, g_strdup(e));
787 entry++;
788 }
789 }
790 g_strfreev(tokens);
791 }
792 }
793 }
794 }
795 dt_tag_free_result(&sorted_tags);
796
797 return dt_util_glist_uniq(tags);
798}
799
800GList *dt_tag_get_hierarchical_export(int32_t imgid, int32_t flags)
801{
802 GList *taglist = NULL;
803 GList *tags = NULL;
804
805 const int count = dt_tag_get_attached(imgid, &taglist, TRUE);
806
807 if(count < 1) return NULL;
808 const gboolean export_private_tags = flags & DT_META_PRIVATE_TAG;
809
810 for(GList *tag_iter = taglist; tag_iter; tag_iter = g_list_next(tag_iter))
811 {
812 dt_tag_t *t = (dt_tag_t *)tag_iter->data;
813 if (export_private_tags || !(t->flags & DT_TF_PRIVATE))
814 {
815 tags = g_list_prepend(tags, g_strdup(t->tag));
816 }
817 }
818
819 dt_tag_free_result(&taglist);
820
821 return g_list_reverse(tags); // list was built in reverse order, so un-reverse it
822}
823
824gboolean dt_is_tag_attached(const guint tagid, const int32_t imgid)
825{
826 return dt_tag_repository_is_attached(tagid, imgid);
827}
828
829GList *dt_tag_get_images(const gint tagid)
830{
831 return dt_tag_repository_get_images(tagid); // in row order, as before
832}
833
834GList *dt_tag_get_images_from_list(const GList *img, const gint tagid)
835{
836 char *images = NULL;
837 for(GList *imgs = (GList *)img; imgs; imgs = g_list_next(imgs))
838 {
839 images = dt_util_dstrcat(images, "%d,",GPOINTER_TO_INT(imgs->data));
840 }
841
842 GList *result = NULL;
843 if(images)
844 {
845 images[strlen(images) - 1] = '\0';
846 result = dt_tag_repository_get_images_in_list(tagid, images);
847 dt_free(images);
848 }
849 return result; // the repository returns them in row order
850}
851
852uint32_t dt_tag_get_suggestions(GList **result)
853{
854 const uint32_t nb_selected = dt_selection_get_length(dt_selection_get_global());
855 const int nb_recent = dt_conf_get_int("plugins/lighttable/tagging/nb_recent_tags");
856 const uint32_t confidence = dt_conf_get_int("plugins/lighttable/tagging/confidence");
857 const char *slist = dt_conf_get_string_const("plugins/lighttable/tagging/recent_tags");
858
859 GList *tags = dt_tag_repository_get_suggestions(nb_selected, confidence, slist, nb_recent);
860
861 uint32_t count = 0;
862 for(GList *l = tags; l; l = g_list_next(l))
863 {
864 dt_tag_t *t = (dt_tag_t *)l->data;
865 t->leave = g_strrstr(t->tag, "|");
866 t->leave = t->leave ? t->leave + 1 : t->tag;
867 *result = g_list_append(*result, t);
868 count++;
869 }
870 g_list_free(tags); // the elements moved into *result
871
872 return count;
873}
874
875void dt_tag_count_tags_images(const gchar *keyword, int *tag_count, int *img_count)
876{
877 dt_tag_repository_count_similar(keyword, tag_count, img_count);
878}
879
880void dt_tag_get_tags_images(const gchar *keyword, GList **tag_list, GList **img_list)
881{
882 dt_tag_repository_get_similar(keyword, tag_list, img_list);
883}
884
885uint32_t dt_tag_images_count(gint tagid)
886{
888}
889
890uint32_t dt_tag_get_with_usage(GList **result)
891{
892 const uint32_t nb_selected = dt_selection_get_length(dt_selection_get_global());
893 GList *tags = dt_tag_repository_get_with_usage(nb_selected);
894
895 /* ... and create the result list to send upwards */
896 uint32_t count = 0;
897 for(GList *l = tags; l; l = g_list_next(l))
898 {
899 dt_tag_t *t = (dt_tag_t *)l->data;
900 t->leave = g_strrstr(t->tag, "|");
901 t->leave = t->leave ? t->leave + 1 : t->tag;
902 *result = g_list_append(*result, t);
903 count++;
904 }
905 g_list_free(tags); // the elements moved into *result
906
907 return count;
908}
909
910uint32_t dt_tag_get_collection_tags(GList **result)
911{
913
914 uint32_t count = 0;
915 for(GList *l = tags; l; l = g_list_next(l))
916 {
917 dt_tag_t *t = (dt_tag_t *)l->data;
918 t->leave = g_strrstr(t->tag, "|");
919 t->leave = t->leave ? t->leave + 1 : t->tag;
920 *result = g_list_append(*result, t);
921 count++;
922 }
923 g_list_free(tags); // the elements moved into *result
924
925 return count;
926}
927
928static gchar *dt_cleanup_synonyms(gchar *synonyms_entry)
929{
930 gchar *synonyms = NULL;
931 for(char *letter = synonyms_entry; *letter; letter++)
932 {
933 if(*letter == ';' || *letter == '\n') *letter = ',';
934 if(*letter == '\r') *letter = ' ';
935 }
936 gchar **tokens = g_strsplit(synonyms_entry, ",", 0);
937 if(tokens)
938 {
939 gchar **entry = tokens;
940 while (*entry)
941 {
942 char *e = g_strstrip(*entry);
943 if(*e)
944 {
945 synonyms = dt_util_dstrcat(synonyms, "%s, ", e);
946 }
947 entry++;
948 }
949 if (synonyms)
950 synonyms[strlen(synonyms) - 2] = '\0';
951 }
952 g_strfreev(tokens);
953 return synonyms;
954}
955
956gchar *dt_tag_get_synonyms(gint tagid)
957{
958 return dt_tag_repository_get_synonyms(tagid);
959}
960
961void dt_tag_set_synonyms(gint tagid, gchar *synonyms_entry)
962{
963 if (!synonyms_entry) return;
964 char *synonyms = dt_cleanup_synonyms(synonyms_entry);
965
966 dt_tag_repository_set_synonyms(tagid, synonyms);
967 dt_free(synonyms);
968}
969
970gint dt_tag_get_flags(gint tagid)
971{
972 return dt_tag_repository_get_flags(tagid);
973}
974
975void dt_tag_set_flags(gint tagid, gint flags)
976{
978}
979
980void dt_tag_add_synonym(gint tagid, gchar *synonym)
981{
982 char *synonyms = dt_tag_get_synonyms(tagid);
983 if (synonyms)
984 {
985 synonyms = dt_util_dstrcat(synonyms, ", %s", synonym);
986 }
987 else
988 {
989 synonyms = g_strdup(synonym);
990 }
991 dt_tag_repository_set_synonyms(tagid, synonyms);
992 dt_free(synonyms);
993}
994
995static void _free_result_item(gpointer data)
996{
997 dt_tag_t *t = (dt_tag_t*)data;
998 dt_free(t->tag);
999 dt_free(t->synonym);
1000 dt_free(t);
1001}
1002
1003void dt_tag_free_result(GList **result)
1004{
1005 if(result && *result)
1006 {
1007 g_list_free_full(*result, _free_result_item);
1008 *result = NULL;
1009 }
1010}
1011
1012uint32_t dt_tag_get_recent_used(GList **result)
1013{
1014 return 0;
1015}
1016
1017/*
1018 TODO
1019 the file format allows to specify {synonyms} that are one hierarchy level deeper than the parent. those are not
1020 to be shown in the gui but can be searched. when the parent or a synonym is attached then ALSO the rest of the
1021 bunch is to be added.
1022 there is also a ~ prefix for tags that indicate that the tag order has to be kept instead of sorting them. that's
1023 also not possible at the moment.
1024*/
1025uint32_t dt_tag_import(const char *filename)
1026{
1027 FILE *fd = g_fopen(filename, "r");
1028 if(IS_NULL_PTR(fd)) return -1;
1029
1030 GList * hierarchy = NULL;
1031 char *line = NULL;
1032 size_t len = 0;
1033 uint32_t count = 0;
1034 guint tagid = 0;
1035 guint previous_category_depth = 0;
1036 gboolean previous_category = FALSE;
1037 gboolean previous_synonym = FALSE;
1038
1039 while(getline(&line, &len, fd) != -1)
1040 {
1041 // remove newlines and set start past the initial tabs
1042 char *start = line;
1043 while(*start == '\t' || *start == ' ' || *start == ',' || *start == ';') start++;
1044 const int depth = start - line;
1045
1046 char *end = line + strlen(line) - 1;
1047 while((*end == '\n' || *end == '\r' || *end == ',' || *end == ';') && end >= start)
1048 {
1049 *end = '\0';
1050 end--;
1051 }
1052
1053 // remove control characters from the string
1054 // if no associated synonym the previous category node can be reused
1055 gboolean skip = FALSE;
1056 gboolean category = FALSE;
1057 gboolean synonym = FALSE;
1058 if (*start == '[' && *end == ']') // categories
1059 {
1060 category = TRUE;
1061 start++;
1062 *end-- = '\0';
1063 }
1064 else if (*start == '{' && *end == '}') // synonyms
1065 {
1066 synonym = TRUE;
1067 start++;
1068 *end-- = '\0';
1069 }
1070 if(*start == '~') // fixed order. TODO not possible with our db
1071 {
1072 skip = TRUE;
1073 start++;
1074 }
1075
1076 if (synonym)
1077 {
1078 // associate the synonym to last tag
1079 if (tagid)
1080 {
1081 char *tagname = g_strdup(start);
1082 // clear synonyms before importing the new ones => allows export, modification and back import
1083 if (!previous_synonym) dt_tag_set_synonyms(tagid, "");
1084 dt_tag_add_synonym(tagid, tagname);
1085 dt_free(tagname);
1086 }
1087 }
1088 else
1089 {
1090 // remove everything past the current prefix from hierarchy
1091 GList *iter = g_list_nth(hierarchy, depth);
1092 while(iter)
1093 {
1094 GList *current = iter;
1095 iter = g_list_next(iter);
1096 hierarchy = g_list_delete_link(hierarchy, current);
1097 }
1098
1099 // add the current level
1100 hierarchy = g_list_append(hierarchy, g_strdup(start));
1101
1102 // add tag to db iff it's not something to be ignored
1103 if(!skip)
1104 {
1105 char *tag = dt_util_glist_to_str("|", hierarchy);
1106 if (previous_category && (depth > previous_category_depth + 1))
1107 {
1108 // reuse previous tag
1109 dt_tag_rename(tagid, tag);
1110 if (!category)
1111 dt_tag_set_flags(tagid, 0);
1112 }
1113 else
1114 {
1115 // create a new tag
1116 count++;
1117 tagid = 1; // if 0, dt_tag_new creates a new one even if the tag already exists
1118 dt_tag_new(tag, &tagid);
1119 if (category)
1121 }
1122 dt_free(tag);
1123 }
1124 }
1125 previous_category_depth = category ? depth : 0;
1126 previous_category = category;
1127 previous_synonym = synonym;
1128 }
1129
1130 dt_free(line);
1131 g_list_free_full(hierarchy, dt_free_gpointer);
1132 hierarchy = NULL;
1133 fclose(fd);
1134
1136
1137 return count;
1138}
1139
1140/*
1141 TODO: there is one corner case where i am not sure if we are doing the correct thing. some examples i found
1142 on the internet agreed with this version, some used an alternative:
1143 consider two tags like "foo|bar" and "foo|bar|baz". the "foo|bar" part is both a regular tag (from the 1st tag)
1144 and also a category (from the 2nd tag). the two way to output are
1145
1146 [foo]
1147 bar
1148 baz
1149
1150 and
1151
1152 [foo]
1153 bar
1154 [bar]
1155 baz
1156
1157 we are using the first (mostly because it was easier to implement ;)). if this poses problems with other programs
1158 supporting these files then we should fix that.
1159*/
1160uint32_t dt_tag_export(const char *filename)
1161{
1162 FILE *fd = g_fopen(filename, "w");
1163
1164 if(IS_NULL_PTR(fd)) return -1;
1165
1166 GList *tags = NULL;
1167 gint count = 0;
1168 dt_tag_get_with_usage(&tags);
1169 GList *sorted_tags = dt_sort_tag(tags, 0);
1170
1171 gchar **hierarchy = NULL;
1172 for(GList *tag_elt = sorted_tags; tag_elt; tag_elt = g_list_next(tag_elt))
1173 {
1174 const gchar *tag = ((dt_tag_t *)tag_elt->data)->tag;
1175 const char *synonyms = ((dt_tag_t *)tag_elt->data)->synonym;
1176 const guint flags = ((dt_tag_t *)tag_elt->data)->flags;
1177 gchar **tokens = g_strsplit(tag, "|", -1);
1178
1179 // find how many common levels are shared with the last tag
1180 int common_start;
1181 for(common_start = 0; hierarchy && hierarchy[common_start] && tokens && tokens[common_start]; common_start++)
1182 {
1183 if(g_strcmp0(hierarchy[common_start], tokens[common_start])) break;
1184 }
1185
1186 g_strfreev(hierarchy);
1187 hierarchy = tokens;
1188
1189 int tabs = common_start;
1190 for(size_t i = common_start; tokens && tokens[i]; i++, tabs++)
1191 {
1192 for(int j = 0; j < tabs; j++) fputc('\t', fd);
1193 if(!tokens[i + 1])
1194 {
1195 count++;
1196 if (flags & DT_TF_CATEGORY)
1197 fprintf(fd, "[%s]\n", tokens[i]);
1198 else
1199 fprintf(fd, "%s\n", tokens[i]);
1200 if (synonyms && synonyms[0])
1201 {
1202 gchar **tokens2 = g_strsplit(synonyms, ",", 0);
1203 if(tokens2)
1204 {
1205 gchar **entry = tokens2;
1206 while(*entry)
1207 {
1208 char *e = *entry;
1209 if (*e == ' ') e++;
1210 for(int j = 0; j < tabs+1; j++) fputc('\t', fd);
1211 fprintf(fd, "{%s}\n", e);
1212 entry++;
1213 }
1214 }
1215 g_strfreev(tokens2);
1216 }
1217 }
1218 else
1219 fprintf(fd, "%s\n", tokens[i]);
1220 }
1221 }
1222
1223 g_strfreev(hierarchy);
1224
1225 dt_tag_free_result(&tags);
1226
1227 fclose(fd);
1228
1229 return count;
1230}
1231
1232char *dt_tag_get_subtags(const int32_t imgid, const char *category, const int level)
1233{
1234 if (IS_NULL_PTR(category)) return NULL;
1235 const guint rootnb = dt_util_string_count_char(category, '|');
1236 char *tags = NULL;
1237
1238 GList *names = dt_tag_repository_get_names_under(imgid, category);
1239 for(GList *l = names; l; l = g_list_next(l))
1240 {
1241 const char *tag = (const char *)l->data;
1242 const guint tagnb = dt_util_string_count_char(tag, '|');
1243 if (tagnb >= rootnb + level)
1244 {
1245 gchar **pch = g_strsplit(tag, "|", -1);
1246 char *subtag = pch[rootnb + level];
1247 gboolean valid = TRUE;
1248 // check we have not yet this subtag in the list
1249 if(tags && strlen(tags) >= strlen(subtag) + 1)
1250 {
1251 gchar *found = g_strstr_len(tags, strlen(tags), subtag);
1252 if(found && found[strlen(subtag)] == ',')
1253 valid = FALSE;
1254 }
1255 if(valid)
1256 tags = dt_util_dstrcat(tags, "%s,", subtag);
1257 g_strfreev(pch);
1258 }
1259 }
1260 g_list_free_full(names, g_free);
1261
1262 if(tags) tags[strlen(tags) - 1] = '\0'; // remove the last comma
1263 return tags;
1264}
1265
1266gboolean dt_tag_get_tag_order_by_id(const uint32_t tagid, uint32_t *sort,
1267 gboolean *descending)
1268{
1269 gboolean res = FALSE;
1270 if(IS_NULL_PTR(sort) || !descending) return res;
1271
1272 const uint32_t flags = dt_tag_repository_get_flags(tagid);
1274 {
1275 *sort = (flags & ~DT_TF_DESCENDING) >> 16;
1276 *descending = flags & DT_TF_DESCENDING;
1277 res = TRUE;
1278 }
1279 return res;
1280}
1281
1282uint32_t dt_tag_get_tag_id_by_name(const char * const name)
1283{
1284 if(IS_NULL_PTR(name)) return 0;
1286}
1287
1288void dt_tag_set_tag_order_by_id(const uint32_t tagid, const uint32_t sort,
1289 const gboolean descending)
1290{
1291 const uint32_t flags = sort << 16 | (descending ? DT_TF_DESCENDING : 0)
1294}
1295
1297{
1298 /* The four attached-tag statements this used to finalise belong to
1299 * database/tag_repository.c now, and are released with the rest of its cache. */
1301}
1302
1303// clang-format off
1304// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
1305// vim: shiftwidth=2 expandtab tabstop=2 cindent
1306// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
1307// clang-format on
GList * dt_act_on_get_images()
Definition act_on.c:38
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
static const dt_adaptation_t kind
const int t
int dt_conf_get_bool(const char *name)
int dt_conf_get_int(const char *name)
Integer for name, clamped to the bounds declared in the XML.
const char * dt_conf_get_string_const(const char *name)
Borrow the stored string for name without copying it.
GHashTable * names
GtkWidget* -> the name the application knows it by.
const int res
Definition dtpthread.h:351
ssize_t getline(char **lineptr, size_t *n, FILE *stream)
Definition getdelim.c:157
void dt_grouping_add_grouped_images(GList **images)
Definition grouping.c:148
#define UNKNOWN_IMAGE
Definition image.h:78
_lib_location_type_t type
Definition location.c:1
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition macros.h:96
static void dt_free_gpointer(gpointer ptr)
g_free() one pointer, with the signature GDestroyNotify wants.
Definition mem_alloc.h:184
#define dt_free(ptr)
g_free() ptr and set it to NULL, skipping both if it is already NULL.
Definition mem_alloc.h:171
dt_tag_actions_t
void dt_metadata_tags_changed(void)
Raise it. Internal to the module.
Everything this module says to the outside world: messages for the user, and the fact that the tag vo...
@ DT_META_OMIT_HIERARCHY
@ DT_META_PRIVATE_TAG
@ DT_META_SYNONYMS_TAG
dt_mipmap_buffer_dsc_flags flags
Definition mipmap_cache.c:4
const char * name
Definition pdf.h:90
struct dt_selection_t * dt_selection_get_global(void)
Definition selection.c:80
gchar * dt_selection_ids_to_string(struct dt_selection_t *selection)
Definition selection.c:377
int dt_selection_get_length(struct dt_selection_t *selection)
Definition selection.c:193
static const dt_aligned_pixel_simd_t value
Definition simd.h:144
gchar * tag
Definition tags.h:53
guint count
Definition tags.h:56
gchar * leave
Definition tags.h:54
gint flags
Definition tags.h:58
int32_t imgid
Definition tags.c:64
GList * before
Definition tags.c:65
GList * after
Definition tags.c:66
void dt_tag_repository_delete_batch(const char *id_list)
Delete every tag in id_list and its attachments.
void dt_tag_repository_rename(const guint tagid, const char *new_name)
Rename tagid. The caller checks first that the new name is free.
GList * dt_tag_repository_get_with_usage(const uint32_t nb_selected)
Every user tag with how often it is used and how much of the selection carries it.
GList * dt_tag_repository_get_collection_tags(void)
User tags attached to at least one image of the current collection, by name.
gchar * dt_tag_repository_get_name(const guint tagid)
The name of tagid, newly allocated, or NULL.
int dt_tag_repository_count_attachments(const guint tagid)
How many attachment ROWS tagid has, or -1 if the count could not be read. See dt_tag_repository_count...
GList * dt_tag_repository_get_images(const guint tagid)
Images carrying tagid, GINT_TO_POINTER. Free with g_list_free().
GList * dt_tag_repository_get_suggestions(const uint32_t nb_selected, const int confidence, const char *recent_tags, const int nb_recent)
Tags worth suggesting for the current selection.
GList * dt_tag_repository_get_ids_for_images(const char *imgid_list, const dt_tag_kind_t kind)
Ids of the tags on a set of images, optionally restricted by kind.
void dt_tag_repository_mark_internal(const guint tagid)
Record tagid as an internal tag.
void dt_tag_repository_get_similar(const char *keyword, GList **tags, GList **imgids)
The tags matching keyword and the images carrying any of them.
guint dt_tag_repository_find_by_name(const char *name)
The id of the tag named name, or 0 when there is none.
GList * dt_tag_repository_get_attached_for_export(const int32_t imgid)
Tags attached to imgid for export, plus every ancestor on their paths.
GList * dt_tag_repository_get_names_under(const int32_t imgid, const char *category)
Names of the tags on imgid that start with category.
guint dt_tag_repository_find_by_name_nocase(const char *name)
The id of the tag whose name matches name case-INSENSITIVELY, or 0.
gint dt_tag_repository_get_flags(const guint tagid)
The flags word of tagid, or 0.
void dt_tag_repository_update_flags(const guint tagid, const gint set, const gint keep_mask)
Set the bits in set and clear those absent from keep_mask: flags = (IFNULL(flags,0) & keep_mask) | se...
void dt_tag_repository_count_similar(const char *keyword, int *tag_count, int *img_count)
How many tags match keyword, and how many images carry any of them.
GList * dt_tag_repository_get_images_in_list(const guint tagid, const char *imgid_list)
Images carrying tagid, restricted to imgid_list – a comma-separated list of decimal image ids compose...
void dt_tag_repository_delete(const guint tagid)
Delete tagid, its attachments, and its memory.darktable_tags entry.
void dt_tag_repository_cleanup(void)
Finalise whatever this repository still caches – today, nothing. See dt_colorlabel_repository_cleanup...
void dt_tag_repository_attach_batch(const char *values)
Attach rows given as the VALUES clause of the insert – "(imgid,tagid,pos),…". The position expression...
gchar * dt_tag_repository_get_synonyms(const guint tagid)
The synonyms of tagid, newly allocated, or NULL.
void dt_tag_repository_set_synonyms(const guint tagid, const char *synonyms)
Replace the synonyms of tagid.
void dt_tag_repository_rebuild_internal(void)
Rebuild the whole internal-tag cache from data.tags.
uint32_t dt_tag_repository_count_distinct_images(const guint tagid)
Distinct images carrying tagid.
void dt_tag_repository_set_flags(const guint tagid, const gint flags)
Replace the flags word of tagid.
gboolean dt_tag_repository_is_attached(const guint tagid, const int32_t imgid)
TRUE when tagid is attached to imgid.
void dt_tag_repository_detach_batch(const int32_t imgid, const char *tagid_list)
Detach every tag in tagid_list from imgid.
GList * dt_tag_repository_get_attached(const int32_t imgid, const gboolean ignore_internal)
Tags attached to one image or to the current selection, ordered by name.
guint dt_tag_repository_insert(const char *name)
Insert a tag named name and return its new id, or 0 on failure.
data.tags and main.tagged_images.
dt_tag_kind_t
@ DT_TAG_KIND_INTERNAL
@ DT_TAG_KIND_USER
@ DT_TAG_KIND_ANY
GList * dt_tag_get_images_from_list(const GList *img, const gint tagid)
Definition tags.c:834
GList * dt_tag_get_images(const gint tagid)
Definition tags.c:829
static gchar * dt_cleanup_synonyms(gchar *synonyms_entry)
Definition tags.c:928
GList * dt_tag_get_list_export(int32_t imgid, int32_t flags)
Definition tags.c:720
void dt_tag_get_tags_images(const gchar *keyword, GList **tag_list, GList **img_list)
Definition tags.c:880
static void _bulk_add_tags(const gchar *tag_list)
Definition tags.c:112
guint dt_tag_remove_list(GList *tag_list)
Definition tags.c:211
gboolean dt_tag_attach(const guint tagid, const int32_t imgid, const gboolean undo_on, const gboolean group_on)
Definition tags.c:377
gboolean dt_tag_set_tags(const GList *tags, const GList *img, const gboolean ignore_dt_tags, const gboolean clear_on, const gboolean undo_on)
Definition tags.c:398
uint32_t dt_tag_get_suggestions(GList **result)
Definition tags.c:852
static void _pop_undo(gpointer user_data, dt_undo_type_t type, dt_undo_data_t data, dt_undo_action_t action, GList **imgs)
Definition tags.c:129
static gint sort_tag_by_count(gconstpointer a, gconstpointer b)
Definition tags.c:588
char * dt_tag_get_subtags(const int32_t imgid, const char *category, const int level)
Definition tags.c:1232
uint32_t dt_tag_get_attached(const int32_t imgid, GList **result, const gboolean ignore_dt_tags)
Definition tags.c:515
static gint sort_tag_by_path(gconstpointer a, gconstpointer b)
Definition tags.c:572
gboolean dt_tag_attach_images(const guint tagid, const GList *img, const gboolean undo_on)
Definition tags.c:355
void dt_tag_add_synonym(gint tagid, gchar *synonym)
Definition tags.c:980
void dt_set_darktable_tags()
Definition tags.c:510
gchar * dt_tag_get_synonyms(gint tagid)
Definition tags.c:956
static void _free_result_item(gpointer data)
Definition tags.c:995
gboolean dt_tag_detach(const guint tagid, const int32_t imgid, const gboolean undo_on, const gboolean group_on)
Definition tags.c:485
gint dt_tag_get_flags(gint tagid)
Definition tags.c:970
static gboolean _tag_add_tags_to_list(GList **list, const GList *tags)
Definition tags.c:269
static GList * _tag_get_tags(const int32_t imgid, const dt_tag_type_t type)
Definition tags.c:688
gboolean dt_tag_attach_string_list(const gchar *tags, const GList *img, const gboolean undo_on)
Definition tags.c:418
uint32_t dt_tag_images_count(gint tagid)
Definition tags.c:885
static gint _is_not_exportable_tag(gconstpointer a, gconstpointer b)
Definition tags.c:712
gboolean dt_tag_detach_by_string(const char *name, const int32_t imgid, const gboolean undo_on, const gboolean group_on)
Definition tags.c:500
gboolean dt_is_tag_attached(const guint tagid, const int32_t imgid)
Definition tags.c:824
static gchar * _get_tb_added_tag_string_values(const int img, GList *before, GList *after)
Definition tags.c:84
gboolean dt_tag_new(const char *name, guint *tagid)
Definition tags.c:164
void dt_tag_set_tag_order_by_id(const uint32_t tagid, const uint32_t sort, const gboolean descending)
Definition tags.c:1288
uint32_t dt_tag_get_collection_tags(GList **result)
Definition tags.c:910
gboolean dt_tag_get_tag_order_by_id(const uint32_t tagid, uint32_t *sort, gboolean *descending)
Definition tags.c:1266
static gboolean _tag_remove_tags_from_list(GList **list, const GList *tags)
Definition tags.c:283
void dt_tag_rename(const guint tagid, const gchar *new_tagname)
Definition tags.c:247
gchar * dt_tag_get_name(const guint tagid)
Definition tags.c:242
dt_tag_actions_t
Definition tags.c:301
@ DT_TA_ATTACH
Definition tags.c:302
@ DT_TA_SET
Definition tags.c:304
@ DT_TA_SET_ALL
Definition tags.c:305
@ DT_TA_DETACH
Definition tags.c:303
void dt_tags_cleanup(void)
Definition tags.c:1296
GList * dt_tag_get_list(int32_t imgid)
Definition tags.c:623
uint32_t dt_tag_import(const char *filename)
Definition tags.c:1025
GList * dt_tag_get_hierarchical_export(int32_t imgid, int32_t flags)
Definition tags.c:800
static gint sort_tag_by_leave(gconstpointer a, gconstpointer b)
Definition tags.c:580
void dt_tag_set_flags(gint tagid, gint flags)
Definition tags.c:975
guint dt_tag_remove(const guint tagid, gboolean final)
Definition tags.c:195
static void _tags_undo_data_free(gpointer data)
Definition tags.c:157
void dt_tag_delete_tag_batch(const char *flatlist)
Definition tags.c:205
gboolean dt_tag_detach_images(const guint tagid, const GList *img, const gboolean undo_on)
Definition tags.c:462
GList * dt_tag_get_hierarchical(int32_t imgid)
Definition tags.c:667
static gboolean _tag_execute(const GList *tags, const GList *imgs, GList **undo, const gboolean undo_on, const gint action)
Definition tags.c:310
GList * dt_tag_get_tags(const int32_t imgid, const gboolean ignore_dt_tags)
Definition tags.c:707
void dt_tag_free_result(GList **result)
Definition tags.c:1003
GList * dt_sort_tag(GList *tags, gint sort_type)
Definition tags.c:596
gboolean dt_tag_exists(const char *name, guint *tagid)
Definition tags.c:255
void dt_tag_set_synonyms(gint tagid, gchar *synonyms_entry)
Definition tags.c:961
static void _undo_tags_free(gpointer data)
Definition tags.c:147
static gchar * _get_tb_removed_tag_string_values(GList *before, GList *after)
Definition tags.c:69
uint32_t dt_tag_export(const char *filename)
Definition tags.c:1160
uint32_t dt_tag_get_tag_id_by_name(const char *const name)
Definition tags.c:1282
dt_tag_type_t
Definition tags.c:294
@ DT_TAG_TYPE_DT
Definition tags.c:295
@ DT_TAG_TYPE_USER
Definition tags.c:296
@ DT_TAG_TYPE_ALL
Definition tags.c:297
gboolean dt_tag_new_from_gui(const char *name, guint *tagid)
Definition tags.c:187
uint32_t dt_tag_get_recent_used(GList **result)
Definition tags.c:1012
static void _bulk_remove_tags(const int img, const gchar *tag_list)
Definition tags.c:107
void dt_tag_count_tags_images(const gchar *keyword, int *tag_count, int *img_count)
Definition tags.c:875
static void _pop_undo_execute(const int32_t imgid, GList *before, GList *after)
Definition tags.c:117
uint32_t dt_tag_get_with_usage(GList **result)
Definition tags.c:890
static uint32_t _tag_get_attached_export(const int32_t imgid, GList **result)
Definition tags.c:551
@ DT_TS_ALL_IMAGES
Definition tags.h:76
@ DT_TS_NO_IMAGE
Definition tags.h:74
@ DT_TS_SOME_IMAGES
Definition tags.h:75
@ DT_TF_ORDER_SET
Definition tags.h:66
@ DT_TF_DESCENDING
Definition tags.h:67
@ DT_TF_CATEGORY
Definition tags.h:64
@ DT_TF_PRIVATE
Definition tags.h:65
#define DT_TF_ALL
Definition tags.h:70
void dt_undo_end_group(dt_undo_t *self)
Definition undo.c:149
void dt_undo_start_group(dt_undo_t *self, dt_undo_type_t type)
Definition undo.c:134
void dt_undo_record(dt_undo_t *self, gpointer user_data, dt_undo_type_t type, dt_undo_data_t data, void(*undo)(gpointer user_data, dt_undo_type_t type, dt_undo_data_t item, dt_undo_action_t action, GList **imgs), void(*free_data)(gpointer data))
Definition undo.c:163
dt_undo_type_t
Definition undo.h:40
@ DT_UNDO_TAGS
Definition undo.h:47
dt_undo_t * dt_undo_get_global(void)
Definition darktable.c:611
void * dt_undo_data_t
Definition undo.h:70
dt_undo_action_t
Definition undo.h:65
@ DT_ACTION_UNDO
Definition undo.h:66
guint dt_util_string_count_char(const char *text, const char needle)
Definition utility.c:814
gchar * dt_util_glist_to_str(const gchar *separator, GList *items)
Definition utility.c:170
gchar * dt_util_dstrcat(gchar *str, const gchar *format,...)
Definition utility.c:99
GList * dt_util_glist_uniq(GList *items)
Definition utility.c:197