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 "common/tags.h"
43#include "common/collection.h"
44#include "common/darktable.h"
45#include "common/debug.h"
46#include "common/grouping.h"
47#include "common/selection.h"
48#include "common/undo.h"
49#include "control/conf.h"
50#include "control/control.h"
51#include <glib.h>
52#if defined (_WIN32)
53#include "win/getdelim.h"
54#endif // defined (_WIN32)
55
56static sqlite3_stmt *_tag_get_attached_single_stmt = NULL;
57static sqlite3_stmt *_tag_get_attached_single_ignore_stmt = NULL;
58static sqlite3_stmt *_tag_get_attached_selected_stmt = NULL;
59static sqlite3_stmt *_tag_get_attached_selected_ignore_stmt = NULL;
60
61typedef struct dt_undo_tags_t
62{
63 int32_t imgid;
64 GList *before; // list of tagid before
65 GList *after; // list of tagid after
67
68static gchar *_get_tb_removed_tag_string_values(GList *before, GList *after)
69{
70 GList *a = after;
71 gchar *tag_list = NULL;
72 for(GList *b = before; b; b = g_list_next(b))
73 {
74 if(!g_list_find(a, b->data))
75 {
76 tag_list = dt_util_dstrcat(tag_list, "%d,", GPOINTER_TO_INT(b->data));
77 }
78 }
79 if(tag_list) tag_list[strlen(tag_list) - 1] = '\0';
80 return tag_list;
81}
82
83static gchar *_get_tb_added_tag_string_values(const int img, GList *before, GList *after)
84{
85 GList *b = before;
86 gchar *tag_list = NULL;
87 for(GList *a = after; a; a = g_list_next(a))
88 {
89 if(!g_list_find(b, a->data))
90 {
91 // clang-format off
92 tag_list = dt_util_dstrcat(tag_list,
93 "(%d,%d,"
94 " (SELECT (IFNULL(MAX(position),0) & 0xFFFFFFFF00000000) + (1 << 32)"
95 " FROM main.tagged_images)"
96 "),",
97 GPOINTER_TO_INT(img),
98 GPOINTER_TO_INT(a->data));
99 // clang-format on
100 }
101 }
102 if(tag_list) tag_list[strlen(tag_list) - 1] = '\0';
103 return tag_list;
104}
105
106static void _bulk_remove_tags(const int img, const gchar *tag_list)
107{
108 if(img > 0 && tag_list)
109 {
110 sqlite3_stmt *stmt;
111 gchar *query = g_strdup_printf("DELETE FROM main.tagged_images WHERE imgid = %d AND tagid IN (%s)", img, tag_list);
113 sqlite3_step(stmt);
114 sqlite3_finalize(stmt);
115 dt_free(query);
116 }
117}
118
119static void _bulk_add_tags(const gchar *tag_list)
120{
121 if(tag_list)
122 {
123 sqlite3_stmt *stmt;
124 gchar *query = g_strdup_printf("INSERT INTO main.tagged_images (imgid, tagid, position) VALUES %s", tag_list);
126 sqlite3_step(stmt);
127 sqlite3_finalize(stmt);
128 dt_free(query);
129 }
130}
131
132static void _pop_undo_execute(const int32_t imgid, GList *before, GList *after)
133{
134 gchar *tobe_removed_list = _get_tb_removed_tag_string_values(before, after);
135 gchar *tobe_added_list = _get_tb_added_tag_string_values(imgid, before, after);
136
137 _bulk_remove_tags(imgid, tobe_removed_list);
138 _bulk_add_tags(tobe_added_list);
139
140 dt_free(tobe_removed_list);
141 dt_free(tobe_added_list);
142}
143
144static void _pop_undo(gpointer user_data, dt_undo_type_t type, dt_undo_data_t data, dt_undo_action_t action, GList **imgs)
145{
146 if(type == DT_UNDO_TAGS)
147 {
148 for(GList *list = (GList *)data; list; list = g_list_next(list))
149 {
150 dt_undo_tags_t *undotags = (dt_undo_tags_t *)list->data;
151
152 GList *before = (action == DT_ACTION_UNDO) ? undotags->after : undotags->before;
153 GList *after = (action == DT_ACTION_UNDO) ? undotags->before : undotags->after;
154 _pop_undo_execute(undotags->imgid, before, after);
155 *imgs = g_list_prepend(*imgs, GINT_TO_POINTER(undotags->imgid));
156 }
157
159 }
160}
161
162static void _undo_tags_free(gpointer data)
163{
164 dt_undo_tags_t *undotags = (dt_undo_tags_t *)data;
165 g_list_free(undotags->before);
166 undotags->before = NULL;
167 g_list_free(undotags->after);
168 undotags->after = NULL;
169 dt_free(undotags);
170}
171
172static void _tags_undo_data_free(gpointer data)
173{
174 GList *l = (GList *)data;
175 g_list_free_full(l, _undo_tags_free);
176 l = NULL;
177}
178
179gboolean dt_tag_new(const char *name, guint *tagid)
180{
181 int rt;
182 sqlite3_stmt *stmt;
183
184 if(IS_NULL_PTR(name) || name[0] == '\0') return FALSE; // no tagid name.
185
186 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), "SELECT id FROM data.tags WHERE name = ?1", -1, &stmt,
187 NULL);
188 DT_DEBUG_SQLITE3_BIND_TEXT(stmt, 1, name, -1, SQLITE_TRANSIENT);
189 rt = sqlite3_step(stmt);
190 if(rt == SQLITE_ROW)
191 {
192 // tagid already exists.
193 if(!IS_NULL_PTR(tagid)) *tagid = sqlite3_column_int64(stmt, 0);
194 sqlite3_finalize(stmt);
195 return TRUE;
196 }
197 sqlite3_finalize(stmt);
198
199 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), "INSERT INTO data.tags (id, name) VALUES (NULL, ?1)",
200 -1, &stmt, NULL);
201 DT_DEBUG_SQLITE3_BIND_TEXT(stmt, 1, name, -1, SQLITE_TRANSIENT);
202 sqlite3_step(stmt);
203 sqlite3_finalize(stmt);
204
205 guint id = 0;
206 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), "SELECT id FROM data.tags WHERE name = ?1", -1,
207 &stmt, NULL);
208 DT_DEBUG_SQLITE3_BIND_TEXT(stmt, 1, name, -1, SQLITE_TRANSIENT);
209 if(sqlite3_step(stmt) == SQLITE_ROW) id = sqlite3_column_int(stmt, 0);
210 sqlite3_finalize(stmt);
211
212 if(id && g_strstr_len(name, -1, "darktable|") == name)
213 {
215 "INSERT INTO memory.darktable_tags (tagid) VALUES (?1)",
216 -1, &stmt, NULL);
217 DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, id);
218 sqlite3_step(stmt);
219 sqlite3_finalize(stmt);
220 }
221
222 if(!IS_NULL_PTR(tagid))
223 *tagid = id;
224
225 return TRUE;
226}
227
228gboolean dt_tag_new_from_gui(const char *name, guint *tagid)
229{
230 const gboolean ret = dt_tag_new(name, tagid);
231 /* if everything went fine, raise signal of tags change to refresh keywords module in GUI */
233 return ret;
234}
235
236guint dt_tag_remove(const guint tagid, gboolean final)
237{
238 int rv, count = -1;
239 sqlite3_stmt *stmt;
240
242 "SELECT COUNT(*) FROM main.tagged_images WHERE tagid=?1", -1, &stmt, NULL);
243 DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, tagid);
244 rv = sqlite3_step(stmt);
245 if(rv == SQLITE_ROW) count = sqlite3_column_int(stmt, 0);
246 sqlite3_finalize(stmt);
247
248 if(final == TRUE)
249 {
250 // let's actually remove the tag
251 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), "DELETE FROM data.tags WHERE id=?1",
252 -1, &stmt, NULL);
253 DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, tagid);
254 sqlite3_step(stmt);
255 sqlite3_finalize(stmt);
256
257 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), "DELETE FROM main.tagged_images WHERE tagid=?1",
258 -1, &stmt, NULL);
259 DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, tagid);
260 sqlite3_step(stmt);
261 sqlite3_finalize(stmt);
262
263 // remove it also form darktable tags table if it is there
264 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), "DELETE FROM memory.darktable_tags WHERE tagid=?1",
265 -1, &stmt, NULL);
266 DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, tagid);
267 sqlite3_step(stmt);
268 sqlite3_finalize(stmt);
269 }
270
271 return count;
272}
273
274void dt_tag_delete_tag_batch(const char *flatlist)
275{
276 sqlite3_stmt *stmt;
277
278 gchar *query = g_strdup_printf("DELETE FROM data.tags WHERE id IN (%s)", flatlist);
280 sqlite3_step(stmt);
281 sqlite3_finalize(stmt);
282 dt_free(query);
283
284 query = g_strdup_printf("DELETE FROM main.tagged_images WHERE tagid IN (%s)", flatlist);
286 sqlite3_step(stmt);
287 sqlite3_finalize(stmt);
288 dt_free(query);
289
290 // make sure the darktable tags table is up to date
292}
293
294guint dt_tag_remove_list(GList *tag_list)
295{
296 if (!tag_list) return 0;
297
298 char *flatlist = NULL;
299 guint count = 0;
300 guint tcount = 0;
301 for (GList *taglist = tag_list; taglist ; taglist = g_list_next(taglist))
302 {
303 const guint tagid = ((dt_tag_t *)taglist->data)->id;
304 flatlist = dt_util_dstrcat(flatlist, "%u,", tagid);
305 count++;
306 if(flatlist && count > 1000)
307 {
308 flatlist[strlen(flatlist)-1] = '\0';
309 dt_tag_delete_tag_batch(flatlist);
310 dt_free(flatlist);
311 tcount = tcount + count;
312 count = 0;
313 }
314 }
315 if(flatlist)
316 {
317 flatlist[strlen(flatlist)-1] = '\0';
318 dt_tag_delete_tag_batch(flatlist);
319 dt_free(flatlist);
320 tcount = tcount + count;
321 }
322 return tcount;
323}
324
325gchar *dt_tag_get_name(const guint tagid)
326{
327 int rt;
328 char *name = NULL;
329 sqlite3_stmt *stmt;
330 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), "SELECT name FROM data.tags WHERE id= ?1", -1, &stmt,
331 NULL);
332 DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, tagid);
333 rt = sqlite3_step(stmt);
334 if(rt == SQLITE_ROW) name = g_strdup((const char *)sqlite3_column_text(stmt, 0));
335 sqlite3_finalize(stmt);
336
337 return name;
338}
339
340void dt_tag_rename(const guint tagid, const gchar *new_tagname)
341{
342 sqlite3_stmt *stmt;
343
344 if(IS_NULL_PTR(new_tagname) || !new_tagname[0]) return;
345 if(dt_tag_exists(new_tagname, NULL)) return;
346
348 "UPDATE data.tags SET name = ?2 WHERE id = ?1", -1, &stmt, NULL);
349 DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, tagid);
350 DT_DEBUG_SQLITE3_BIND_TEXT(stmt, 2, new_tagname, -1, SQLITE_TRANSIENT);
351 sqlite3_step(stmt);
352 sqlite3_finalize(stmt);
353
354}
355
356gboolean dt_tag_exists(const char *name, guint *tagid)
357{
358 int rt;
359 sqlite3_stmt *stmt;
360 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), "SELECT id FROM data.tags WHERE name = ?1", -1, &stmt,
361 NULL);
362 DT_DEBUG_SQLITE3_BIND_TEXT(stmt, 1, name, -1, SQLITE_TRANSIENT);
363 rt = sqlite3_step(stmt);
364
365 if(rt == SQLITE_ROW)
366 {
367 if(!IS_NULL_PTR(tagid)) *tagid = sqlite3_column_int64(stmt, 0);
368 sqlite3_finalize(stmt);
369 return TRUE;
370 }
371
372 if(!IS_NULL_PTR(tagid)) *tagid = -1;
373 sqlite3_finalize(stmt);
374 return FALSE;
375}
376
377static gboolean _tag_add_tags_to_list(GList **list, const GList *tags)
378{
379 gboolean res = FALSE;
380 for(const GList *t = tags; t; t = g_list_next(t))
381 {
382 if(!g_list_find(*list, t->data))
383 {
384 *list = g_list_prepend(*list, t->data);
385 res = TRUE;
386 }
387 }
388 return res;
389}
390
391static gboolean _tag_remove_tags_from_list(GList **list, const GList *tags)
392{
393 const int nb_ini = g_list_length(*list);
394 for(const GList *t = tags; t; t = g_list_next(t))
395 {
396 *list = g_list_remove(*list, t->data);
397 }
398 return (g_list_length(*list) != nb_ini);
399}
400
407
415
416static GList *_tag_get_tags(const int32_t imgid, const dt_tag_type_t type);
417
418static gboolean _tag_execute(const GList *tags, const GList *imgs, GList **undo, const gboolean undo_on,
419 const gint action)
420{
421 gboolean res = FALSE;
422 for(const GList *images = imgs; images; images = g_list_next(images))
423 {
424 const int32_t image_id = GPOINTER_TO_INT(images->data);
425 dt_undo_tags_t *undotags = (dt_undo_tags_t *)malloc(sizeof(dt_undo_tags_t));
426 undotags->imgid = image_id;
427 undotags->before = _tag_get_tags(image_id, DT_TAG_TYPE_ALL);
428 switch(action)
429 {
430 case DT_TA_ATTACH:
431 undotags->after = g_list_copy(undotags->before);
432 if(_tag_add_tags_to_list(&undotags->after, tags)) res = TRUE;
433 break;
434 case DT_TA_DETACH:
435 undotags->after = g_list_copy(undotags->before);
436 if(_tag_remove_tags_from_list(&undotags->after, tags)) res = TRUE;
437 break;
438 case DT_TA_SET:
439 undotags->after = g_list_copy((GList *)tags);
440 // preserve dt tags
441 GList *dttags = _tag_get_tags(image_id, DT_TAG_TYPE_DT);
442 if(dttags) undotags->after = g_list_concat(undotags->after, dttags);
443 res = TRUE;
444 break;
445 case DT_TA_SET_ALL:
446 undotags->after = g_list_copy((GList *)tags);
447 res = TRUE;
448 break;
449 default:
450 undotags->after = g_list_copy(undotags->before);
451 res = FALSE;
452 break;
453 }
454 _pop_undo_execute(image_id, undotags->before, undotags->after);
455 if(undo_on)
456 *undo = g_list_append(*undo, undotags);
457 else
458 _undo_tags_free(undotags);
459 }
460 return res;
461}
462
463gboolean dt_tag_attach_images(const guint tagid, const GList *img, const gboolean undo_on)
464{
465 if(IS_NULL_PTR(img)) return FALSE;
466 GList *undo = NULL;
467 GList *tags = NULL;
468
469 tags = g_list_prepend(tags, GINT_TO_POINTER(tagid));
471
472 const gboolean res = _tag_execute(tags, img, &undo, undo_on, DT_TA_ATTACH);
473
474 g_list_free(tags);
475 tags = NULL;
476 if(undo_on)
477 {
480 }
481
482 return res;
483}
484
485gboolean dt_tag_attach(const guint tagid, const int32_t imgid, const gboolean undo_on, const gboolean group_on)
486{
487 gboolean res = FALSE;
488 if(imgid == UNKNOWN_IMAGE)
489 {
490 GList *imgs = dt_act_on_get_images();
491 res = dt_tag_attach_images(tagid, imgs, undo_on);
492 g_list_free(imgs);
493 imgs = NULL;
494 }
495 else
496 {
497 if(dt_is_tag_attached(tagid, imgid)) return FALSE;
498 GList *imgs = g_list_append(NULL, GINT_TO_POINTER(imgid));
499 res = dt_tag_attach_images(tagid, imgs, undo_on);
500 g_list_free(imgs);
501 imgs = NULL;
502 }
503 return res;
504}
505
506gboolean dt_tag_set_tags(const GList *tags, const GList *img, const gboolean ignore_dt_tags,
507 const gboolean clear_on, const gboolean undo_on)
508{
509 if(img)
510 {
511 GList *undo = NULL;
513
514 const gboolean res = _tag_execute(tags, img, &undo, undo_on,
515 clear_on ? ignore_dt_tags ? DT_TA_SET : DT_TA_SET_ALL : DT_TA_ATTACH);
516 if(undo_on)
517 {
520 }
521 return res;
522 }
523 return FALSE;
524}
525
526gboolean dt_tag_attach_string_list(const gchar *tags, const GList *img, const gboolean undo_on)
527{
528 // tags may not exist yet
529 // undo only undoes the tags attachments. it doesn't remove created tags.
530 gchar **tokens = g_strsplit(tags, ",", 0);
531 gboolean res = FALSE;
532 if(tokens)
533 {
534 // tag(s) creation
535 GList *tagl = NULL;
536 gchar **entry = tokens;
537 while(*entry)
538 {
539 char *e = g_strstrip(*entry);
540 if(*e)
541 {
542 guint tagid = 0;
543 dt_tag_new(e, &tagid);
544 tagl = g_list_prepend(tagl, GINT_TO_POINTER(tagid));
545 }
546 entry++;
547 }
548
549 // attach newly created tags
550 if(img)
551 {
552 GList *undo = NULL;
554
555 res = _tag_execute(tagl, img, &undo, undo_on, DT_TA_ATTACH);
556
557 if(undo_on)
558 {
561 }
562 }
563 g_list_free(tagl);
564 tagl = NULL;
565 }
566 g_strfreev(tokens);
567 return res;
568}
569
570gboolean dt_tag_detach_images(const guint tagid, const GList *img, const gboolean undo_on)
571{
572 if(img)
573 {
574 GList *tags = NULL;
575 tags = g_list_prepend(tags, GINT_TO_POINTER(tagid));
576 GList *undo = NULL;
578
579 const gboolean res = _tag_execute(tags, img, &undo, undo_on, DT_TA_DETACH);
580
581 g_list_free(tags);
582 tags = NULL;
583 if(undo_on)
584 {
587 }
588 return res;
589 }
590 return FALSE;
591}
592
593gboolean dt_tag_detach(const guint tagid, const int32_t imgid, const gboolean undo_on, const gboolean group_on)
594{
595 GList *imgs = NULL;
596 if(imgid == UNKNOWN_IMAGE)
597 imgs = dt_act_on_get_images();
598 else
599 imgs = g_list_prepend(imgs, GINT_TO_POINTER(imgid));
600 if(group_on) dt_grouping_add_grouped_images(&imgs);
601
602 const gboolean res = dt_tag_detach_images(tagid, imgs, undo_on);
603 g_list_free(imgs);
604 imgs = NULL;
605 return res;
606}
607
608gboolean dt_tag_detach_by_string(const char *name, const int32_t imgid, const gboolean undo_on,
609 const gboolean group_on)
610{
611 if(IS_NULL_PTR(name) || !name[0]) return FALSE;
612 guint tagid = 0;
613 if(!dt_tag_exists(name, &tagid)) return FALSE;
614
615 return dt_tag_detach(tagid, imgid, undo_on, group_on);
616}
617
619{
620 DT_DEBUG_SQLITE3_EXEC(dt_database_get(darktable.db), "DELETE FROM memory.darktable_tags", NULL, NULL, NULL);
621
622 sqlite3_stmt *stmt;
623 // clang-format off
625 "INSERT INTO memory.darktable_tags (tagid)"
626 " SELECT DISTINCT id"
627 " FROM data.tags"
628 " WHERE name LIKE 'darktable|%%'",
629 -1, &stmt, NULL);
630 // clang-format on
631 sqlite3_step(stmt);
632 sqlite3_finalize(stmt);
633}
634
635uint32_t dt_tag_get_attached(const int32_t imgid, GList **result, const gboolean ignore_dt_tags)
636{
637 sqlite3_stmt *stmt;
638 uint32_t nb_selected = 0;
639 if(imgid > 0)
640 {
641 nb_selected = 1;
642 }
643 else
644 {
646 }
647 uint32_t count = 0;
648 if(imgid > 0 || nb_selected > 0)
649 {
650 if(imgid > 0)
651 {
652 if(ignore_dt_tags)
653 {
655 {
656 // clang-format off
658 "SELECT DISTINCT I.tagid, T.name, T.flags, T.synonyms,"
659 " COUNT(DISTINCT I.imgid) AS inb"
660 " FROM main.tagged_images AS I"
661 " JOIN data.tags AS T ON T.id = I.tagid"
662 " WHERE I.imgid = ?1 AND T.id NOT IN memory.darktable_tags"
663 " GROUP BY I.tagid "
664 " ORDER by T.name",
666 // clang-format on
667 }
669 }
670 else
671 {
673 {
674 // clang-format off
676 "SELECT DISTINCT I.tagid, T.name, T.flags, T.synonyms,"
677 " COUNT(DISTINCT I.imgid) AS inb"
678 " FROM main.tagged_images AS I"
679 " JOIN data.tags AS T ON T.id = I.tagid"
680 " WHERE I.imgid = ?1"
681 " GROUP BY I.tagid "
682 " ORDER by T.name",
684 // clang-format on
685 }
687 }
688 sqlite3_reset(stmt);
689 sqlite3_clear_bindings(stmt);
690 DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, imgid);
691 }
692 else
693 {
694 if(ignore_dt_tags)
695 {
697 {
698 // clang-format off
700 "SELECT DISTINCT I.tagid, T.name, T.flags, T.synonyms,"
701 " COUNT(DISTINCT I.imgid) AS inb"
702 " FROM main.tagged_images AS I"
703 " JOIN data.tags AS T ON T.id = I.tagid"
704 " JOIN main.selected_images AS S ON S.imgid = I.imgid"
705 " WHERE T.id NOT IN memory.darktable_tags"
706 " GROUP BY I.tagid "
707 " ORDER by T.name",
709 // clang-format on
710 }
712 }
713 else
714 {
716 {
717 // clang-format off
719 "SELECT DISTINCT I.tagid, T.name, T.flags, T.synonyms,"
720 " COUNT(DISTINCT I.imgid) AS inb"
721 " FROM main.tagged_images AS I"
722 " JOIN data.tags AS T ON T.id = I.tagid"
723 " JOIN main.selected_images AS S ON S.imgid = I.imgid"
724 " GROUP BY I.tagid "
725 " ORDER by T.name",
727 // clang-format on
728 }
730 }
731 sqlite3_reset(stmt);
732 sqlite3_clear_bindings(stmt);
733 }
734
735 // Create result
736 *result = NULL;
737 while(sqlite3_step(stmt) == SQLITE_ROW)
738 {
739 dt_tag_t *t = g_malloc0(sizeof(dt_tag_t));
740 t->id = sqlite3_column_int(stmt, 0);
741 t->tag = g_strdup((char *)sqlite3_column_text(stmt, 1));
742 t->leave = g_strrstr(t->tag, "|");
743 t->leave = t->leave ? t->leave + 1 : t->tag;
744 t->flags = sqlite3_column_int(stmt, 2);
745 t->synonym = g_strdup((char *)sqlite3_column_text(stmt, 3));
746 const uint32_t imgnb = sqlite3_column_int(stmt, 4);
747 t->count = imgnb;
748 t->select = (nb_selected == 0) ? DT_TS_NO_IMAGE :
749 (imgnb == nb_selected) ? DT_TS_ALL_IMAGES :
750 (imgnb == 0) ? DT_TS_NO_IMAGE : DT_TS_SOME_IMAGES;
751 *result = g_list_append(*result, t);
752 count++;
753 }
754 }
755 return count;
756}
757
758static uint32_t _tag_get_attached_export(const int32_t imgid, GList **result)
759{
760 if(!(imgid > 0)) return 0;
761
762 sqlite3_stmt *stmt;
763 // clang-format off
765 "SELECT DISTINCT T.id, T.name, T.flags, T.synonyms"
766 " FROM data.tags AS T"
767 // tags attached to image(s), not dt tag, ordered by name
768 " JOIN (SELECT DISTINCT I.tagid, T.name"
769 " FROM main.tagged_images AS I"
770 " JOIN data.tags AS T ON T.id = I.tagid"
771 " WHERE I.imgid = ?1 AND T.id NOT IN memory.darktable_tags"
772 " ORDER by T.name) AS T1"
773 // keep also tags in the path to be able to check category in path
774 " ON T.id = T1.tagid"
775 " OR (T.name = SUBSTR(T1.name, 1, LENGTH(T.name))"
776 " AND SUBSTR(T1.name, LENGTH(T.name) + 1, 1) = '|')",
777 -1, &stmt, NULL);
778 // clang-format on
779 DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, imgid);
780 // Create result
781 uint32_t count = 0;
782 while(sqlite3_step(stmt) == SQLITE_ROW)
783 {
784 dt_tag_t *t = g_malloc0(sizeof(dt_tag_t));
785 t->id = sqlite3_column_int(stmt, 0);
786 t->tag = g_strdup((char *)sqlite3_column_text(stmt, 1));
787 t->leave = g_strrstr(t->tag, "|");
788 t->leave = t->leave ? t->leave + 1 : t->tag;
789 t->flags = sqlite3_column_int(stmt, 2);
790 t->synonym = g_strdup((char *)sqlite3_column_text(stmt, 3));
791 *result = g_list_append(*result, t);
792 count++;
793 }
794 sqlite3_finalize(stmt);
795
796 return count;
797}
798
799static gint sort_tag_by_path(gconstpointer a, gconstpointer b)
800{
801 const dt_tag_t *tuple_a = (const dt_tag_t *)a;
802 const dt_tag_t *tuple_b = (const dt_tag_t *)b;
803
804 return g_strcmp0(tuple_a->tag, tuple_b->tag);
805}
806
807static gint sort_tag_by_leave(gconstpointer a, gconstpointer b)
808{
809 const dt_tag_t *tuple_a = (const dt_tag_t *)a;
810 const dt_tag_t *tuple_b = (const dt_tag_t *)b;
811
812 return g_strcmp0(tuple_a->leave, tuple_b->leave);
813}
814
815static gint sort_tag_by_count(gconstpointer a, gconstpointer b)
816{
817 const dt_tag_t *tuple_a = (const dt_tag_t *)a;
818 const dt_tag_t *tuple_b = (const dt_tag_t *)b;
819
820 return (tuple_b->count - tuple_a->count);
821}
822// sort_type 0 = path, 1 = leave other = count
823GList *dt_sort_tag(GList *tags, gint sort_type)
824{
825 GList *sorted_tags;
826 if (sort_type <= 1)
827 {
828 for(GList *taglist = tags; taglist; taglist = g_list_next(taglist))
829 {
830 // order such that sub tags are coming directly behind their parent
831 gchar *tag = ((dt_tag_t *)taglist->data)->tag;
832 for(char *letter = tag; *letter; letter++)
833 if(*letter == '|') *letter = '\1';
834 }
835 sorted_tags = g_list_sort(tags, !sort_type ? sort_tag_by_path : sort_tag_by_leave);
836 for(GList *taglist = sorted_tags; taglist; taglist = g_list_next(taglist))
837 {
838 gchar *tag = ((dt_tag_t *)taglist->data)->tag;
839 for(char *letter = tag; *letter; letter++)
840 if(*letter == '\1') *letter = '|';
841 }
842 }
843 else
844 {
845 sorted_tags = g_list_sort(tags, sort_tag_by_count);
846 }
847 return sorted_tags;
848}
849
850GList *dt_tag_get_list(int32_t imgid)
851{
852 GList *taglist = NULL;
853 GList *tags = NULL;
854
855 gboolean omit_tag_hierarchy = dt_conf_get_bool("omit_tag_hierarchy");
856
857 uint32_t count = dt_tag_get_attached(imgid, &taglist, TRUE);
858
859 if(count < 1) return NULL;
860
861 for(GList *tag_iter = taglist; tag_iter; tag_iter = g_list_next(tag_iter))
862 {
863 dt_tag_t *t = (dt_tag_t *)tag_iter->data;
864 gchar *value = t->tag;
865
866 gchar **pch = g_strsplit(value, "|", -1);
867
868 if(!IS_NULL_PTR(pch))
869 {
870 if(omit_tag_hierarchy)
871 {
872 char **iter = pch;
873 for(; *iter && *(iter + 1); iter++);
874 if(*iter) tags = g_list_prepend(tags, g_strdup(*iter));
875 }
876 else
877 {
878 size_t j = 0;
879 while(!IS_NULL_PTR(pch[j]))
880 {
881 tags = g_list_prepend(tags, g_strdup(pch[j]));
882 j++;
883 }
884 }
885 g_strfreev(pch);
886 }
887 }
888
889 dt_tag_free_result(&taglist);
890
891 return dt_util_glist_uniq(tags);
892}
893
894GList *dt_tag_get_hierarchical(int32_t imgid)
895{
896 GList *taglist = NULL;
897 GList *tags = NULL;
898
899 int count = dt_tag_get_attached(imgid, &taglist, TRUE);
900
901 if(count < 1) return NULL;
902
903 for(GList *tag_iter = taglist; tag_iter; tag_iter = g_list_next(tag_iter))
904 {
905 dt_tag_t *t = (dt_tag_t *)tag_iter->data;
906 tags = g_list_prepend(tags, g_strdup(t->tag));
907 }
908
909 dt_tag_free_result(&taglist);
910
911 tags = g_list_reverse(tags); // list was built in reverse order, so un-reverse it
912 return tags;
913}
914
915static GList *_tag_get_tags(const int32_t imgid, const dt_tag_type_t type)
916{
917 GList *tags = NULL;
918 char *images = NULL;
919 if(imgid > 0)
920 images = g_strdup_printf("%d", imgid);
921 else
922 {
923 // we get the query used to retrieve the list of select images
925 }
926
927 sqlite3_stmt *stmt;
928 char query[256] = { 0 };
929 // clang-format off
930 snprintf(query, sizeof(query), "SELECT DISTINCT T.id"
931 " FROM main.tagged_images AS I"
932 " JOIN data.tags T on T.id = I.tagid"
933 " WHERE I.imgid IN (%s) %s",
934 images, type == DT_TAG_TYPE_ALL ? "" :
935 type == DT_TAG_TYPE_DT ? "AND T.id IN memory.darktable_tags" :
936 "AND NOT T.id IN memory.darktable_tags");
937 // clang-format on
939
940 while(sqlite3_step(stmt) == SQLITE_ROW)
941 {
942 tags = g_list_prepend(tags, GINT_TO_POINTER(sqlite3_column_int(stmt, 0)));
943 }
944
945 sqlite3_finalize(stmt);
946 dt_free(images);
947 return tags;
948}
949
950GList *dt_tag_get_tags(const int32_t imgid, const gboolean ignore_dt_tags)
951{
952 return _tag_get_tags(imgid, ignore_dt_tags ? DT_TAG_TYPE_USER : DT_TAG_TYPE_ALL);
953}
954
955static gint _is_not_exportable_tag(gconstpointer a, gconstpointer b)
956{
957 dt_tag_t *ta = (dt_tag_t *)a;
958 dt_tag_t *tb = (dt_tag_t *)b;
959 return ((g_strcmp0(ta->tag, tb->tag) == 0) &&
960 ((ta->flags) & (DT_TF_CATEGORY | DT_TF_PRIVATE))) ? 0 : -1;
961}
962
963GList *dt_tag_get_list_export(int32_t imgid, int32_t flags)
964{
965 GList *taglist = NULL;
966 GList *tags = NULL;
967
968 gboolean omit_tag_hierarchy = flags & DT_META_OMIT_HIERARCHY;
969 gboolean export_private_tags = flags & DT_META_PRIVATE_TAG;
970 gboolean export_tag_synonyms = flags & DT_META_SYNONYMS_TAG;
971
972 uint32_t count = _tag_get_attached_export(imgid, &taglist);
973
974 if(count < 1) return NULL;
975 GList *sorted_tags = dt_sort_tag(taglist, 0);
976 sorted_tags = g_list_reverse(sorted_tags);
977
978 // reset private if export private
979 if(export_private_tags)
980 {
981 for(GList *tagt = sorted_tags; tagt; tagt = g_list_next(tagt))
982 {
983 dt_tag_t *t = (dt_tag_t *)tagt->data;
984 t->flags &= ~DT_TF_PRIVATE;
985 }
986 }
987 for(GList *sorted_iter = sorted_tags; sorted_iter; sorted_iter = g_list_next(sorted_iter))
988 {
989 dt_tag_t *t = (dt_tag_t *)sorted_iter->data;
990 if ((export_private_tags || !(t->flags & DT_TF_PRIVATE))
991 && !(t->flags & DT_TF_CATEGORY))
992 {
993 gchar *tagname = t->leave;
994 tags = g_list_prepend(tags, g_strdup(tagname));
995
996 // if not "omit tag hierarchy" the path elements are added
997 // unless otherwise stated (defined as category or private)
998 if(!omit_tag_hierarchy)
999 {
1000 GList *next = g_list_next(sorted_iter);
1001 gchar *end = g_strrstr(t->tag, "|");
1002 while (end)
1003 {
1004 end[0] = '\0';
1005 end = g_strrstr(t->tag, "|");
1006 if (IS_NULL_PTR(next) ||
1007 !g_list_find_custom(next, t, (GCompareFunc)_is_not_exportable_tag))
1008 {
1009 const gchar *tag = end ? end + 1 : t->tag;
1010 tags = g_list_prepend(tags, g_strdup(tag));
1011 }
1012 }
1013 }
1014
1015 // add synonyms as necessary
1016 if (export_tag_synonyms)
1017 {
1018 gchar *synonyms = t->synonym;
1019 if (synonyms && synonyms[0])
1020 {
1021 gchar **tokens = g_strsplit(synonyms, ",", 0);
1022 if(tokens)
1023 {
1024 gchar **entry = tokens;
1025 while(*entry)
1026 {
1027 char *e = *entry;
1028 if (*e == ' ') e++;
1029 tags = g_list_append(tags, g_strdup(e));
1030 entry++;
1031 }
1032 }
1033 g_strfreev(tokens);
1034 }
1035 }
1036 }
1037 }
1038 dt_tag_free_result(&sorted_tags);
1039
1040 return dt_util_glist_uniq(tags);
1041}
1042
1043GList *dt_tag_get_hierarchical_export(int32_t imgid, int32_t flags)
1044{
1045 GList *taglist = NULL;
1046 GList *tags = NULL;
1047
1048 const int count = dt_tag_get_attached(imgid, &taglist, TRUE);
1049
1050 if(count < 1) return NULL;
1051 const gboolean export_private_tags = flags & DT_META_PRIVATE_TAG;
1052
1053 for(GList *tag_iter = taglist; tag_iter; tag_iter = g_list_next(tag_iter))
1054 {
1055 dt_tag_t *t = (dt_tag_t *)tag_iter->data;
1056 if (export_private_tags || !(t->flags & DT_TF_PRIVATE))
1057 {
1058 tags = g_list_prepend(tags, g_strdup(t->tag));
1059 }
1060 }
1061
1062 dt_tag_free_result(&taglist);
1063
1064 return g_list_reverse(tags); // list was built in reverse order, so un-reverse it
1065}
1066
1067gboolean dt_is_tag_attached(const guint tagid, const int32_t imgid)
1068{
1069 sqlite3_stmt *stmt;
1070 // clang-format off
1072 "SELECT imgid"
1073 " FROM main.tagged_images"
1074 " WHERE imgid = ?1 AND tagid = ?2", -1, &stmt, NULL);
1075 // clang-format on
1076 DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, imgid);
1077 DT_DEBUG_SQLITE3_BIND_INT(stmt, 2, tagid);
1078
1079 const gboolean ret = (sqlite3_step(stmt) == SQLITE_ROW);
1080 sqlite3_finalize(stmt);
1081 return ret;
1082}
1083
1084GList *dt_tag_get_images(const gint tagid)
1085{
1086 GList *result = NULL;
1087 sqlite3_stmt *stmt;
1088
1089 // clang-format off
1091 "SELECT imgid FROM main.tagged_images"
1092 " WHERE tagid = ?1",
1093 -1, &stmt, NULL);
1094 // clang-format on
1095 DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, tagid);
1096
1097 while(sqlite3_step(stmt) == SQLITE_ROW)
1098 {
1099 int id = sqlite3_column_int(stmt, 0);
1100 result = g_list_prepend(result, GINT_TO_POINTER(id));
1101 }
1102 sqlite3_finalize(stmt);
1103
1104 return g_list_reverse(result); // list was built in reverse order, so un-reverse it
1105}
1106
1107GList *dt_tag_get_images_from_list(const GList *img, const gint tagid)
1108{
1109 GList *result = NULL;
1110 char *images = NULL;
1111 for(GList *imgs = (GList *)img; imgs; imgs = g_list_next(imgs))
1112 {
1113 images = dt_util_dstrcat(images, "%d,",GPOINTER_TO_INT(imgs->data));
1114 }
1115 if(images)
1116 {
1117 images[strlen(images) - 1] = '\0';
1118
1119 sqlite3_stmt *stmt;
1120 // clang-format off
1121 gchar *query = g_strdup_printf(
1122 "SELECT imgid FROM main.tagged_images"
1123 " WHERE tagid = %d AND imgid IN (%s)",
1124 tagid, images);
1125 // clang-format on
1126 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), query, -1, &stmt, NULL);
1127
1128 while(sqlite3_step(stmt) == SQLITE_ROW)
1129 {
1130 int id = sqlite3_column_int(stmt, 0);
1131 result = g_list_prepend(result, GINT_TO_POINTER(id));
1132 }
1133
1134 sqlite3_finalize(stmt);
1135 dt_free(query);
1136 dt_free(images);
1137 }
1138 return g_list_reverse(result); // list was built in reverse order, so un-reverse it
1139}
1140
1141uint32_t dt_tag_get_suggestions(GList **result)
1142{
1143 sqlite3_stmt *stmt;
1144
1145 const uint32_t nb_selected = dt_selection_get_length(darktable.selection);
1146 const int nb_recent = dt_conf_get_int("plugins/lighttable/tagging/nb_recent_tags");
1147 const uint32_t confidence = dt_conf_get_int("plugins/lighttable/tagging/confidence");
1148 const char *slist = dt_conf_get_string_const("plugins/lighttable/tagging/recent_tags");
1149
1150 // get attached tags with how many times they are attached in db and on selected images
1151 // clang-format off
1153 "INSERT INTO memory.taglist (id, count, count2)"
1154 " SELECT S.tagid, COUNT(imgid) AS count,"
1155 " CASE WHEN count2 IS NULL THEN 0 ELSE count2 END AS count2"
1156 " FROM main.tagged_images AS S"
1157 " LEFT JOIN ("
1158 " SELECT tagid, COUNT(imgid) AS count2"
1159 " FROM main.tagged_images"
1160 " WHERE imgid IN main.selected_images"
1161 " GROUP BY tagid) AS at"
1162 " ON at.tagid = S.tagid"
1163 " WHERE S.tagid NOT IN memory.darktable_tags"
1164 " GROUP BY S.tagid",
1165 -1, &stmt, NULL);
1166 // clang-format on
1167 sqlite3_step(stmt);
1168 sqlite3_finalize(stmt);
1169
1170 char *query = NULL;
1171 if(confidence != 100)
1172 // clang-format off
1173 query = g_strdup_printf("SELECT td.name, tagid2, t21.count, t21.count2,"
1174 " td.flags, td.synonyms FROM ("
1175 // get tags with required confidence
1176 " SELECT DISTINCT tagid2 FROM ("
1177 " SELECT tagid2 FROM ("
1178 // get how many times (tag1, tag2) are attached together (c12)
1179 " SELECT tagid1, tagid2, count(*) AS c12"
1180 " FROM ("
1181 " SELECT DISTINCT tagid AS tagid1, imgid FROM main.tagged_images"
1182 " JOIN memory.taglist AS t00"
1183 " ON t00.id = tagid1 AND t00.count2 > 0) AS t1"
1184 " JOIN ("
1185 " SELECT DISTINCT tagid AS tagid2, imgid FROM main.tagged_images"
1186 " WHERE tagid NOT IN memory.darktable_tags) AS t2"
1187 " ON t2.imgid = t1.imgid AND tagid1 != tagid2"
1188 " GROUP BY tagid1, tagid2)"
1189 " JOIN memory.taglist AS t01"
1190 " ON t01.id = tagid1"
1191 " JOIN memory.taglist AS t02"
1192 " ON t02.id = tagid2"
1193 // filter by confidence and reject tags attached on all selected images
1194 " WHERE (t01.count-t01.count2) != 0"
1195 " AND (100 * c12 / (t01.count-t01.count2) >= %d)"
1196 " AND t02.count2 != %d) "
1197 " UNION"
1198 // get recent list tags
1199 " SELECT * FROM ("
1200 " SELECT tn.id AS tagid2 FROM data.tags AS tn"
1201 " JOIN memory.taglist AS t02"
1202 " ON t02.id = tn.id"
1203 " WHERE tn.name IN (\'%s\')"
1204 // reject tags attached on all selected images and keep the required number
1205 " AND t02.count2 != %d LIMIT %d)) "
1206 "LEFT JOIN memory.taglist AS t21 "
1207 "ON t21.id = tagid2 "
1208 "LEFT JOIN data.tags as td ON td.id = tagid2 ",
1209 confidence, nb_selected, slist, nb_selected, nb_recent);
1210 // clang-format on
1211 else
1212 // clang-format off
1213 query = g_strdup_printf("SELECT tn.name, tn.id, count, count2,"
1214 " tn.flags, tn.synonyms "
1215 // get recent list tags
1216 "FROM data.tags AS tn "
1217 "JOIN memory.taglist AS t02 "
1218 "ON t02.id = tn.id "
1219 "WHERE tn.name IN (\'%s\')"
1220 // reject tags attached on all selected images and keep the required number
1221 " AND t02.count2 != %d LIMIT %d",
1222 slist, nb_selected, nb_recent);
1223 // clang-format on
1225 -1, &stmt, NULL);
1226
1227 uint32_t count = 0;
1228 while(sqlite3_step(stmt) == SQLITE_ROW)
1229 {
1230 dt_tag_t *t = g_malloc0(sizeof(dt_tag_t));
1231 t->tag = g_strdup((char *)sqlite3_column_text(stmt, 0));
1232 t->leave = g_strrstr(t->tag, "|");
1233 t->leave = t->leave ? t->leave + 1 : t->tag;
1234 t->id = sqlite3_column_int(stmt, 1);
1235 t->count = sqlite3_column_int(stmt, 2);
1236 const uint32_t imgnb = sqlite3_column_int(stmt, 3);
1237 t->select = (nb_selected == 0) ? DT_TS_NO_IMAGE :
1238 (imgnb == nb_selected) ? DT_TS_ALL_IMAGES :
1239 (imgnb == 0) ? DT_TS_NO_IMAGE : DT_TS_SOME_IMAGES;
1240 t->flags = sqlite3_column_int(stmt, 4);
1241 t->synonym = g_strdup((char *)sqlite3_column_text(stmt, 5));
1242 *result = g_list_append(*result, t);
1243 count++;
1244 }
1245
1246 sqlite3_finalize(stmt);
1247
1248 DT_DEBUG_SQLITE3_EXEC(dt_database_get(darktable.db), "DELETE FROM memory.taglist", NULL, NULL, NULL);
1249 dt_free(query);
1250
1251 return count;
1252}
1253
1254void dt_tag_count_tags_images(const gchar *keyword, int *tag_count, int *img_count)
1255{
1256 sqlite3_stmt *stmt;
1257 *tag_count = 0;
1258 *img_count = 0;
1259
1260 if(IS_NULL_PTR(keyword)) return;
1261 gchar *keyword_expr = g_strdup_printf("%s|", keyword);
1262
1263 /* Only select tags that are equal or child to the one we are looking for once. */
1264 // clang-format off
1266 "INSERT INTO memory.similar_tags (tagid)"
1267 " SELECT id"
1268 " FROM data.tags"
1269 " WHERE name = ?1 OR SUBSTR(name, 1, LENGTH(?2)) = ?2",
1270 -1, &stmt, NULL);
1271 // clang-format on
1272 DT_DEBUG_SQLITE3_BIND_TEXT(stmt, 1, keyword, -1, SQLITE_TRANSIENT);
1273 DT_DEBUG_SQLITE3_BIND_TEXT(stmt, 2, keyword_expr, -1, SQLITE_TRANSIENT);
1274 sqlite3_step(stmt);
1275 sqlite3_finalize(stmt);
1276
1277 dt_free(keyword_expr);
1278
1280 "SELECT COUNT(DISTINCT tagid) FROM memory.similar_tags",
1281 -1, &stmt, NULL);
1282 sqlite3_step(stmt);
1283 *tag_count = sqlite3_column_int(stmt, 0);
1284 sqlite3_finalize(stmt);
1285
1286 // clang-format off
1288 "SELECT COUNT(DISTINCT ti.imgid)"
1289 " FROM main.tagged_images AS ti "
1290 " JOIN memory.similar_tags AS st"
1291 " ON st.tagid = ti.tagid",
1292 -1, &stmt, NULL);
1293 // clang-format on
1294
1295 sqlite3_step(stmt);
1296 *img_count = sqlite3_column_int(stmt, 0);
1297 sqlite3_finalize(stmt);
1298
1299 DT_DEBUG_SQLITE3_EXEC(dt_database_get(darktable.db), "DELETE FROM memory.similar_tags", NULL, NULL, NULL);
1300 }
1301
1302void dt_tag_get_tags_images(const gchar *keyword, GList **tag_list, GList **img_list)
1303{
1304 sqlite3_stmt *stmt;
1305
1306 if(IS_NULL_PTR(keyword)) return;
1307 gchar *keyword_expr = g_strdup_printf("%s|", keyword);
1308
1309/* Only select tags that are equal or child to the one we are looking for once. */
1310 // clang-format off
1312 "INSERT INTO memory.similar_tags (tagid)"
1313 " SELECT id"
1314 " FROM data.tags"
1315 " WHERE name = ?1 OR SUBSTR(name, 1, LENGTH(?2)) = ?2",
1316 -1, &stmt, NULL);
1317 // clang-format on
1318 DT_DEBUG_SQLITE3_BIND_TEXT(stmt, 1, keyword, -1, SQLITE_TRANSIENT);
1319 DT_DEBUG_SQLITE3_BIND_TEXT(stmt, 2, keyword_expr, -1, SQLITE_TRANSIENT);
1320 sqlite3_step(stmt);
1321 sqlite3_finalize(stmt);
1322
1323 dt_free(keyword_expr);
1324
1325 // clang-format off
1327 "SELECT ST.tagid, T.name"
1328 " FROM memory.similar_tags ST"
1329 " JOIN data.tags T"
1330 " ON T.id = ST.tagid ",
1331 -1, &stmt, NULL);
1332 // clang-format on
1333
1334 while(sqlite3_step(stmt) == SQLITE_ROW)
1335 {
1336 dt_tag_t *t = g_malloc0(sizeof(dt_tag_t));
1337 t->id = sqlite3_column_int(stmt, 0);
1338 t->tag = g_strdup((char *)sqlite3_column_text(stmt, 1));
1339 *tag_list = g_list_append((*tag_list), t);
1340 }
1341 sqlite3_finalize(stmt);
1342 // clang-format off
1344 "SELECT DISTINCT ti.imgid"
1345 " FROM main.tagged_images AS ti"
1346 " JOIN memory.similar_tags AS st"
1347 " ON st.tagid = ti.tagid",
1348 -1, &stmt, NULL);
1349 // clang-format on
1350 while(sqlite3_step(stmt) == SQLITE_ROW)
1351 {
1352 *img_list = g_list_append((*img_list), GINT_TO_POINTER(sqlite3_column_int(stmt, 0)));
1353 }
1354 sqlite3_finalize(stmt);
1355
1356 DT_DEBUG_SQLITE3_EXEC(dt_database_get(darktable.db), "DELETE FROM memory.similar_tags", NULL, NULL, NULL);
1357}
1358
1359uint32_t dt_tag_images_count(gint tagid)
1360{
1361 sqlite3_stmt *stmt;
1362
1363 // clang-format off
1365 "SELECT COUNT(DISTINCT imgid) AS imgnb"
1366 " FROM main.tagged_images"
1367 " WHERE tagid = ?1",
1368 -1, &stmt, NULL);
1369 // clang-format on
1370 DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, tagid);
1371 sqlite3_step(stmt);
1372 const uint32_t nb_images = sqlite3_column_int(stmt, 0);
1373 sqlite3_finalize(stmt);
1374 return nb_images;
1375}
1376
1377uint32_t dt_tag_get_with_usage(GList **result)
1378{
1379 sqlite3_stmt *stmt;
1380
1381 /* Select tags that are similar to the keyword and are actually used to tag images*/
1382 // clang-format off
1384 "INSERT INTO memory.taglist (id, count)"
1385 " SELECT tagid, COUNT(*)"
1386 " FROM main.tagged_images"
1387 " GROUP BY tagid",
1388 -1, &stmt, NULL);
1389 // clang-format on
1390 sqlite3_step(stmt);
1391 sqlite3_finalize(stmt);
1392
1393 const uint32_t nb_selected = dt_selection_get_length(darktable.selection);
1394
1395 /* Now put all the bits together */
1396 // clang-format off
1398 "SELECT T.name, T.id, MT.count, CT.imgnb, T.flags, T.synonyms"
1399 " FROM data.tags T "
1400 " LEFT JOIN memory.taglist MT ON MT.id = T.id "
1401 " LEFT JOIN (SELECT tagid, COUNT(DISTINCT imgid) AS imgnb"
1402 " FROM main.tagged_images "
1403 " WHERE imgid IN (SELECT imgid FROM main.selected_images) GROUP BY tagid) AS CT "
1404 " ON CT.tagid = T.id"
1405 " WHERE T.id NOT IN memory.darktable_tags "
1406 " ORDER BY T.name ",
1407 -1, &stmt, NULL);
1408 // clang-format on
1409
1410 /* ... and create the result list to send upwards */
1411 uint32_t count = 0;
1412 while(sqlite3_step(stmt) == SQLITE_ROW)
1413 {
1414 dt_tag_t *t = g_malloc0(sizeof(dt_tag_t));
1415 t->tag = g_strdup((char *)sqlite3_column_text(stmt, 0));
1416 t->leave = g_strrstr(t->tag, "|");
1417 t->leave = t->leave ? t->leave + 1 : t->tag;
1418 t->id = sqlite3_column_int(stmt, 1);
1419 t->count = sqlite3_column_int(stmt, 2);
1420 const uint32_t imgnb = sqlite3_column_int(stmt, 3);
1421 t->select = (nb_selected == 0) ? DT_TS_NO_IMAGE :
1422 (imgnb == nb_selected) ? DT_TS_ALL_IMAGES :
1423 (imgnb == 0) ? DT_TS_NO_IMAGE : DT_TS_SOME_IMAGES;
1424 t->flags = sqlite3_column_int(stmt, 4);
1425 t->synonym = g_strdup((char *)sqlite3_column_text(stmt, 5));
1426 *result = g_list_append(*result, t);
1427 count++;
1428 }
1429
1430 sqlite3_finalize(stmt);
1431 DT_DEBUG_SQLITE3_EXEC(dt_database_get(darktable.db), "DELETE FROM memory.taglist", NULL, NULL, NULL);
1432
1433 return count;
1434}
1435
1436uint32_t dt_tag_get_collection_tags(GList **result)
1437{
1438 sqlite3_stmt *stmt;
1439
1440 /* Tags attached to at least one image of the current collection */
1441 // clang-format off
1443 "SELECT DISTINCT T.name, T.id"
1444 " FROM data.tags T"
1445 " JOIN main.tagged_images TI ON TI.tagid = T.id"
1446 " WHERE TI.imgid IN (SELECT imgid FROM memory.collected_images)"
1447 " AND T.id NOT IN memory.darktable_tags"
1448 " ORDER BY T.name",
1449 -1, &stmt, NULL);
1450 // clang-format on
1451
1452 uint32_t count = 0;
1453 while(sqlite3_step(stmt) == SQLITE_ROW)
1454 {
1455 dt_tag_t *t = g_malloc0(sizeof(dt_tag_t));
1456 t->tag = g_strdup((char *)sqlite3_column_text(stmt, 0));
1457 t->leave = g_strrstr(t->tag, "|");
1458 t->leave = t->leave ? t->leave + 1 : t->tag;
1459 t->id = sqlite3_column_int(stmt, 1);
1460 *result = g_list_append(*result, t);
1461 count++;
1462 }
1463
1464 sqlite3_finalize(stmt);
1465
1466 return count;
1467}
1468
1469static gchar *dt_cleanup_synonyms(gchar *synonyms_entry)
1470{
1471 gchar *synonyms = NULL;
1472 for(char *letter = synonyms_entry; *letter; letter++)
1473 {
1474 if(*letter == ';' || *letter == '\n') *letter = ',';
1475 if(*letter == '\r') *letter = ' ';
1476 }
1477 gchar **tokens = g_strsplit(synonyms_entry, ",", 0);
1478 if(tokens)
1479 {
1480 gchar **entry = tokens;
1481 while (*entry)
1482 {
1483 char *e = g_strstrip(*entry);
1484 if(*e)
1485 {
1486 synonyms = dt_util_dstrcat(synonyms, "%s, ", e);
1487 }
1488 entry++;
1489 }
1490 if (synonyms)
1491 synonyms[strlen(synonyms) - 2] = '\0';
1492 }
1493 g_strfreev(tokens);
1494 return synonyms;
1495}
1496
1497gchar *dt_tag_get_synonyms(gint tagid)
1498{
1499 sqlite3_stmt *stmt;
1500 gchar *synonyms = NULL;
1501
1503 "SELECT synonyms FROM data.tags WHERE id = ?1 ",
1504 -1, &stmt, NULL);
1505 DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, tagid);
1506
1507 if (sqlite3_step(stmt) == SQLITE_ROW)
1508 {
1509 synonyms = g_strdup((char *)sqlite3_column_text(stmt, 0));
1510 }
1511 sqlite3_finalize(stmt);
1512 return synonyms;
1513}
1514
1515void dt_tag_set_synonyms(gint tagid, gchar *synonyms_entry)
1516{
1517 if (!synonyms_entry) return;
1518 char *synonyms = dt_cleanup_synonyms(synonyms_entry);
1519
1520 sqlite3_stmt *stmt;
1522 "UPDATE data.tags SET synonyms = ?2 WHERE id = ?1 ",
1523 -1, &stmt, NULL);
1524 DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, tagid);
1525 DT_DEBUG_SQLITE3_BIND_TEXT(stmt, 2, synonyms, -1, SQLITE_TRANSIENT);
1526 sqlite3_step(stmt);
1527 sqlite3_finalize(stmt);
1528 dt_free(synonyms);
1529}
1530
1531gint dt_tag_get_flags(gint tagid)
1532{
1533 sqlite3_stmt *stmt;
1534
1536 "SELECT flags FROM data.tags WHERE id = ?1 ",
1537 -1, &stmt, NULL);
1538 DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, tagid);
1539
1540 gint flags = 0;
1541 if (sqlite3_step(stmt) == SQLITE_ROW)
1542 {
1543 flags = sqlite3_column_int(stmt, 0);
1544 }
1545 sqlite3_finalize(stmt);
1546 return flags;
1547}
1548
1549void dt_tag_set_flags(gint tagid, gint flags)
1550{
1551 sqlite3_stmt *stmt;
1552
1554 "UPDATE data.tags SET flags = ?2 WHERE id = ?1 ",
1555 -1, &stmt, NULL);
1556 DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, tagid);
1558 sqlite3_step(stmt);
1559 sqlite3_finalize(stmt);
1560}
1561
1562void dt_tag_add_synonym(gint tagid, gchar *synonym)
1563{
1564 char *synonyms = dt_tag_get_synonyms(tagid);
1565 if (synonyms)
1566 {
1567 synonyms = dt_util_dstrcat(synonyms, ", %s", synonym);
1568 }
1569 else
1570 {
1571 synonyms = g_strdup(synonym);
1572 }
1573 sqlite3_stmt *stmt;
1575 "UPDATE data.tags SET synonyms = ?2 WHERE id = ?1 ",
1576 -1, &stmt, NULL);
1577 DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, tagid);
1578 DT_DEBUG_SQLITE3_BIND_TEXT(stmt, 2, synonyms, -1, SQLITE_TRANSIENT);
1579 sqlite3_step(stmt);
1580 sqlite3_finalize(stmt);
1581 dt_free(synonyms);
1582}
1583
1584static void _free_result_item(gpointer data)
1585{
1586 dt_tag_t *t = (dt_tag_t*)data;
1587 dt_free(t->tag);
1588 dt_free(t->synonym);
1589 dt_free(t);
1590}
1591
1592void dt_tag_free_result(GList **result)
1593{
1594 if(result && *result)
1595 {
1596 g_list_free_full(*result, _free_result_item);
1597 *result = NULL;
1598 }
1599}
1600
1601uint32_t dt_tag_get_recent_used(GList **result)
1602{
1603 return 0;
1604}
1605
1606/*
1607 TODO
1608 the file format allows to specify {synonyms} that are one hierarchy level deeper than the parent. those are not
1609 to be shown in the gui but can be searched. when the parent or a synonym is attached then ALSO the rest of the
1610 bunch is to be added.
1611 there is also a ~ prefix for tags that indicate that the tag order has to be kept instead of sorting them. that's
1612 also not possible at the moment.
1613*/
1614uint32_t dt_tag_import(const char *filename)
1615{
1616 FILE *fd = g_fopen(filename, "r");
1617 if(IS_NULL_PTR(fd)) return -1;
1618
1619 GList * hierarchy = NULL;
1620 char *line = NULL;
1621 size_t len = 0;
1622 uint32_t count = 0;
1623 guint tagid = 0;
1624 guint previous_category_depth = 0;
1625 gboolean previous_category = FALSE;
1626 gboolean previous_synonym = FALSE;
1627
1628 while(getline(&line, &len, fd) != -1)
1629 {
1630 // remove newlines and set start past the initial tabs
1631 char *start = line;
1632 while(*start == '\t' || *start == ' ' || *start == ',' || *start == ';') start++;
1633 const int depth = start - line;
1634
1635 char *end = line + strlen(line) - 1;
1636 while((*end == '\n' || *end == '\r' || *end == ',' || *end == ';') && end >= start)
1637 {
1638 *end = '\0';
1639 end--;
1640 }
1641
1642 // remove control characters from the string
1643 // if no associated synonym the previous category node can be reused
1644 gboolean skip = FALSE;
1645 gboolean category = FALSE;
1646 gboolean synonym = FALSE;
1647 if (*start == '[' && *end == ']') // categories
1648 {
1649 category = TRUE;
1650 start++;
1651 *end-- = '\0';
1652 }
1653 else if (*start == '{' && *end == '}') // synonyms
1654 {
1655 synonym = TRUE;
1656 start++;
1657 *end-- = '\0';
1658 }
1659 if(*start == '~') // fixed order. TODO not possible with our db
1660 {
1661 skip = TRUE;
1662 start++;
1663 }
1664
1665 if (synonym)
1666 {
1667 // associate the synonym to last tag
1668 if (tagid)
1669 {
1670 char *tagname = g_strdup(start);
1671 // clear synonyms before importing the new ones => allows export, modification and back import
1672 if (!previous_synonym) dt_tag_set_synonyms(tagid, "");
1673 dt_tag_add_synonym(tagid, tagname);
1674 dt_free(tagname);
1675 }
1676 }
1677 else
1678 {
1679 // remove everything past the current prefix from hierarchy
1680 GList *iter = g_list_nth(hierarchy, depth);
1681 while(iter)
1682 {
1683 GList *current = iter;
1684 iter = g_list_next(iter);
1685 hierarchy = g_list_delete_link(hierarchy, current);
1686 }
1687
1688 // add the current level
1689 hierarchy = g_list_append(hierarchy, g_strdup(start));
1690
1691 // add tag to db iff it's not something to be ignored
1692 if(!skip)
1693 {
1694 char *tag = dt_util_glist_to_str("|", hierarchy);
1695 if (previous_category && (depth > previous_category_depth + 1))
1696 {
1697 // reuse previous tag
1698 dt_tag_rename(tagid, tag);
1699 if (!category)
1700 dt_tag_set_flags(tagid, 0);
1701 }
1702 else
1703 {
1704 // create a new tag
1705 count++;
1706 tagid = 1; // if 0, dt_tag_new creates a new one even if the tag already exists
1707 dt_tag_new(tag, &tagid);
1708 if (category)
1710 }
1711 dt_free(tag);
1712 }
1713 }
1714 previous_category_depth = category ? depth : 0;
1715 previous_category = category;
1716 previous_synonym = synonym;
1717 }
1718
1719 dt_free(line);
1720 g_list_free_full(hierarchy, dt_free_gpointer);
1721 hierarchy = NULL;
1722 fclose(fd);
1723
1725
1726 return count;
1727}
1728
1729/*
1730 TODO: there is one corner case where i am not sure if we are doing the correct thing. some examples i found
1731 on the internet agreed with this version, some used an alternative:
1732 consider two tags like "foo|bar" and "foo|bar|baz". the "foo|bar" part is both a regular tag (from the 1st tag)
1733 and also a category (from the 2nd tag). the two way to output are
1734
1735 [foo]
1736 bar
1737 baz
1738
1739 and
1740
1741 [foo]
1742 bar
1743 [bar]
1744 baz
1745
1746 we are using the first (mostly because it was easier to implement ;)). if this poses problems with other programs
1747 supporting these files then we should fix that.
1748*/
1749uint32_t dt_tag_export(const char *filename)
1750{
1751 FILE *fd = g_fopen(filename, "w");
1752
1753 if(IS_NULL_PTR(fd)) return -1;
1754
1755 GList *tags = NULL;
1756 gint count = 0;
1757 dt_tag_get_with_usage(&tags);
1758 GList *sorted_tags = dt_sort_tag(tags, 0);
1759
1760 gchar **hierarchy = NULL;
1761 for(GList *tag_elt = sorted_tags; tag_elt; tag_elt = g_list_next(tag_elt))
1762 {
1763 const gchar *tag = ((dt_tag_t *)tag_elt->data)->tag;
1764 const char *synonyms = ((dt_tag_t *)tag_elt->data)->synonym;
1765 const guint flags = ((dt_tag_t *)tag_elt->data)->flags;
1766 gchar **tokens = g_strsplit(tag, "|", -1);
1767
1768 // find how many common levels are shared with the last tag
1769 int common_start;
1770 for(common_start = 0; hierarchy && hierarchy[common_start] && tokens && tokens[common_start]; common_start++)
1771 {
1772 if(g_strcmp0(hierarchy[common_start], tokens[common_start])) break;
1773 }
1774
1775 g_strfreev(hierarchy);
1776 hierarchy = tokens;
1777
1778 int tabs = common_start;
1779 for(size_t i = common_start; tokens && tokens[i]; i++, tabs++)
1780 {
1781 for(int j = 0; j < tabs; j++) fputc('\t', fd);
1782 if(!tokens[i + 1])
1783 {
1784 count++;
1785 if (flags & DT_TF_CATEGORY)
1786 fprintf(fd, "[%s]\n", tokens[i]);
1787 else
1788 fprintf(fd, "%s\n", tokens[i]);
1789 if (synonyms && synonyms[0])
1790 {
1791 gchar **tokens2 = g_strsplit(synonyms, ",", 0);
1792 if(tokens2)
1793 {
1794 gchar **entry = tokens2;
1795 while(*entry)
1796 {
1797 char *e = *entry;
1798 if (*e == ' ') e++;
1799 for(int j = 0; j < tabs+1; j++) fputc('\t', fd);
1800 fprintf(fd, "{%s}\n", e);
1801 entry++;
1802 }
1803 }
1804 g_strfreev(tokens2);
1805 }
1806 }
1807 else
1808 fprintf(fd, "%s\n", tokens[i]);
1809 }
1810 }
1811
1812 g_strfreev(hierarchy);
1813
1814 dt_tag_free_result(&tags);
1815
1816 fclose(fd);
1817
1818 return count;
1819}
1820
1821char *dt_tag_get_subtags(const int32_t imgid, const char *category, const int level)
1822{
1823 if (IS_NULL_PTR(category)) return NULL;
1824 const guint rootnb = dt_util_string_count_char(category, '|');
1825 char *tags = NULL;
1826 sqlite3_stmt *stmt;
1827 // clang-format off
1829 "SELECT DISTINCT T.name FROM main.tagged_images AS I "
1830 "INNER JOIN data.tags AS T "
1831 "ON T.id = I.tagid AND SUBSTR(T.name, 1, LENGTH(?2)) = ?2 "
1832 "WHERE I.imgid = ?1",
1833 -1, &stmt, NULL);
1834 // clang-format on
1835 DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, imgid);
1836 DT_DEBUG_SQLITE3_BIND_TEXT(stmt, 2, category, -1, SQLITE_TRANSIENT);
1837 while(sqlite3_step(stmt) == SQLITE_ROW)
1838 {
1839 char *tag = (char *)sqlite3_column_text(stmt, 0);
1840 const guint tagnb = dt_util_string_count_char(tag, '|');
1841 if (tagnb >= rootnb + level)
1842 {
1843 gchar **pch = g_strsplit(tag, "|", -1);
1844 char *subtag = pch[rootnb + level];
1845 gboolean valid = TRUE;
1846 // check we have not yet this subtag in the list
1847 if(tags && strlen(tags) >= strlen(subtag) + 1)
1848 {
1849 gchar *found = g_strstr_len(tags, strlen(tags), subtag);
1850 if(found && found[strlen(subtag)] == ',')
1851 valid = FALSE;
1852 }
1853 if(valid)
1854 tags = dt_util_dstrcat(tags, "%s,", subtag);
1855 g_strfreev(pch);
1856 }
1857 }
1858 if(tags) tags[strlen(tags) - 1] = '\0'; // remove the last comma
1859 sqlite3_finalize(stmt);
1860 return tags;
1861}
1862
1863gboolean dt_tag_get_tag_order_by_id(const uint32_t tagid, uint32_t *sort,
1864 gboolean *descending)
1865{
1866 gboolean res = FALSE;
1867 if(IS_NULL_PTR(sort) || !descending) return res;
1868 sqlite3_stmt *stmt;
1869 // clang-format off
1871 "SELECT T.flags FROM data.tags AS T "
1872 "WHERE T.id = ?1",
1873 -1, &stmt, NULL);
1874 // clang-format on
1875 DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, tagid);
1876 if(sqlite3_step(stmt) == SQLITE_ROW)
1877 {
1878 const uint32_t flags = sqlite3_column_int(stmt, 0);
1880 {
1881 // the 16 upper bits of flags hold the order
1882 *sort = (flags & ~DT_TF_DESCENDING) >> 16;
1883 *descending = flags & DT_TF_DESCENDING;
1884 res = TRUE;
1885 }
1886 }
1887 sqlite3_finalize(stmt);
1888 return res;
1889}
1890
1891uint32_t dt_tag_get_tag_id_by_name(const char * const name)
1892{
1893 if(IS_NULL_PTR(name)) return 0;
1894 uint32_t tagid = 0;
1895 // clang-format off
1896 const char *query = "SELECT T.id, T.flags FROM data.tags AS T "
1897 "WHERE LOWER(T.name) = LOWER(?1)";
1898 // clang-format on
1899 sqlite3_stmt *stmt;
1901 query, -1, &stmt, NULL);
1902 DT_DEBUG_SQLITE3_BIND_TEXT(stmt, 1, name, -1, SQLITE_TRANSIENT);
1903 if(sqlite3_step(stmt) == SQLITE_ROW)
1904 {
1905 tagid = sqlite3_column_int(stmt, 0);
1906 }
1907 sqlite3_finalize(stmt);
1908 return tagid;
1909}
1910
1911void dt_tag_set_tag_order_by_id(const uint32_t tagid, const uint32_t sort,
1912 const gboolean descending)
1913{
1914 // use the upper 16 bits of flags to store the order
1915 const uint32_t flags = sort << 16 | (descending ? DT_TF_DESCENDING : 0)
1917 sqlite3_stmt *stmt;
1918 // clang-format off
1920 "UPDATE data.tags"
1921 " SET flags = (IFNULL(flags, 0) & ?3) | ?2 "
1922 "WHERE id = ?1",
1923 -1, &stmt, NULL);
1924 // clang-format on
1925 DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, tagid);
1928 sqlite3_step(stmt);
1929 sqlite3_finalize(stmt);
1930}
1931
1955
1956// clang-format off
1957// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
1958// vim: shiftwidth=2 expandtab tabstop=2 cindent
1959// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
1960// clang-format on
GList * dt_act_on_get_images()
Definition act_on.c:39
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
dt_tag_actions_t
int type
char * name
int dt_conf_get_bool(const char *name)
int dt_conf_get_int(const char *name)
const char * dt_conf_get_string_const(const char *name)
darktable_t darktable
Definition darktable.c:181
#define UNKNOWN_IMAGE
Definition darktable.h:182
static void dt_free_gpointer(gpointer ptr)
Definition darktable.h:463
#define dt_free(ptr)
Definition darktable.h:456
static const dt_aligned_pixel_simd_t value
Definition darktable.h:577
#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 darktable.h:281
sqlite3 * dt_database_get(const dt_database_t *db)
Definition database.c:3646
#define DT_DEBUG_SQLITE3_EXEC(a, b, c, d, e)
Definition debug.h:99
#define DT_DEBUG_SQLITE3_PREPARE_V2(a, b, c, d, e)
Definition debug.h:107
#define DT_DEBUG_SQLITE3_BIND_TEXT(a, b, c, d, e)
Definition debug.h:118
#define DT_DEBUG_SQLITE3_BIND_INT(a, b, c)
Definition debug.h:115
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:170
const int t
@ DT_META_OMIT_HIERARCHY
@ DT_META_PRIVATE_TAG
@ DT_META_SYNONYMS_TAG
dt_mipmap_buffer_dsc_flags flags
Definition mipmap_cache.c:4
gchar * dt_selection_ids_to_string(struct dt_selection_t *selection)
Definition selection.c:367
int dt_selection_get_length(struct dt_selection_t *selection)
Definition selection.c:179
#define DT_DEBUG_CONTROL_SIGNAL_RAISE(ctlsig, signal,...)
Definition signal.h:347
@ DT_SIGNAL_TAG_CHANGED
This signal is raised when a tag is added/deleted/changed
Definition signal.h:130
struct dt_undo_t * undo
Definition darktable.h:787
struct dt_selection_t * selection
Definition darktable.h:782
const struct dt_database_t * db
Definition darktable.h:779
struct dt_control_signal_t * signals
Definition darktable.h:774
gchar * tag
Definition tags.h:52
guint count
Definition tags.h:55
gchar * leave
Definition tags.h:53
gint flags
Definition tags.h:57
int32_t imgid
Definition tags.c:63
GList * before
Definition tags.c:64
GList * after
Definition tags.c:65
GList * dt_tag_get_images_from_list(const GList *img, const gint tagid)
Definition tags.c:1107
GList * dt_tag_get_images(const gint tagid)
Definition tags.c:1084
static gchar * dt_cleanup_synonyms(gchar *synonyms_entry)
Definition tags.c:1469
GList * dt_tag_get_list_export(int32_t imgid, int32_t flags)
Definition tags.c:963
void dt_tag_get_tags_images(const gchar *keyword, GList **tag_list, GList **img_list)
Definition tags.c:1302
static void _bulk_add_tags(const gchar *tag_list)
Definition tags.c:119
guint dt_tag_remove_list(GList *tag_list)
Definition tags.c:294
gboolean dt_tag_attach(const guint tagid, const int32_t imgid, const gboolean undo_on, const gboolean group_on)
Definition tags.c:485
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:506
uint32_t dt_tag_get_suggestions(GList **result)
Definition tags.c:1141
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:144
static gint sort_tag_by_count(gconstpointer a, gconstpointer b)
Definition tags.c:815
char * dt_tag_get_subtags(const int32_t imgid, const char *category, const int level)
Definition tags.c:1821
uint32_t dt_tag_get_attached(const int32_t imgid, GList **result, const gboolean ignore_dt_tags)
Definition tags.c:635
static gint sort_tag_by_path(gconstpointer a, gconstpointer b)
Definition tags.c:799
gboolean dt_tag_attach_images(const guint tagid, const GList *img, const gboolean undo_on)
Definition tags.c:463
void dt_tag_add_synonym(gint tagid, gchar *synonym)
Definition tags.c:1562
void dt_set_darktable_tags()
Definition tags.c:618
gchar * dt_tag_get_synonyms(gint tagid)
Definition tags.c:1497
static void _free_result_item(gpointer data)
Definition tags.c:1584
gboolean dt_tag_detach(const guint tagid, const int32_t imgid, const gboolean undo_on, const gboolean group_on)
Definition tags.c:593
gint dt_tag_get_flags(gint tagid)
Definition tags.c:1531
static gboolean _tag_add_tags_to_list(GList **list, const GList *tags)
Definition tags.c:377
static GList * _tag_get_tags(const int32_t imgid, const dt_tag_type_t type)
Definition tags.c:915
gboolean dt_tag_attach_string_list(const gchar *tags, const GList *img, const gboolean undo_on)
Definition tags.c:526
uint32_t dt_tag_images_count(gint tagid)
Definition tags.c:1359
static gint _is_not_exportable_tag(gconstpointer a, gconstpointer b)
Definition tags.c:955
gboolean dt_tag_detach_by_string(const char *name, const int32_t imgid, const gboolean undo_on, const gboolean group_on)
Definition tags.c:608
gboolean dt_is_tag_attached(const guint tagid, const int32_t imgid)
Definition tags.c:1067
static gchar * _get_tb_added_tag_string_values(const int img, GList *before, GList *after)
Definition tags.c:83
gboolean dt_tag_new(const char *name, guint *tagid)
Definition tags.c:179
static sqlite3_stmt * _tag_get_attached_single_stmt
Definition tags.c:56
void dt_tag_set_tag_order_by_id(const uint32_t tagid, const uint32_t sort, const gboolean descending)
Definition tags.c:1911
uint32_t dt_tag_get_collection_tags(GList **result)
Definition tags.c:1436
static sqlite3_stmt * _tag_get_attached_single_ignore_stmt
Definition tags.c:57
gboolean dt_tag_get_tag_order_by_id(const uint32_t tagid, uint32_t *sort, gboolean *descending)
Definition tags.c:1863
static gboolean _tag_remove_tags_from_list(GList **list, const GList *tags)
Definition tags.c:391
void dt_tag_rename(const guint tagid, const gchar *new_tagname)
Definition tags.c:340
gchar * dt_tag_get_name(const guint tagid)
Definition tags.c:325
dt_tag_actions_t
Definition tags.c:409
@ DT_TA_ATTACH
Definition tags.c:410
@ DT_TA_SET
Definition tags.c:412
@ DT_TA_SET_ALL
Definition tags.c:413
@ DT_TA_DETACH
Definition tags.c:411
void dt_tags_cleanup(void)
Definition tags.c:1932
GList * dt_tag_get_list(int32_t imgid)
Definition tags.c:850
uint32_t dt_tag_import(const char *filename)
Definition tags.c:1614
GList * dt_tag_get_hierarchical_export(int32_t imgid, int32_t flags)
Definition tags.c:1043
static gint sort_tag_by_leave(gconstpointer a, gconstpointer b)
Definition tags.c:807
void dt_tag_set_flags(gint tagid, gint flags)
Definition tags.c:1549
guint dt_tag_remove(const guint tagid, gboolean final)
Definition tags.c:236
static void _tags_undo_data_free(gpointer data)
Definition tags.c:172
void dt_tag_delete_tag_batch(const char *flatlist)
Definition tags.c:274
static sqlite3_stmt * _tag_get_attached_selected_ignore_stmt
Definition tags.c:59
gboolean dt_tag_detach_images(const guint tagid, const GList *img, const gboolean undo_on)
Definition tags.c:570
GList * dt_tag_get_hierarchical(int32_t imgid)
Definition tags.c:894
static gboolean _tag_execute(const GList *tags, const GList *imgs, GList **undo, const gboolean undo_on, const gint action)
Definition tags.c:418
GList * dt_tag_get_tags(const int32_t imgid, const gboolean ignore_dt_tags)
Definition tags.c:950
void dt_tag_free_result(GList **result)
Definition tags.c:1592
GList * dt_sort_tag(GList *tags, gint sort_type)
Definition tags.c:823
gboolean dt_tag_exists(const char *name, guint *tagid)
Definition tags.c:356
void dt_tag_set_synonyms(gint tagid, gchar *synonyms_entry)
Definition tags.c:1515
static sqlite3_stmt * _tag_get_attached_selected_stmt
Definition tags.c:58
static void _undo_tags_free(gpointer data)
Definition tags.c:162
static gchar * _get_tb_removed_tag_string_values(GList *before, GList *after)
Definition tags.c:68
uint32_t dt_tag_export(const char *filename)
Definition tags.c:1749
uint32_t dt_tag_get_tag_id_by_name(const char *const name)
Definition tags.c:1891
dt_tag_type_t
Definition tags.c:402
@ DT_TAG_TYPE_DT
Definition tags.c:403
@ DT_TAG_TYPE_USER
Definition tags.c:404
@ DT_TAG_TYPE_ALL
Definition tags.c:405
gboolean dt_tag_new_from_gui(const char *name, guint *tagid)
Definition tags.c:228
uint32_t dt_tag_get_recent_used(GList **result)
Definition tags.c:1601
static void _bulk_remove_tags(const int img, const gchar *tag_list)
Definition tags.c:106
void dt_tag_count_tags_images(const gchar *keyword, int *tag_count, int *img_count)
Definition tags.c:1254
static void _pop_undo_execute(const int32_t imgid, GList *before, GList *after)
Definition tags.c:132
uint32_t dt_tag_get_with_usage(GList **result)
Definition tags.c:1377
static uint32_t _tag_get_attached_export(const int32_t imgid, GList **result)
Definition tags.c:758
@ DT_TS_ALL_IMAGES
Definition tags.h:75
@ DT_TS_NO_IMAGE
Definition tags.h:73
@ DT_TS_SOME_IMAGES
Definition tags.h:74
@ DT_TF_ORDER_SET
Definition tags.h:65
@ DT_TF_DESCENDING
Definition tags.h:66
@ DT_TF_CATEGORY
Definition tags.h:63
@ DT_TF_PRIVATE
Definition tags.h:64
#define DT_TF_ALL
Definition tags.h:69
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:39
@ DT_UNDO_TAGS
Definition undo.h:46
void * dt_undo_data_t
Definition undo.h:67
dt_undo_action_t
Definition undo.h:62
@ DT_ACTION_UNDO
Definition undo.h:63
guint dt_util_string_count_char(const char *text, const char needle)
Definition utility.c:811
gchar * dt_util_glist_to_str(const gchar *separator, GList *items)
Definition utility.c:166
gchar * dt_util_dstrcat(gchar *str, const gchar *format,...)
Definition utility.c:95
GList * dt_util_glist_uniq(GList *items)
Definition utility.c:193