Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
tagging.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2010-2011, 2013 Henrik Andersson.
4 Copyright (C) 2010-2011, 2013-2014 johannes hanika.
5 Copyright (C) 2010 Stuart Henderson.
6 Copyright (C) 2010-2017 Tobias Ellinghaus.
7 Copyright (C) 2011 Antony Dovgal.
8 Copyright (C) 2011, 2013 Jérémy Rosen.
9 Copyright (C) 2011 Robert Bieber.
10 Copyright (C) 2012 Richard Wonka.
11 Copyright (C) 2013 José Carlos García Sogo.
12 Copyright (C) 2013-2016 Roman Lebedev.
13 Copyright (C) 2013 Simon Spannagel.
14 Copyright (C) 2014 Pascal de Bruijn.
15 Copyright (C) 2017-2018, 2020 parafin.
16 Copyright (C) 2018 August Schwerdfeger.
17 Copyright (C) 2018, 2023 Maurizio Paglia.
18 Copyright (C) 2018 rawfiner.
19 Copyright (C) 2019 Alexis Mousset.
20 Copyright (C) 2019, 2022-2023, 2025 Aurélien PIERRE.
21 Copyright (C) 2019 Edgardo Hoszowski.
22 Copyright (C) 2019-2022 Pascal Obry.
23 Copyright (C) 2019-2022 Philippe Weyland.
24 Copyright (C) 2019 Sam Smith.
25 Copyright (C) 2020-2022 Aldric Renaudin.
26 Copyright (C) 2020-2022 Diederik Ter Rahe.
27 Copyright (C) 2020-2021 Hubert Kowalski.
28 Copyright (C) 2020 Marco.
29 Copyright (C) 2020-2021 Ralf Brown.
30 Copyright (C) 2021 Harald.
31 Copyright (C) 2021 luzpaz.
32 Copyright (C) 2021 Marco Carrarini.
33 Copyright (C) 2022 Martin Bařinka.
34 Copyright (C) 2022 Nicolas Auffray.
35
36 darktable is free software: you can redistribute it and/or modify
37 it under the terms of the GNU General Public License as published by
38 the Free Software Foundation, either version 3 of the License, or
39 (at your option) any later version.
40
41 darktable is distributed in the hope that it will be useful,
42 but WITHOUT ANY WARRANTY; without even the implied warranty of
43 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
44 GNU General Public License for more details.
45
46 You should have received a copy of the GNU General Public License
47 along with darktable. If not, see <http://www.gnu.org/licenses/>.
48*/
49#include "common/collection.h"
50#include "common/selection.h"
51#include "common/darktable.h"
52#include "gui/gdkkeys.h"
53#include "common/debug.h"
54#include "common/tags.h"
55#include "control/conf.h"
56#include "control/control.h"
57#include "bauhaus/bauhaus.h"
58#include "dtgtk/button.h"
61
62#include "gui/gtk.h"
63#include "gui/drag_and_drop.h"
64#include "libs/lib.h"
65#include "libs/lib_api.h"
66#include "views/view.h"
67#ifdef GDK_WINDOWING_QUARTZ
68#include "osx/osx.h"
69#endif
70#include <gdk/gdkkeysyms.h>
71#include <math.h>
72
73#define FLOATING_ENTRY_WIDTH DT_PIXEL_APPLY_DPI(150)
74
75DT_MODULE(1)
76
77typedef struct dt_lib_tagging_t
78{
79 char keyword[1024];
80 GtkEntry *entry, *dict_entry;
82 GtkTreeView *attached_view, *dictionary_view;
83 GtkWidget *validate_button, *new_button, *import_button, *export_button;
84 GtkWidget *toggle_tree_button, *toggle_suggestion_button;
85 GtkWidget *attached_view_combo, *sort_combo, *dttags_check; // sidebar attached-list controls
86 GtkListStore *attached_liststore, *dictionary_liststore, *completion_store;
87 GtkTreeStore *attached_treestore, *dictionary_treestore;
88 GtkTreeModelFilter *dictionary_listfilter, *dictionary_treefilter;
89 GHashTable *collection_tags;
92 gboolean tree_flag, suggestion_flag, sort_count_flag, hide_path_flag, dttags_flag;
93 gboolean attached_tree_flag; // sidebar attached view: TRUE = tree, FALSE = list
95 char *last_tag;
96 struct
97 {
98 gchar *tagname;
99 GtkTreePath *path, *lastpath;
100 int expand_timeout, scroll_timeout, last_y;
101 gboolean root, tag_source;
102 } drag;
104
105typedef struct dt_tag_op_t
106{
107 gint tagid;
110 gboolean tree_flag;
112
125
132
133static void _save_last_tag_used(const char *tags, dt_lib_tagging_t *d);
136static void _size_recent_tags_list();
137static gboolean _lib_tagging_tag_redo_accel(GtkAccelGroup *accel_group, GObject *accelerable, guint keyval,
138 GdkModifierType mods, gpointer user_data);
139static gboolean _lib_tagging_tag_show_accel(GtkAccelGroup *accel_group, GObject *accelerable, guint keyval,
140 GdkModifierType mods, gpointer user_data);
141
142const char *name(struct dt_lib_module_t *self)
143{
144 return _("Tags");
145}
146
147const char **views(dt_lib_module_t *self)
148{
149 static const char *v[] = {"lighttable", "map", NULL};
150 return v;
151}
152
154{
156}
157
158static gboolean _is_user_tag(GtkTreeModel *model, GtkTreeIter *iter)
159{
160 char *path;
161 gtk_tree_model_get(model, iter, DT_LIB_TAGGING_COL_PATH, &path, -1);
162 const gboolean user_tag = !g_str_has_prefix(path, "darktable|") || g_str_has_prefix(path, "darktable|style|");
163 dt_free(path);
164 return user_tag;
165}
166
167static void _unselect_all_in_view(GtkTreeView *view)
168{
169 GtkTreeSelection *selection = gtk_tree_view_get_selection(view);
170 gtk_tree_selection_unselect_all(selection);
171}
172
173// MULTIPLE-selection-safe replacement for gtk_tree_selection_get_selected():
174// returns the first selected row. gtk_tree_selection_get_selected() asserts
175// (Gtk-CRITICAL) on GTK_SELECTION_MULTIPLE views, which both our views now are.
176static gboolean _selection_get_first(GtkTreeSelection *selection, GtkTreeModel **model, GtkTreeIter *iter)
177{
178 GList *rows = gtk_tree_selection_get_selected_rows(selection, model);
179 gboolean found = FALSE;
180 if(rows)
181 {
182 found = gtk_tree_model_get_iter(*model, iter, (GtkTreePath *)rows->data);
183 g_list_free_full(rows, (GDestroyNotify)gtk_tree_path_free);
184 }
185 return found;
186}
187
188// kept as a hook for callers that want to refresh after a selection/act-on change;
189// the attach/detach buttons it used to drive are gone (tagging is done from the entry,
190// detaching from per-row icons or the right-click menu)
195
196static void _propagate_sel_to_parents(GtkTreeModel *model, GtkTreeIter *iter)
197{
198 guint sel;
199 GtkTreeIter parent, child = *iter;
200 while(gtk_tree_model_iter_parent(model, &parent, &child))
201 {
202 gtk_tree_model_get(model, &parent, DT_LIB_TAGGING_COL_SEL, &sel, -1);
203 if(sel == DT_TS_NO_IMAGE)
204 gtk_tree_store_set(GTK_TREE_STORE(model), &parent,
206 child = parent;
207 }
208}
209
210static gboolean _set_matching_tag_visibility(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, dt_lib_module_t *self)
211{
213 gboolean visible;
214 gchar *tagname = NULL;
215 gchar *synonyms = NULL;
216 gtk_tree_model_get(model, iter, DT_LIB_TAGGING_COL_PATH, &tagname, DT_LIB_TAGGING_COL_SYNONYM, &synonyms, -1);
217 if(!d->keyword[0])
218 visible = TRUE;
219 else
220 {
221 if(synonyms && synonyms[0]) tagname = dt_util_dstrcat(tagname, ", %s", synonyms);
222 gchar *haystack = g_utf8_strdown(tagname, -1);
223 gchar *needle = g_utf8_strdown(d->keyword, -1);
224 visible = (g_strrstr(haystack, needle) != NULL);
225 dt_free(haystack);
226 dt_free(needle);
227 }
228 if(d->tree_flag)
229 gtk_tree_store_set(GTK_TREE_STORE(model), iter, DT_LIB_TAGGING_COL_VISIBLE, visible, -1);
230 else
231 gtk_list_store_set(GTK_LIST_STORE(model), iter, DT_LIB_TAGGING_COL_VISIBLE, visible, -1);
232 dt_free(tagname);
233 dt_free(synonyms);
234 return FALSE;
235}
236
237static gboolean _tree_reveal_func(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
238{
239 gboolean state;
240 GtkTreeIter parent, child = *iter;
241
242 gtk_tree_model_get(model, iter, DT_LIB_TAGGING_COL_VISIBLE, &state, -1);
243 if(!state) return FALSE;
244
245 while(gtk_tree_model_iter_parent(model, &parent, &child))
246 {
247 gtk_tree_model_get(model, &parent, DT_LIB_TAGGING_COL_VISIBLE, &state, -1);
248 gtk_tree_store_set(GTK_TREE_STORE(model), &parent, DT_LIB_TAGGING_COL_VISIBLE, TRUE, -1);
249 child = parent;
250 }
251 return FALSE;
252}
253
254static void _sort_attached_list(dt_lib_module_t *self, gboolean force)
255{
257 if(d->attached_tree_flag)
258 {
259 // tree view: order by full path so the hierarchy lines up
260 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(d->attached_treestore), DT_TAG_SORT_PATH_ID, GTK_SORT_ASCENDING);
261 return;
262 }
263 if(force && d->sort_count_flag)
264 {
265 // ugly but when sorted by count _tree_tagname_show() is not triggered
266 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(d->attached_liststore), DT_TAG_SORT_NAME_ID, GTK_SORT_ASCENDING);
267 }
268 const gint sort = d->sort_count_flag ? DT_TAG_SORT_COUNT_ID : DT_TAG_SORT_NAME_ID;
269 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(d->attached_liststore), sort, GTK_SORT_ASCENDING);
270}
271
272static void _sort_dictionary_list(dt_lib_module_t *self, gboolean force)
273{
275 if(!d->tree_flag)
276 {
277 if(force && d->sort_count_flag)
278 {
279 // ugly but when sorted by count _tree_tagname_show() is not triggered
280 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(d->dictionary_liststore), DT_TAG_SORT_NAME_ID, GTK_SORT_ASCENDING);
281 }
282 const gint sort = d->sort_count_flag ? DT_TAG_SORT_COUNT_ID : d->hide_path_flag ? DT_TAG_SORT_NAME_ID : DT_TAG_SORT_PATH_ID;
283 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(d->dictionary_liststore), sort, GTK_SORT_ASCENDING);
284 }
285 else
286 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(d->dictionary_treestore), DT_TAG_SORT_PATH_ID, GTK_SORT_ASCENDING);
287}
288
289// find a tag on the tree
290static gboolean _find_tag_iter_tagname(GtkTreeModel *model, GtkTreeIter *iter,
291 const char *tagname, const gboolean needle)
292{
293 gboolean found = FALSE;
294 if(IS_NULL_PTR(tagname)) return found;
295 char *path;
296 do
297 {
298 gtk_tree_model_get(model, iter, DT_LIB_TAGGING_COL_PATH, &path, -1);
299 if(needle)
300 {
301 gchar *haystack = g_utf8_strdown(path, -1);
302 found = g_strstr_len(haystack, strlen(haystack), tagname) != NULL;
303 dt_free(haystack);
304 }
305 else
306 found = !g_strcmp0(tagname, path);
307 dt_free(path);
308 if(found) return found;
309 GtkTreeIter child, parent = *iter;
310 if(gtk_tree_model_iter_children(model, &child, &parent))
311 {
312 found = _find_tag_iter_tagname(model, &child, tagname, needle);
313 if(found)
314 {
315 *iter = child;
316 return found;
317 }
318 }
319 } while(gtk_tree_model_iter_next(model, iter));
320 return found;
321}
322
323static void _show_iter_on_view(GtkTreeView *view, GtkTreeIter *iter, const gboolean select)
324{
325 GtkTreeModel *model = gtk_tree_view_get_model(view);
326 GtkTreePath *path = gtk_tree_model_get_path(model, iter);
327 gtk_tree_view_expand_to_path(view, path);
328 gtk_tree_view_scroll_to_cell(view, path, NULL, TRUE, 0.5, 0.5);
329 gtk_tree_path_free(path);
330 if(select)
331 {
332 GtkTreeSelection *selection = gtk_tree_view_get_selection(view);
333 gtk_tree_selection_select_iter(selection, iter);
334 }
335}
336
337// make the tag visible on view
338static void _show_tag_on_view(GtkTreeView *view, const char *tagname,
339 const gboolean needle, const gboolean select)
340{
341 if(tagname)
342 {
343 char *lt = g_strdup(tagname);
344 char *t = g_strstrip(lt);
345 GtkTreeIter iter;
346 GtkTreeModel *model = gtk_tree_view_get_model(view);
347 if(gtk_tree_model_get_iter_first(model, &iter))
348 {
349 if(_find_tag_iter_tagname(model, &iter, t, needle))
350 _show_iter_on_view(view, &iter, select);
351 }
352 dt_free(lt);
353 }
354}
355
356static void _show_keyword_on_view(GtkTreeView *view, const char *keyword, const gboolean select)
357{
358 gchar *needle = g_utf8_strdown(keyword, -1);
359 _show_tag_on_view(view, needle, TRUE, select);
360 dt_free(needle);
361}
362
363static gboolean _select_previous_user_attached_tag(const int index, GtkTreeView *view)
364{
365 if(index == 0)
366 return FALSE;
367 GtkTreeIter iter;
368 GtkTreeModel *model = gtk_tree_view_get_model(view);
369 gboolean valid = gtk_tree_model_get_iter_first(model, &iter);
370 int i = 0;
371 for(; valid && i < index -1; i++)
372 valid = gtk_tree_model_iter_next(model, &iter);
373 for(; valid; valid = gtk_tree_model_iter_previous(model, &iter))
374 {
375 if(_is_user_tag(model, &iter))
376 {
378 return TRUE;
379 }
380 }
381 return FALSE;
382}
383
384static gboolean _select_next_user_attached_tag(const int index, GtkTreeView *view)
385{
386 GtkTreeIter iter;
387 GtkTreeModel *model = gtk_tree_view_get_model(view);
388 gboolean valid = gtk_tree_model_get_iter_first(model, &iter);
389 int i = 0;
390 for(; valid && i < index; i++)
391 valid = gtk_tree_model_iter_next(model, &iter);
392 for(; valid; valid = gtk_tree_model_iter_next(model, &iter))
393 {
394 if(_is_user_tag(model, &iter))
395 {
397 return TRUE;
398 }
399 }
400 if(index)
402 return FALSE;
403}
404
405static void _init_treeview(dt_lib_module_t *self, const int which)
406{
408 GList *tags = NULL;
409 uint32_t count;
410 GtkTreeIter iter;
411 GtkTreeView *view;
412 GtkTreeModel *store;
413 GtkTreeModel *model;
414 gboolean no_sel = FALSE;
415
416 // are we building a hierarchical tree (split on '|') or a flat list?
417 const gboolean build_tree = which ? d->tree_flag : d->attached_tree_flag;
418
419 if(which == 0) // tags of selected images
420 {
421 const int imgsel = dt_control_get_mouse_over_id();
422 no_sel = imgsel > 0 || dt_selection_get_length(darktable.selection) == 1;
423 count = dt_tag_get_attached(imgsel, &tags, d->dttags_flag ? FALSE : TRUE);
424 view = d->attached_view;
425 // the attached view shows its store directly (no filter), so model == store
426 store = build_tree ? GTK_TREE_MODEL(d->attached_treestore) : GTK_TREE_MODEL(d->attached_liststore);
427 model = store;
428 }
429 else // dictionary_view tags of typed text
430 {
431 if(!d->tree_flag && d->suggestion_flag)
432 count = dt_tag_get_suggestions(&tags);
433 else
434 count = dt_tag_get_with_usage(&tags);
435 view = d->dictionary_view;
436 model = gtk_tree_view_get_model(GTK_TREE_VIEW(view));
437 if(d->tree_flag)
438 store = GTK_TREE_MODEL(d->dictionary_treestore);
439 else
440 store = GTK_TREE_MODEL(d->dictionary_liststore);
441 }
442 g_object_ref(model);
443 gtk_tree_view_set_model(GTK_TREE_VIEW(view), NULL);
444
445 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(store), GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, GTK_SORT_ASCENDING);
446 if(build_tree)
447 {
448 gtk_tree_store_clear(GTK_TREE_STORE(store));
449 {
450 char **last_tokens = NULL;
451 int last_tokens_length = 0;
452 GtkTreeIter last_parent = { 0 };
453 GList *sorted_tags = dt_sort_tag(tags, 0); // ordered by full tag name
454 tags = sorted_tags;
455 for(GList *taglist = tags; taglist; taglist = g_list_next(taglist))
456 {
457 const gchar *tag = ((dt_tag_t *)taglist->data)->tag;
458 if(IS_NULL_PTR(tag)) continue;
459 char **tokens;
460 tokens = g_strsplit(tag, "|", -1);
461 if(tokens)
462 {
463 // find the number of common parts at the beginning of tokens and last_tokens
464 GtkTreeIter parent = last_parent;
465 const int tokens_length = g_strv_length(tokens);
466 int common_length = 0;
467 if(last_tokens)
468 {
469 while(tokens[common_length] && last_tokens[common_length] &&
470 !g_strcmp0(tokens[common_length], last_tokens[common_length]))
471 {
472 common_length++;
473 }
474
475 // point parent iter to where the entries should be added
476 for(int i = common_length; i < last_tokens_length; i++)
477 {
478 gtk_tree_model_iter_parent(GTK_TREE_MODEL(store), &parent, &last_parent);
479 last_parent = parent;
480 }
481 }
482
483 // insert everything from tokens past the common part
484 char *pth = NULL;
485 for(int i = 0; i < common_length; i++)
486 pth = dt_util_dstrcat(pth, "%s|", tokens[i]);
487
488 for(char **token = &tokens[common_length]; *token; token++)
489 {
490 pth = dt_util_dstrcat(pth, "%s|", *token);
491 gchar *pth2 = g_strdup(pth);
492 pth2[strlen(pth2) - 1] = '\0';
493 gtk_tree_store_insert(GTK_TREE_STORE(store), &iter, common_length > 0 ? &parent : NULL, -1);
494 gtk_tree_store_set(GTK_TREE_STORE(store), &iter,
496 DT_LIB_TAGGING_COL_ID, (token == &tokens[tokens_length-1]) ?
497 ((dt_tag_t *)taglist->data)->id : 0,
499 DT_LIB_TAGGING_COL_COUNT, (token == &tokens[tokens_length-1]) ?
500 ((dt_tag_t *)taglist->data)->count : 0,
501 DT_LIB_TAGGING_COL_SEL, ((dt_tag_t *)taglist->data)->select,
502 DT_LIB_TAGGING_COL_FLAGS, ((dt_tag_t *)taglist->data)->flags,
503 DT_LIB_TAGGING_COL_SYNONYM, ((dt_tag_t *)taglist->data)->synonym,
505 -1);
506 if(((dt_tag_t *)taglist->data)->select)
507 _propagate_sel_to_parents(GTK_TREE_MODEL(store), &iter);
508 common_length++;
509 parent = iter;
510 dt_free(pth2);
511 }
512 dt_free(pth);
513
514 // remember things for the next round
515 if(last_tokens) g_strfreev(last_tokens);
516 last_tokens = tokens;
517 last_parent = parent;
518 last_tokens_length = tokens_length;
519 }
520 }
521 g_strfreev(last_tokens);
522 }
523 if(which && d->keyword[0]) // keyword filtering only applies to the dictionary
524 {
525 gtk_tree_model_foreach(store, (GtkTreeModelForeachFunc)_set_matching_tag_visibility, self);
526 gtk_tree_model_foreach(store, (GtkTreeModelForeachFunc)_tree_reveal_func, NULL);
527 gtk_tree_view_set_model(GTK_TREE_VIEW(view), model);
528 }
529 else gtk_tree_view_set_model(GTK_TREE_VIEW(view), model);
530 g_object_unref(model);
531 }
532 else
533 {
534 gtk_list_store_clear(GTK_LIST_STORE(store));
535 if(count > 0 && tags)
536 {
537 for (GList *tag = tags; tag; tag = g_list_next(tag))
538 {
539 const char *subtag = g_strrstr(((dt_tag_t *)tag->data)->tag, "|");
540 gtk_list_store_append(GTK_LIST_STORE(store), &iter);
541 gtk_list_store_set(GTK_LIST_STORE(store), &iter,
542 DT_LIB_TAGGING_COL_TAG, !subtag ? ((dt_tag_t *)tag->data)->tag : subtag + 1,
543 DT_LIB_TAGGING_COL_ID, ((dt_tag_t *)tag->data)->id,
544 DT_LIB_TAGGING_COL_PATH, ((dt_tag_t *)tag->data)->tag,
545 DT_LIB_TAGGING_COL_COUNT, ((dt_tag_t *)tag->data)->count,
547 ((dt_tag_t *)tag->data)->select,
548 DT_LIB_TAGGING_COL_FLAGS, ((dt_tag_t *)tag->data)->flags,
549 DT_LIB_TAGGING_COL_SYNONYM, ((dt_tag_t *)tag->data)->synonym,
551 -1);
552 }
553 }
554 if(which && d->keyword[0])
555 {
556 gtk_tree_model_foreach(store, (GtkTreeModelForeachFunc)_set_matching_tag_visibility, self);
557 }
558 gtk_tree_view_set_model(GTK_TREE_VIEW(view), model);
559 g_object_unref(model);
560 }
561 if(which)
563 else
565 // the attached tree is meant to display the tags, not to be browsed, so reveal everything
566 if(!which && d->attached_tree_flag)
567 gtk_tree_view_expand_all(GTK_TREE_VIEW(d->attached_view));
568 // Free result...
569 dt_tag_free_result(&tags);
570}
571
572static void _tree_tagname_show(GtkTreeViewColumn *col, GtkCellRenderer *renderer,
573 GtkTreeModel *model, GtkTreeIter *iter,
574 gpointer data, gboolean dictionary_view)
575{
576 dt_lib_module_t *self = (dt_lib_module_t *)data;
578 guint tagid;
579 gchar *name;
580 gchar *path;
581 guint count;
582 gchar *coltext;
583 gint flags;
584
585 gtk_tree_model_get(model, iter,
586 DT_LIB_TAGGING_COL_ID, &tagid,
590 DT_LIB_TAGGING_COL_PATH, &path, -1);
591 // tree view shows the leaf token only; list view shows the whole "a|b|c" path
592 const gboolean hide = dictionary_view ? (d->tree_flag ? TRUE : d->hide_path_flag) : d->attached_tree_flag;
593 const gboolean istag = !(flags & DT_TF_CATEGORY) && tagid;
594 if((dictionary_view && !count) || (!dictionary_view && count <= 1))
595 {
596 coltext = g_markup_printf_escaped(istag ? "%s" : "<i>%s</i>", hide ? name : path);
597 }
598 else
599 {
600 coltext = g_markup_printf_escaped(istag ? "%s (%d)" : "<i>%s</i> (%d)", hide ? name : path, count);
601 }
602 g_object_set(renderer, "markup", coltext, NULL);
603 dt_free(coltext);
604 dt_free(name);
605 dt_free(path);
606}
607
608static void _tree_tagname_show_attached(GtkTreeViewColumn *col, GtkCellRenderer *renderer,
609 GtkTreeModel *model, GtkTreeIter *iter,
610 gpointer data)
611{
612 _tree_tagname_show(col, renderer, model, iter, data, 0);
613}
614
615static void _tree_tagname_show_dictionary(GtkTreeViewColumn *col, GtkCellRenderer *renderer,
616 GtkTreeModel *model, GtkTreeIter *iter,
617 gpointer data)
618{
619 _tree_tagname_show(col, renderer, model, iter, data, 1);
620}
621
622
624{
625 _init_treeview(self, 0);
627}
628
629static void _lib_tagging_redraw_callback(gpointer instance, dt_lib_module_t *self)
630{
631 //if(dt_lib_gui_get_expanded(self)) // segfaults internally, for some reason
633}
634
635static void _lib_tagging_tags_changed_callback(gpointer instance, dt_lib_module_t *self)
636{
637 _init_treeview(self, 0);
638 _init_treeview(self, 1);
639 // the set of known tags may have changed (created / deleted / renamed)
642}
643
644static void _collection_updated_callback(gpointer instance, dt_collection_change_t query_change,
645 dt_collection_properties_t changed_property, gpointer imgs, int next,
646 dt_lib_module_t *self)
647{
649 d->collection[0] = '\0';
650 // the tags suggested on an empty entry follow the current collection
653}
654
656{
658 // when collection is on tag any attach & detach becomes very slow
659 // speeding up when jumping from tag collection to the other
660 // the cost is that tag collection doesn't reflect the tag changes real time
661 if(!d->collection[0])
662 {
663 // raises change only for other modules
669 }
670}
671
672// find a tag on the tree
673static gboolean _find_tag_iter_tagid(GtkTreeModel *model, GtkTreeIter *iter, const gint tagid)
674{
675 gint tag;
676 do
677 {
678 gtk_tree_model_get(model, iter, DT_LIB_TAGGING_COL_ID, &tag, -1);
679 if(tag == tagid)
680 {
681 return TRUE;
682 }
683 GtkTreeIter child, parent = *iter;
684 if(gtk_tree_model_iter_children(model, &child, &parent))
685 if(_find_tag_iter_tagid(model, &child, tagid))
686 {
687 *iter = child;
688 return TRUE;
689 }
690 } while (gtk_tree_model_iter_next(model, iter));
691 return FALSE;
692}
693
694// calculate the indeterminated state (1) where needed on the tree
695static void _calculate_sel_on_path(GtkTreeModel *model, GtkTreeIter *iter, const gboolean root)
696{
697 GtkTreeIter child, parent = *iter;
698 do
699 {
700 gint sel = DT_TS_NO_IMAGE;
701 gtk_tree_model_get(model, &parent, DT_LIB_TAGGING_COL_SEL, &sel, -1);
702 if(sel == DT_TS_ALL_IMAGES)
703 {
705 }
706 if(gtk_tree_model_iter_children(model, &child, &parent))
708 } while (!root && gtk_tree_model_iter_next(model, &parent));
709}
710
711// reset the indeterminated selection (1) on the tree
712static void _reset_sel_on_path(GtkTreeModel *model, GtkTreeIter *iter, const gboolean root)
713{
714 GtkTreeIter child, parent = *iter;
715 do
716 {
717 if(gtk_tree_model_iter_children(model, &child, &parent))
718 {
719 gint sel = DT_TS_NO_IMAGE;
720 gtk_tree_model_get(model, &parent, DT_LIB_TAGGING_COL_SEL, &sel, -1);
721 if(sel == DT_TS_SOME_IMAGES)
722 {
723 gtk_tree_store_set(GTK_TREE_STORE(model), &parent,
725 }
727 }
728 } while (!root && gtk_tree_model_iter_next(model, &parent));
729}
730
731// reset all selection (1 & 2) on the tree
732static void _reset_sel_on_path_full(GtkTreeModel *model, GtkTreeIter *iter, const gboolean root)
733{
734 GtkTreeIter child, parent = *iter;
735 do
736 {
737 if(GTK_IS_TREE_STORE(model))
738 {
739 gtk_tree_store_set(GTK_TREE_STORE(model), &parent,
741 if(gtk_tree_model_iter_children(model, &child, &parent))
743 }
744 else
745 {
746 gtk_list_store_set(GTK_LIST_STORE(model), &parent,
748 }
749 } while (!root && gtk_tree_model_iter_next(model, &parent));
750}
751
752// try to find a node fully attached (2) which is the root of the update loop. If not the full tree will be used
753static void _find_root_iter_iter(GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *parent)
754{
755 guint sel;
756 GtkTreeIter child = *iter;
757 while (gtk_tree_model_iter_parent(model, parent, &child))
758 {
759 gtk_tree_model_get(model, parent, DT_LIB_TAGGING_COL_SEL, &sel, -1);
760 if(sel == DT_TS_ALL_IMAGES)
761 {
762 char *path = NULL;
763 gtk_tree_model_get(model, parent, DT_LIB_TAGGING_COL_PATH, &path, -1);
764 dt_free(path);
765 return; // no need to go further
766 }
767 child = *parent;
768 }
769 *parent = child; // last before root
770 char *path = NULL;
771 gtk_tree_model_get(model, parent, DT_LIB_TAGGING_COL_PATH, &path, -1);
772 dt_free(path);
773}
774
775// with tag detach update the tree selection
776static void _calculate_sel_on_tree(GtkTreeModel *model, GtkTreeIter *iter)
777{
778 GtkTreeIter parent;
779 if(iter)
780 {
781 // only on sub-tree
782 _find_root_iter_iter(model, iter, &parent);
783 _reset_sel_on_path(model, &parent, TRUE);
785 }
786 else
787 {
788 // on full tree
789 if(gtk_tree_model_get_iter_first(model, &parent))
790 {
791 _reset_sel_on_path(model, &parent, FALSE);
793 }
794 }
795}
796
797// get the new selected images and update the tree selection
798static void _update_sel_on_tree(GtkTreeModel *model)
799{
800 GList *tags = NULL;
801 dt_tag_get_attached(-1, &tags, TRUE);
802 GtkTreeIter parent;
803 if(gtk_tree_model_get_iter_first(model, &parent))
804 {
806 for (GList *tag = tags; tag; tag = g_list_next(tag))
807 {
808 GtkTreeIter iter = parent;
809 if(_find_tag_iter_tagid(model, &iter, ((dt_tag_t *)tag->data)->id))
810 {
811 if(GTK_IS_TREE_STORE(model))
812 {
813 gtk_tree_store_set(GTK_TREE_STORE(model), &iter,
814 DT_LIB_TAGGING_COL_SEL, ((dt_tag_t *)tag->data)->select, -1);
816 }
817 else
818 {
819 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
820 DT_LIB_TAGGING_COL_SEL, ((dt_tag_t *)tag->data)->select, -1);
821 }
822 }
823 }
824 }
825 if(tags)
826 dt_tag_free_result(&tags);
827}
828
829// delete a tag in the tree (tree or list)
830// delete a branch of the tag tree
831static void _delete_tree_path(GtkTreeModel *model, GtkTreeIter *iter, gboolean root, gboolean tree)
832{
833 if(tree) // the treeview is tree. It handles the hierarchy itself (parent / child)
834 {
835 GtkTreeIter child, parent = *iter;
836 gboolean valid = TRUE;
837 do
838 {
839 if(gtk_tree_model_iter_children(model, &child, &parent))
840 _delete_tree_path(model, &child, FALSE, tree);
841 GtkTreeIter tobedel = parent;
842 valid = gtk_tree_model_iter_next(model, &parent);
843 if(root)
844 {
845 gtk_tree_store_set(GTK_TREE_STORE(model), &tobedel,
848
849 char *path2 = NULL;
850 gtk_tree_model_get(model, &tobedel, DT_LIB_TAGGING_COL_PATH, &path2, -1);
851 dt_free(path2);
852
853 _calculate_sel_on_tree(model, &tobedel);
854 }
855 char *path = NULL;
856 gtk_tree_model_get(model, &tobedel, DT_LIB_TAGGING_COL_PATH, &path, -1);
857 dt_free(path);
858 gtk_tree_store_remove(GTK_TREE_STORE(model), &tobedel);
859 } while (!root && valid);
860 }
861 else // treeview is a list. The hierarchy of tags is found with the root (left part) of tagname
862 {
863 GtkTreeIter child;
864 char *path = NULL;
865 gtk_tree_model_get(model, iter, DT_LIB_TAGGING_COL_PATH, &path, -1);
866 guint pathlen = strlen(path);
867 gboolean valid = gtk_tree_model_get_iter_first(model, &child);
868 while(valid)
869 {
870 char *path2 = NULL;
871 gtk_tree_model_get(model, &child, DT_LIB_TAGGING_COL_PATH, &path2, -1);
872 GtkTreeIter tobedel = child;
873 valid = gtk_tree_model_iter_next (model, &child);
874 if(strlen(path2) >= pathlen)
875 {
876 char letter = path2[pathlen];
877 path2[pathlen] = '\0';
878 if(g_strcmp0(path, path2) == 0)
879 {
880 path2[pathlen] = letter;
881 gtk_list_store_remove(GTK_LIST_STORE(model), &tobedel);
882 }
883 }
884 dt_free(path2);
885 }
886 dt_free(path);
887 }
888}
889
890static void _lib_selection_changed_callback(gpointer instance, dt_lib_module_t *self)
891{
893 _init_treeview(self, 0);
894 if(!d->tree_flag && d->suggestion_flag)
895 {
896 _init_treeview(self, 1);
897 }
898 else
899 _update_sel_on_tree(d->tree_flag ? GTK_TREE_MODEL(d->dictionary_treestore)
900 : GTK_TREE_MODEL(d->dictionary_liststore));
901
903}
904
906{
908 const gchar *beg = g_strrstr(gtk_entry_get_text(d->dict_entry), ",");
909
910 if(IS_NULL_PTR(beg))
911 beg = gtk_entry_get_text(d->dict_entry);
912 else
913 {
914 if(*beg == ',') beg++;
915 if(*beg == ' ') beg++;
916 }
917 g_strlcpy(d->keyword, beg, sizeof(d->keyword));
918}
919
920static gboolean _update_tag_name_per_name(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, dt_tag_op_t *to)
921{
922 char *tagname;
923 char *newtagname = to->newtagname;
924 char *oldtagname = to->oldtagname;
925 gtk_tree_model_get(model, iter, DT_LIB_TAGGING_COL_PATH, &tagname, -1);
926 if(g_str_has_prefix(tagname, oldtagname))
927 {
928 if(strlen(tagname) == strlen(oldtagname))
929 {
930 // rename the tag itself
931 if(to->tree_flag)
932 {
933 char *subtag = g_strrstr(to->newtagname, "|");
934 subtag = (!subtag) ? newtagname : subtag + 1;
935 gtk_tree_store_set(GTK_TREE_STORE(model), iter, DT_LIB_TAGGING_COL_PATH,
936 newtagname, DT_LIB_TAGGING_COL_TAG, subtag, -1);
937 }
938 else
939 {
940 gtk_list_store_set(GTK_LIST_STORE(model), iter, DT_LIB_TAGGING_COL_PATH,
941 newtagname, DT_LIB_TAGGING_COL_TAG, newtagname, -1);
942 }
943 }
944 else if(strlen(tagname) > strlen(oldtagname) && tagname[strlen(oldtagname)] == '|')
945 {
946 // rename similar path
947 char *newpath = g_strconcat(newtagname, &tagname[strlen(oldtagname)] , NULL);
948 if(to->tree_flag)
949 {
950 gtk_tree_store_set(GTK_TREE_STORE(model), iter, DT_LIB_TAGGING_COL_PATH,
951 newpath, -1);
952 }
953 else
954 {
955 gtk_list_store_set(GTK_LIST_STORE(model), iter, DT_LIB_TAGGING_COL_PATH,
956 newpath, DT_LIB_TAGGING_COL_TAG, newpath, -1);
957 }
958 dt_free(newpath);
959 }
960 }
961 dt_free(tagname);
962 return FALSE;
963}
964
965static void _update_attached_count(const int tagid, GtkTreeView *view, const gboolean tree_flag)
966{
967 const guint count = dt_tag_images_count(tagid);
968 GtkTreeModel *model = gtk_tree_view_get_model(view);
969 GtkTreeModel *store = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
970 GtkTreeIter iter;
971 if(gtk_tree_model_get_iter_first(store, &iter))
972 {
973 if(_find_tag_iter_tagid(store, &iter, tagid))
974 {
975 if(tree_flag)
976 {
977 gtk_tree_store_set(GTK_TREE_STORE(store), &iter,
980 _calculate_sel_on_tree(GTK_TREE_MODEL(store), &iter);
981 }
982 else
983 {
984 gtk_list_store_set(GTK_LIST_STORE(store), &iter,
987 }
988 }
989 }
990}
991
993{
994
995}
996
998{
999 char *params = NULL;
1000 *size = 0;
1001 GList *tags = NULL;
1002 const guint count = dt_tag_get_attached(-1, &tags, TRUE);
1003
1004 if(count)
1005 {
1006 for(GList *taglist = tags; taglist; taglist = g_list_next(taglist))
1007 {
1008 params = dt_util_dstrcat(params, "%d,", ((dt_tag_t *)taglist->data)->id);
1009 }
1010 dt_tag_free_result(&tags);
1011 if(IS_NULL_PTR(params))
1012 return NULL;
1013 *size = strlen(params);
1014 params[*size-1]='\0';
1015 }
1016 return params;
1017}
1018
1019int set_params(dt_lib_module_t *self, const void *params, int size)
1020{
1021 if(IS_NULL_PTR(params) || !size) return 1;
1023
1024 const char *buf = (char *)params;
1025 if(buf && buf[0])
1026 {
1027 gchar **tokens = g_strsplit(buf, ",", 0);
1028 if(tokens)
1029 {
1030 GList *tags = NULL;
1031 gchar **entry = tokens;
1032 while(*entry)
1033 {
1034 const guint tagid = strtoul(*entry, NULL, 0);
1035 tags = g_list_prepend(tags, GINT_TO_POINTER(tagid));
1036 entry++;
1037 }
1038 g_strfreev(tokens);
1039 GList *imgs = dt_act_on_get_images();
1040 dt_tag_set_tags(tags, imgs, TRUE, FALSE, TRUE);
1041 g_list_free(imgs);
1042 imgs = NULL;
1043 gboolean change = FALSE;
1044 for(GList *tag = tags; tag; tag = g_list_next(tag))
1045 {
1046 _update_attached_count(GPOINTER_TO_INT(tag->data), d->dictionary_view, d->tree_flag);
1047 change = TRUE;
1048 }
1049
1050 if(change)
1051 {
1052 _init_treeview(self, 0);
1055 }
1056 g_list_free(tags);
1057 tags = NULL;
1058 }
1059 }
1060 return 0;
1061}
1062
1063// collect the tag ids of all currently selected rows of view (skips non-tag nodes)
1064static GList *_selected_tagids(GtkTreeView *view)
1065{
1066 GList *ids = NULL;
1067 GtkTreeModel *model = NULL;
1068 GList *rows = gtk_tree_selection_get_selected_rows(gtk_tree_view_get_selection(view), &model);
1069 for(GList *r = rows; r; r = g_list_next(r))
1070 {
1071 GtkTreeIter iter;
1072 if(gtk_tree_model_get_iter(model, &iter, (GtkTreePath *)r->data))
1073 {
1074 guint tagid = 0;
1075 gtk_tree_model_get(model, &iter, DT_LIB_TAGGING_COL_ID, &tagid, -1);
1076 if(tagid > 0) ids = g_list_prepend(ids, GUINT_TO_POINTER(tagid));
1077 }
1078 }
1079 g_list_free_full(rows, (GDestroyNotify)gtk_tree_path_free);
1080 return ids;
1081}
1082
1083// detach the given tag ids from the images to act on, then refresh the views
1084static void _detach_tagids(GList *tagids, dt_lib_module_t *self)
1085{
1086 if(IS_NULL_PTR(tagids)) return;
1087 GList *imgs = dt_act_on_get_images();
1088 if(IS_NULL_PTR(imgs)) return;
1089
1090 gboolean changed = FALSE;
1091 for(GList *t = tagids; t; t = g_list_next(t))
1092 {
1093 const guint tagid = GPOINTER_TO_UINT(t->data);
1094 if(tagid <= 0) continue;
1095 GList *affected = dt_tag_get_images_from_list(imgs, tagid);
1096 if(affected)
1097 {
1098 if(dt_tag_detach_images(tagid, affected, TRUE)) changed = TRUE;
1099 dt_image_synch_xmps(affected);
1100 g_list_free(affected);
1101 }
1102 }
1103 g_list_free(imgs);
1104
1105 _init_treeview(self, 0);
1106 _init_treeview(self, 1);
1109 if(changed) _raise_signal_tag_changed(self);
1110}
1111
1113{
1115 GtkTreeIter iter;
1116 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(d->attached_view));
1117 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(d->attached_view));
1118 if(!_selection_get_first(selection, &model, &iter))
1119 return;
1120 guint tagid;
1121 gtk_tree_model_get(model, &iter, DT_LIB_TAGGING_COL_ID, &tagid, -1);
1122 if(tagid <= 0) return;
1123
1124 // attach tag on images to act on
1125 const gboolean res = dt_tag_attach(tagid, -1, TRUE, TRUE);
1126
1129
1130 _init_treeview(self, 0);
1131
1132 const uint32_t count = dt_tag_images_count(tagid);
1133 model = gtk_tree_view_get_model(GTK_TREE_VIEW(d->dictionary_view));
1134 if(gtk_tree_model_get_iter_first(model, &iter))
1135 {
1136 if(_find_tag_iter_tagid(model, &iter, tagid))
1137 {
1138 GtkTreeIter store_iter;
1139 GtkTreeModel *store = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
1140 gtk_tree_model_filter_convert_iter_to_child_iter(GTK_TREE_MODEL_FILTER(model),
1141 &store_iter, &iter);
1142 if(d->tree_flag)
1143 {
1144 gtk_tree_store_set(GTK_TREE_STORE(store), &store_iter, DT_LIB_TAGGING_COL_COUNT, count, -1);
1145 }
1146 else
1147 {
1148 gtk_list_store_set(GTK_LIST_STORE(store), &store_iter, DT_LIB_TAGGING_COL_COUNT, count, -1);
1149 }
1150 }
1151 }
1152
1153 if(res)
1154 {
1157 }
1158}
1159
1161{
1163 GList *tagids = _selected_tagids(d->attached_view);
1164 _detach_tagids(tagids, self);
1165 g_list_free(tagids);
1166}
1167
1168// detach every (user) tag currently listed in the attached view
1170{
1172 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(d->attached_view));
1173 GtkTreeIter iter;
1174 GList *tagids = NULL;
1175 if(gtk_tree_model_get_iter_first(model, &iter))
1176 {
1177 do
1178 {
1179 if(!_is_user_tag(model, &iter)) continue; // never touch Ansel internal tags
1180 guint tagid = 0;
1181 gtk_tree_model_get(model, &iter, DT_LIB_TAGGING_COL_ID, &tagid, -1);
1182 if(tagid > 0) tagids = g_list_prepend(tagids, GUINT_TO_POINTER(tagid));
1183 } while(gtk_tree_model_iter_next(model, &iter));
1184 }
1185 _detach_tagids(tagids, self);
1186 g_list_free(tagids);
1187}
1188
1189static void _pop_menu_attached(GtkWidget *treeview, GdkEventButton *event, dt_lib_module_t *self)
1190{
1192 GtkWidget *menu, *menuitem;
1193 menu = gtk_menu_new();
1194
1195 GtkTreeIter iter;
1196 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(d->attached_view));
1197 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(d->attached_view));
1198 const gint sel_cnt = gtk_tree_selection_count_selected_rows(selection);
1199
1200 // "attach to all" only makes sense for a single partially-attached tag
1201 if(sel_cnt == 1 && _selection_get_first(selection, &model, &iter))
1202 {
1203 guint sel;
1204 gtk_tree_model_get(model, &iter, DT_LIB_TAGGING_COL_SEL, &sel, -1);
1205 if(sel == DT_TS_SOME_IMAGES)
1206 {
1207 menuitem = gtk_menu_item_new_with_label(_("attach tag to all"));
1208 g_signal_connect(menuitem, "activate", (GCallback)_pop_menu_attached_attach_to_all, self);
1209 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
1210 menuitem = gtk_separator_menu_item_new();
1211 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
1212 }
1213 }
1214
1215 if(sel_cnt > 0)
1216 {
1217 menuitem = gtk_menu_item_new_with_label(sel_cnt > 1 ? _("detach tags") : _("detach tag"));
1218 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
1219 g_signal_connect(menuitem, "activate", (GCallback)_pop_menu_attached_detach, self);
1220 }
1221
1222 menuitem = gtk_menu_item_new_with_label(_("detach all tags"));
1223 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
1224 g_signal_connect(menuitem, "activate", (GCallback)_pop_menu_attached_detach_all, self);
1225
1226 gtk_widget_show_all(GTK_WIDGET(menu));
1227
1228 gtk_menu_popup_at_pointer(GTK_MENU(menu), (GdkEvent *)event);
1229}
1230
1231// detach the single tag of the row whose per-row "delete" icon was clicked
1232static gboolean _attached_delete_icon_activated(GtkCellRenderer *cell, GdkEvent *event, GtkWidget *treeview,
1233 const gchar *path_str, GdkRectangle *background,
1234 GdkRectangle *cell_area, GtkCellRendererState flags,
1235 gpointer user_data)
1236{
1237 dt_lib_module_t *self = (dt_lib_module_t *)user_data;
1238 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(treeview));
1239 GtkTreePath *path = gtk_tree_path_new_from_string(path_str);
1240 GtkTreeIter iter;
1241 if(path && gtk_tree_model_get_iter(model, &iter, path))
1242 {
1243 guint tagid = 0;
1244 gtk_tree_model_get(model, &iter, DT_LIB_TAGGING_COL_ID, &tagid, -1);
1245 if(tagid > 0)
1246 {
1247 GList *one = g_list_prepend(NULL, GUINT_TO_POINTER(tagid));
1248 _detach_tagids(one, self);
1249 g_list_free(one);
1250 }
1251 }
1252 if(path) gtk_tree_path_free(path);
1253 return TRUE;
1254}
1255
1256// only real user tags get a per-row delete icon (not Ansel internal tags,
1257// nor the free/parent nodes that appear in tree view, which have no tag id)
1258static void _attached_delete_icon_show(GtkTreeViewColumn *col, GtkCellRenderer *renderer,
1259 GtkTreeModel *model, GtkTreeIter *iter, gpointer data)
1260{
1261 guint tagid = 0;
1262 gtk_tree_model_get(model, iter, DT_LIB_TAGGING_COL_ID, &tagid, -1);
1263 g_object_set(renderer, "icon-name", "edit-delete-symbolic",
1264 "visible", (tagid > 0) && _is_user_tag(model, iter), NULL);
1265}
1266
1267static gboolean _click_on_view_attached(GtkWidget *view, GdkEventButton *event, dt_lib_module_t *self)
1268{
1270 _unselect_all_in_view(d->dictionary_view);
1271
1272 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
1273 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(d->attached_view));
1274 GtkTreePath *path = NULL;
1275 const gboolean on_row =
1276 gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(view), (gint)event->x, (gint)event->y, &path, NULL, NULL, NULL);
1277
1278 gboolean valid_tag = FALSE;
1279 GtkTreeIter iter;
1280 if(on_row && gtk_tree_model_get_iter(model, &iter, path))
1281 valid_tag = _is_user_tag(model, &iter); // not an Ansel internal tag
1282
1283 // right-click: always offer the menu, even on empty space (so "detach all tags" is reachable)
1284 if(event->type == GDK_BUTTON_PRESS && event->button == 3)
1285 {
1286 if(valid_tag)
1287 {
1288 // keep an existing multi-selection so the menu can act on it in bulk
1289 if(!gtk_tree_selection_path_is_selected(selection, path))
1290 {
1291 gtk_tree_selection_unselect_all(selection);
1292 gtk_tree_selection_select_path(selection, path);
1293 }
1294 }
1295 else
1296 gtk_tree_selection_unselect_all(selection); // clicked empty space / an Ansel tag
1297 _pop_menu_attached(view, event, self);
1298 if(path) gtk_tree_path_free(path);
1299 return TRUE;
1300 }
1301
1302 // double-click on a user tag: detach that single tag
1303 if(event->type == GDK_2BUTTON_PRESS && event->button == 1 && valid_tag)
1304 {
1305 guint tagid = 0;
1306 gtk_tree_model_get(model, &iter, DT_LIB_TAGGING_COL_ID, &tagid, -1);
1307 if(tagid > 0)
1308 {
1309 GList *one = g_list_prepend(NULL, GUINT_TO_POINTER(tagid));
1310 _detach_tagids(one, self);
1311 g_list_free(one);
1312 }
1313 if(path) gtk_tree_path_free(path);
1314 return TRUE;
1315 }
1316
1317 // single click on an Ansel internal tag: prevent selecting it
1318 if(event->type == GDK_BUTTON_PRESS && event->button == 1 && on_row && !valid_tag)
1319 {
1320 gtk_tree_path_free(path);
1321 return TRUE;
1322 }
1323
1324 if(path) gtk_tree_path_free(path);
1325 return FALSE;
1326}
1327
1328static gboolean _attached_key_pressed(GtkWidget *view, GdkEventKey *event, dt_lib_module_t *self)
1329{
1331 _unselect_all_in_view(d->dictionary_view);
1332
1333 guint key = dt_keys_mainpad_alternatives(event->keyval);
1334 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
1335 if(key == GDK_KEY_Delete && gtk_tree_selection_count_selected_rows(selection) > 0)
1336 {
1337 GList *tagids = _selected_tagids(GTK_TREE_VIEW(view));
1338 _detach_tagids(tagids, self);
1339 g_list_free(tagids);
1340 return TRUE;
1341 }
1342 if(key == GDK_KEY_Tab)
1343 {
1344 gtk_tree_selection_unselect_all(selection);
1345 gtk_widget_grab_focus(GTK_WIDGET(d->entry));
1346 return TRUE;
1347 }
1348 else if(key == GDK_KEY_ISO_Left_Tab)
1349 {
1350 gtk_tree_selection_unselect_all(selection);
1351 return TRUE;
1352 }
1353 return FALSE;
1354}
1355
1356// create the tag typed in src and attach it to the current selection
1357static void _create_tag_from_entry(dt_lib_module_t *self, GtkEntry *src)
1358{
1360 const gchar *tag = gtk_entry_get_text(src);
1361 if(IS_NULL_PTR(tag) || tag[0] == '\0') return;
1362
1364 const gboolean res = dt_tag_attach_string_list(tag, imgs, TRUE);
1365 if(res) dt_image_synch_xmps(imgs);
1366 g_list_free(imgs);
1367 imgs = NULL;
1368
1370 _save_last_tag_used(tag, d);
1371
1373 gtk_entry_set_text(src, "");
1374
1375 _init_treeview(self, 0);
1376 _init_treeview(self, 1);
1377 // _raise_signal_tag_changed() may block the tags-changed callback, so refresh
1378 // the autocompletion explicitly to pick up the freshly created tag
1381 char *tagname = strrchr(d->last_tag, ',');
1382 if(res) _raise_signal_tag_changed(self);
1383 _show_tag_on_view(GTK_TREE_VIEW(d->dictionary_view),
1384 tagname ? tagname + 1 : d->last_tag, FALSE, TRUE);
1385}
1386
1387// "new" button in the management popup: create from its search entry
1388static void _new_button_clicked(GtkButton *button, dt_lib_module_t *self)
1389{
1391 _create_tag_from_entry(self, d->dict_entry);
1392}
1393
1394static gboolean _enter_key_pressed(GtkWidget *entry, GdkEventKey *event, dt_lib_module_t *self)
1395{
1397 guint key = dt_keys_mainpad_alternatives(event->keyval);
1398 switch(key)
1399 {
1400 case GDK_KEY_Return:
1401 _create_tag_from_entry(self, d->entry);
1402 break;
1403 case GDK_KEY_Escape:
1404 gtk_window_set_focus(GTK_WINDOW(dt_ui_main_window(darktable.gui->ui)), NULL);
1405 break;
1406 case GDK_KEY_ISO_Left_Tab:
1407 {
1408 if(_select_next_user_attached_tag(0, d->attached_view))
1409 {
1410 gtk_entry_set_text(GTK_ENTRY(entry), "");
1411 gtk_widget_grab_focus(GTK_WIDGET(d->attached_view));
1412 }
1413 return TRUE;
1414 }
1415 default:
1416 break;
1417 }
1418 return FALSE;
1419}
1420
1421
1422static void _tag_name_changed(GtkEntry *entry, dt_lib_module_t *self)
1423{
1425 _set_keyword(self);
1426 GtkTreeModel *model = gtk_tree_view_get_model(d->dictionary_view);
1427 GtkTreeModel *store = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
1428 gtk_tree_model_foreach(store, (GtkTreeModelForeachFunc)_set_matching_tag_visibility, self);
1429 if(d->tree_flag && d->keyword[0])
1430 {
1431 gtk_tree_model_foreach(store, (GtkTreeModelForeachFunc)_tree_reveal_func, NULL);
1432 _show_keyword_on_view(d->dictionary_view, d->keyword, FALSE);
1433 }
1434}
1435
1437{
1439
1440 int res = GTK_RESPONSE_YES;
1441
1442 char *tagname;
1443 gint tagid;
1444 gchar *text;
1445 GtkWidget *label;
1446 GtkTreeIter iter;
1447 GtkTreeModel *model = NULL;
1448 GtkTreeView *view = d->dictionary_view;
1449 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
1450 if(!_selection_get_first(selection, &model, &iter)) return;
1451
1452 gtk_tree_model_get(model, &iter, DT_LIB_TAGGING_COL_PATH, &tagname,
1453 DT_LIB_TAGGING_COL_ID, &tagid, -1);
1454
1455 gint tag_count = 0;
1456 gint img_count = 0;
1457 dt_tag_count_tags_images(tagname, &tag_count, &img_count);
1458 if(tag_count == 0) return;
1459
1461 GtkWidget *dialog = gtk_dialog_new_with_buttons( _("delete node?"), GTK_WINDOW(win), GTK_DIALOG_DESTROY_WITH_PARENT,
1462 _("cancel"), GTK_RESPONSE_NONE, _("delete"), GTK_RESPONSE_YES, NULL);
1463 gtk_window_set_default_size(GTK_WINDOW(dialog), 300, -1);
1464 GtkWidget *area = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
1465 GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
1466 gtk_container_set_border_width(GTK_CONTAINER(vbox), 8);
1467 gtk_container_add(GTK_CONTAINER(area), vbox);
1468 text = g_strdup_printf(_("selected: %s"), tagname);
1469 label = gtk_label_new(text);
1470 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, TRUE, 0);
1471 dt_free(text);
1472
1473 GtkWidget *box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
1474 gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, TRUE, 0);
1475 text = g_strdup_printf(ngettext("<u>%d</u> tag will be deleted", "<u>%d</u> tags will be deleted", tag_count), tag_count);
1476 label = gtk_label_new(NULL);
1477 gtk_label_set_markup(GTK_LABEL(label), text);
1478 gtk_box_pack_start(GTK_BOX(box), label, FALSE, TRUE, 0);
1479 dt_free(text);
1480 text = g_strdup_printf(ngettext("<u>%d</u> image will be updated", "<u>%d</u> images will be updated", img_count), img_count);
1481 label = gtk_label_new(NULL);
1482 gtk_label_set_markup(GTK_LABEL(label), text);
1483 gtk_box_pack_start(GTK_BOX(box), label, FALSE, TRUE, 0);
1484 dt_free(text);
1485
1486#ifdef GDK_WINDOWING_QUARTZ
1488#endif
1489 gtk_widget_show_all(dialog);
1490
1491 res = gtk_dialog_run(GTK_DIALOG(dialog));
1492 gtk_widget_destroy(dialog);
1493 if(res != GTK_RESPONSE_YES)
1494 {
1495 dt_free(tagname);
1496 return;
1497 }
1498
1499 GList *tag_family = NULL;
1500 GList *tagged_images = NULL;
1501 dt_tag_get_tags_images(tagname, &tag_family, &tagged_images);
1502
1504 tag_count = dt_tag_remove_list(tag_family);
1506 dt_control_log(_("%d tags removed"), tag_count);
1507
1508 GtkTreeIter store_iter;
1509 GtkTreeModel *store = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
1510 gtk_tree_model_filter_convert_iter_to_child_iter(GTK_TREE_MODEL_FILTER(model),
1511 &store_iter, &iter);
1512 _delete_tree_path(GTK_TREE_MODEL(store), &store_iter, TRUE, d->tree_flag);
1513 _init_treeview(self, 0);
1514
1515 dt_tag_free_result(&tag_family);
1516 dt_image_synch_xmps(tagged_images);
1517 g_list_free(tagged_images);
1518 tagged_images = NULL;
1520 dt_free(tagname);
1521}
1522
1523// create tag allows the user to create a single tag, which can be an element of the hierarchy or not
1525{
1527
1528 char *tagname;
1529 char *path;
1530 gint tagid;
1531 gchar *text;
1532 GtkWidget *label;
1533 GtkTreeIter iter;
1534 GtkTreeModel *model = NULL;
1535 GtkTreeView *view = d->dictionary_view;
1536 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
1537 if(!_selection_get_first(selection, &model, &iter)) return;
1538
1539 gtk_tree_model_get(model, &iter, DT_LIB_TAGGING_COL_TAG, &tagname,
1541
1543 GtkWidget *dialog = gtk_dialog_new_with_buttons(_("create tag"), GTK_WINDOW(win), GTK_DIALOG_DESTROY_WITH_PARENT,
1544 _("cancel"), GTK_RESPONSE_NONE, _("save"), GTK_RESPONSE_YES, NULL);
1545 gtk_window_set_default_size(GTK_WINDOW(dialog), 300, -1);
1546 GtkWidget *area = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
1547 GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
1548 gtk_container_set_border_width(GTK_CONTAINER(vbox), 8);
1549 gtk_container_add(GTK_CONTAINER(area), vbox);
1550
1551 GtkWidget *box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
1552 gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, TRUE, 0);
1553 label = gtk_label_new(_("name: "));
1554 gtk_box_pack_start(GTK_BOX(box), label, FALSE, TRUE, 0);
1555 GtkWidget *entry = gtk_entry_new();
1557 gtk_box_pack_end(GTK_BOX(box), entry, TRUE, TRUE, 0);
1558
1559 GtkWidget *category;
1560 GtkWidget *private;
1561 GtkWidget *parent;
1562 GtkTextBuffer *buffer = NULL;
1563 GtkWidget *vbox2 = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
1564 gtk_box_pack_start(GTK_BOX(vbox), vbox2, FALSE, TRUE, 0);
1565
1566 text = g_strdup_printf(_("add to: \"%s\" "), path);
1567 parent = gtk_check_button_new_with_label(text);
1568 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(parent), TRUE);
1569 gtk_box_pack_end(GTK_BOX(vbox2), parent, FALSE, TRUE, 0);
1570 dt_free(text);
1571
1572 category = gtk_check_button_new_with_label(_("category"));
1573 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(category), FALSE);
1574 gtk_box_pack_end(GTK_BOX(vbox2), category, FALSE, TRUE, 0);
1575 private = gtk_check_button_new_with_label(_("private"));
1576 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(private), FALSE);
1577 gtk_box_pack_end(GTK_BOX(vbox2), private, FALSE, TRUE, 0);
1578
1579 box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
1580 gtk_box_pack_end(GTK_BOX(vbox), box, TRUE, TRUE, 0);
1581 label = gtk_label_new(_("synonyms: "));
1582 gtk_box_pack_start(GTK_BOX(box), label, FALSE, TRUE, 0);
1583 GtkWidget *synonyms = gtk_text_view_new();
1585 dt_gui_textview_set_padding(GTK_TEXT_VIEW(synonyms));
1586 gtk_box_pack_end(GTK_BOX(box), synonyms, TRUE, TRUE, 0);
1587 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(synonyms), GTK_WRAP_WORD);
1588 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(synonyms));
1589
1590#ifdef GDK_WINDOWING_QUARTZ
1592#endif
1593 gtk_widget_show_all(dialog);
1594
1595 if(gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_YES)
1596 {
1597 const char *newtag = gtk_entry_get_text(GTK_ENTRY(entry));
1598 char *message = NULL;
1599 if(!newtag[0])
1600 message = _("empty tag is not allowed, aborting");
1601 char *new_tagname = NULL;
1602 const gboolean root = !gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(parent));
1603 if(!root)
1604 {
1605 new_tagname = g_strdup(path);
1606 new_tagname = dt_util_dstrcat(new_tagname, "|%s", newtag);
1607 }
1608 else new_tagname = g_strdup(newtag);
1609
1610 if(dt_tag_exists(new_tagname, NULL))
1611 message = _("tag name already exists. aborting.");
1612 if(message)
1613 {
1614 GtkWidget *warning_dialog = gtk_message_dialog_new(GTK_WINDOW(dialog), GTK_DIALOG_MODAL,
1615 GTK_MESSAGE_INFO, GTK_BUTTONS_CLOSE, "%s", message);
1616 gtk_dialog_run(GTK_DIALOG(warning_dialog));
1617 gtk_widget_destroy(warning_dialog);
1618 gtk_widget_destroy(dialog);
1619 dt_free(tagname);
1620 return;
1621 }
1622 guint new_tagid = 0;
1623 if(dt_tag_new(new_tagname, &new_tagid))
1624 {
1625 const gint new_flags = ((gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(category)) ? DT_TF_CATEGORY : 0) |
1626 (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(private)) ? DT_TF_PRIVATE : 0));
1627 if(new_tagid) dt_tag_set_flags(new_tagid, new_flags);
1628 GtkTextIter start, end;
1629 gtk_text_buffer_get_start_iter(buffer, &start);
1630 gtk_text_buffer_get_end_iter(buffer, &end);
1631 gchar *new_synonyms_list = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1632 if(new_tagid && new_synonyms_list && new_synonyms_list[0])
1633 dt_tag_set_synonyms(new_tagid, new_synonyms_list);
1634 dt_free(new_synonyms_list);
1635 _init_treeview(self, 1);
1636 _show_tag_on_view(view, new_tagname, FALSE, TRUE);
1637 }
1638 dt_free(new_tagname);
1639 }
1640 _init_treeview(self, 0);
1641 gtk_widget_destroy(dialog);
1642 dt_free(tagname);
1643}
1644
1645// edit tag allows the user to rename a single tag, which can be an element of the hierarchy and change other parameters
1647{
1649
1650 char *tagname;
1651 char *synonyms_list;
1652 gint tagid;
1653 gchar *text;
1654 GtkWidget *label;
1655 GtkTreeIter iter;
1656 GtkTreeModel *model = NULL;
1657 GtkTreeView *view = d->dictionary_view;
1658 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
1659 if(!_selection_get_first(selection, &model, &iter)) return;
1660
1661 gtk_tree_model_get(model, &iter, DT_LIB_TAGGING_COL_PATH, &tagname,
1662 DT_LIB_TAGGING_COL_SYNONYM, &synonyms_list, DT_LIB_TAGGING_COL_ID, &tagid, -1);
1663 char *subtag = g_strrstr(tagname, "|");
1664 if(subtag) subtag = subtag + 1;
1665 gint tag_count;
1666 gint img_count;
1667 dt_tag_count_tags_images(tagname, &tag_count, &img_count);
1668 if(tag_count == 0)
1669 {
1670 dt_free(tagname);
1671 dt_free(synonyms_list);
1672 return;
1673 }
1674
1676 GtkWidget *dialog = gtk_dialog_new_with_buttons(_("edit"), GTK_WINDOW(win), GTK_DIALOG_DESTROY_WITH_PARENT,
1677 _("cancel"), GTK_RESPONSE_NONE, _("save"), GTK_RESPONSE_YES, NULL);
1678 gtk_window_set_default_size(GTK_WINDOW(dialog), 300, -1);
1679 GtkWidget *area = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
1680 GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
1681 gtk_container_set_border_width(GTK_CONTAINER(vbox), 8);
1682 gtk_container_add(GTK_CONTAINER(area), vbox);
1683 text = g_strdup_printf(_("selected: %s"), tagname);
1684 label = gtk_label_new(text);
1685 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, TRUE, 0);
1686 dt_free(text);
1687
1688 GtkWidget *box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
1689 gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, TRUE, 0);
1690 text = g_strdup_printf(ngettext("<u>%d</u> tag will be updated", "<u>%d</u> tags will be updated", tag_count), tag_count);
1691 label = gtk_label_new(NULL);
1692 gtk_label_set_markup(GTK_LABEL(label), text);
1693 gtk_box_pack_start(GTK_BOX(box), label, FALSE, TRUE, 0);
1694 dt_free(text);
1695 text = g_strdup_printf(ngettext("<u>%d</u> image will be updated", "<u>%d</u> images will be updated", img_count), img_count);
1696 label = gtk_label_new(NULL);
1697 gtk_label_set_markup(GTK_LABEL(label), text);
1698 gtk_box_pack_start(GTK_BOX(box), label, FALSE, TRUE, 0);
1699 dt_free(text);
1700
1701 box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
1702 gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, TRUE, 0);
1703 label = gtk_label_new(_("name: "));
1704 gtk_box_pack_start(GTK_BOX(box), label, FALSE, TRUE, 0);
1705 GtkWidget *entry = gtk_entry_new();
1707 gtk_entry_set_text(GTK_ENTRY(entry), subtag ? subtag : tagname);
1708 gtk_box_pack_end(GTK_BOX(box), entry, TRUE, TRUE, 0);
1709
1710 gint flags = 0;
1711 GtkWidget *category = NULL;
1712 GtkWidget *private = gtk_check_button_new_with_label(_("private"));
1713 GtkTextBuffer *buffer = NULL;
1714
1715 if(tagid)
1716 {
1717 GtkWidget *vbox2 = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
1718 gtk_box_pack_start(GTK_BOX(vbox), vbox2, FALSE, TRUE, 0);
1719 flags = dt_tag_get_flags(tagid);
1720 category = gtk_check_button_new_with_label(_("category"));
1721 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(category), flags & DT_TF_CATEGORY);
1722 gtk_box_pack_end(GTK_BOX(vbox2), category, FALSE, TRUE, 0);
1723 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(private), flags & DT_TF_PRIVATE);
1724 gtk_box_pack_end(GTK_BOX(vbox2), private, FALSE, TRUE, 0);
1725
1726 box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
1727 gtk_box_pack_end(GTK_BOX(vbox), box, TRUE, TRUE, 0);
1728 label = gtk_label_new(_("synonyms: "));
1729 gtk_box_pack_start(GTK_BOX(box), label, FALSE, TRUE, 0);
1730 GtkWidget *synonyms = gtk_text_view_new();
1732 dt_gui_textview_set_padding(GTK_TEXT_VIEW(synonyms));
1733 gtk_box_pack_end(GTK_BOX(box), synonyms, TRUE, TRUE, 0);
1734 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(synonyms), GTK_WRAP_WORD);
1735 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(synonyms));
1736 if(synonyms_list) gtk_text_buffer_set_text(buffer, synonyms_list, -1);
1737 }
1738
1739#ifdef GDK_WINDOWING_QUARTZ
1741#endif
1742 gtk_widget_show_all(dialog);
1743
1744 if(gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_YES)
1745 {
1746 const char *newtag = gtk_entry_get_text(GTK_ENTRY(entry));
1747 if(g_strcmp0(newtag, subtag ? subtag : tagname) != 0)
1748 {
1749 // tag name has changed
1750 char *message = NULL;
1751 if(!newtag[0])
1752 message = _("empty tag is not allowed, aborting");
1753 if(strchr(newtag, '|') != 0)
1754 message = _("'|' character is not allowed for renaming tag.\nto modify the hierarchy use rename path instead. Aborting.");
1755 if(message)
1756 {
1757 GtkWidget *warning_dialog = gtk_message_dialog_new(GTK_WINDOW(dialog), GTK_DIALOG_MODAL,
1758 GTK_MESSAGE_INFO, GTK_BUTTONS_CLOSE, "%s", message);
1759 gtk_dialog_run(GTK_DIALOG(warning_dialog));
1760 gtk_widget_destroy(warning_dialog);
1761 gtk_widget_destroy(dialog);
1762 dt_free(tagname);
1763 return;
1764 }
1765
1766 GList *tag_family = NULL;
1767 GList *tagged_images = NULL;
1768 dt_tag_get_tags_images(tagname, &tag_family, &tagged_images);
1769
1770 const int tagname_len = strlen(tagname);
1771 char *new_prefix_tag;
1772 if(subtag)
1773 {
1774 const int subtag_len = strlen(subtag);
1775 const char letter = tagname[tagname_len - subtag_len];
1776 tagname[tagname_len - subtag_len] = '\0';
1777 new_prefix_tag = g_strconcat(tagname, newtag, NULL);
1778 tagname[tagname_len - subtag_len] = letter;
1779 }
1780 else
1781 new_prefix_tag = (char *)newtag;
1782
1783 // check if one of the new tagnames already exists.
1784 gboolean tagname_exists = FALSE;
1785 for (GList *taglist = tag_family; taglist && !tagname_exists; taglist = g_list_next(taglist))
1786 {
1787 char *new_tagname = g_strconcat(new_prefix_tag, &((dt_tag_t *)taglist->data)->tag[tagname_len], NULL);
1788 tagname_exists = dt_tag_exists(new_tagname, NULL);
1789 if(tagname_exists)
1790 {
1791 GtkWidget *warning_dialog = gtk_message_dialog_new(GTK_WINDOW(dialog), GTK_DIALOG_MODAL,
1792 GTK_MESSAGE_INFO, GTK_BUTTONS_CLOSE,
1793 _("at least one new tag name (%s) already exists, aborting"), new_tagname);
1794 gtk_dialog_run(GTK_DIALOG(warning_dialog));
1795 gtk_widget_destroy(warning_dialog);
1796 dt_free(new_tagname);
1797 if(subtag)
1798 {
1799 dt_free(new_prefix_tag);
1800 }
1801 gtk_widget_destroy(dialog);
1802 dt_free(tagname);
1803 return;
1804 };
1805 dt_free(new_tagname);
1806 }
1807
1808 // rename related tags
1809 for (GList *taglist = tag_family; taglist; taglist = g_list_next(taglist))
1810 {
1811 char *new_tagname = g_strconcat(new_prefix_tag, &((dt_tag_t *)taglist->data)->tag[tagname_len], NULL);
1812 dt_tag_rename(((dt_tag_t *)taglist->data)->id, new_tagname);
1813 dt_free(new_tagname);
1814 }
1815
1816 // update the store
1817 GtkTreeModel *store = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
1818 dt_tag_op_t *to = g_malloc(sizeof(dt_tag_op_t));
1819 to->tree_flag = d->tree_flag;
1820 to->oldtagname = tagname;
1821 to->newtagname = new_prefix_tag;
1822 gint sort_column;
1823 GtkSortType sort_order;
1824 gtk_tree_sortable_get_sort_column_id(GTK_TREE_SORTABLE(store), &sort_column, &sort_order);
1825 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(store), GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, GTK_SORT_ASCENDING);
1826 gtk_tree_model_foreach(store, (GtkTreeModelForeachFunc)_update_tag_name_per_name, to);
1827 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(store), sort_column, sort_order);
1828 dt_free(to);
1829 if(subtag)
1830 {
1831 dt_free(new_prefix_tag);
1832 }
1833
1835 dt_tag_free_result(&tag_family);
1836 dt_image_synch_xmps(tagged_images);
1837 g_list_free(tagged_images);
1838 tagged_images = NULL;
1839 }
1840
1841 if(tagid)
1842 {
1843 gint new_flags = ((gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(category)) ? DT_TF_CATEGORY : 0) |
1844 (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(private)) ? DT_TF_PRIVATE : 0));
1845 GtkTextIter start, end;
1846 gtk_text_buffer_get_start_iter(buffer, &start);
1847 gtk_text_buffer_get_end_iter(buffer, &end);
1848 gchar *new_synonyms_list = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1849 // refresh iter
1850 _selection_get_first(selection, &model, &iter);
1851 GtkTreeIter store_iter;
1852 GtkTreeModel *store = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
1853 gtk_tree_model_filter_convert_iter_to_child_iter(GTK_TREE_MODEL_FILTER(model),
1854 &store_iter, &iter);
1855 if(new_flags != (flags & (DT_TF_CATEGORY | DT_TF_PRIVATE)))
1856 {
1857 new_flags = (flags & ~(DT_TF_CATEGORY | DT_TF_PRIVATE)) | new_flags;
1858 dt_tag_set_flags(tagid, new_flags);
1859 if(!d->tree_flag)
1860 gtk_list_store_set(GTK_LIST_STORE(store), &store_iter, DT_LIB_TAGGING_COL_FLAGS, new_flags, -1);
1861 else
1862 gtk_tree_store_set(GTK_TREE_STORE(store), &store_iter, DT_LIB_TAGGING_COL_FLAGS, new_flags, -1);
1863 }
1864 if(new_synonyms_list && g_strcmp0(synonyms_list, new_synonyms_list) != 0)
1865 {
1866 dt_tag_set_synonyms(tagid, new_synonyms_list);
1867 if(!d->tree_flag)
1868 gtk_list_store_set(GTK_LIST_STORE(store), &store_iter, DT_LIB_TAGGING_COL_SYNONYM, new_synonyms_list, -1);
1869 else
1870 gtk_tree_store_set(GTK_TREE_STORE(store), &store_iter, DT_LIB_TAGGING_COL_SYNONYM, new_synonyms_list, -1);
1871 }
1872 dt_free(new_synonyms_list);
1873 }
1874 }
1875 _init_treeview(self, 0);
1876 gtk_widget_destroy(dialog);
1877 dt_free(synonyms_list);
1878 dt_free(tagname);
1879}
1880
1881static gboolean _apply_rename_path(GtkWidget *dialog, const char *tagname,
1882 const char *newtag, dt_lib_module_t *self)
1883{
1885
1886 gboolean success = FALSE;
1887 GList *tag_family = NULL;
1888 GList *tagged_images = NULL;
1889 dt_tag_get_tags_images(tagname, &tag_family, &tagged_images);
1890
1891 // check if one of the new tagnames already exists.
1892 const int tagname_len = strlen(tagname);
1893 gboolean tagname_exists = FALSE;
1894 for(GList *taglist = tag_family; taglist && !tagname_exists; taglist = g_list_next(taglist))
1895 {
1896 char *new_tagname = g_strconcat(newtag, &((dt_tag_t *)taglist->data)->tag[tagname_len], NULL);
1897 tagname_exists = dt_tag_exists(new_tagname, NULL);
1898 if(tagname_exists)
1899 {
1900 GtkWidget *win;
1901 if(IS_NULL_PTR(dialog))
1903 else
1904 win = dialog;
1905
1906 GtkWidget *warning_dialog = gtk_message_dialog_new(GTK_WINDOW(win), GTK_DIALOG_MODAL,
1907 GTK_MESSAGE_INFO, GTK_BUTTONS_CLOSE,
1908 _("at least one new tagname (%s) already exists, aborting."), new_tagname);
1909 gtk_dialog_run(GTK_DIALOG(warning_dialog));
1910 gtk_widget_destroy(warning_dialog);
1911 }
1912 dt_free(new_tagname);
1913 }
1914
1915 if(!tagname_exists)
1916 {
1917 for (GList *taglist = tag_family; taglist; taglist = g_list_next(taglist))
1918 {
1919 char *new_tagname = g_strconcat(newtag, &((dt_tag_t *)taglist->data)->tag[tagname_len], NULL);
1920 dt_tag_rename(((dt_tag_t *)taglist->data)->id, new_tagname);
1921 dt_free(new_tagname);
1922 }
1923 _init_treeview(self, 0);
1924 _init_treeview(self, 1);
1925 dt_image_synch_xmps(tagged_images);
1927 _show_tag_on_view(d->dictionary_view, newtag, FALSE, TRUE);
1928 success = TRUE;
1929 }
1930 dt_tag_free_result(&tag_family);
1931 g_list_free(tagged_images);
1932 tagged_images = NULL;
1933
1934 return success;
1935}
1936
1937// rename path allows the user to redefine a hierarchy
1939{
1941
1942 char *tagname;
1943 gint tagid;
1944 gchar *text;
1945 GtkWidget *label;
1946 GtkTreeIter iter;
1947 GtkTreeModel *model = NULL;
1948 GtkTreeView *view = d->dictionary_view;
1949 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
1950 if(!_selection_get_first(selection, &model, &iter)) return;
1951
1952 gtk_tree_model_get(model, &iter, DT_LIB_TAGGING_COL_PATH, &tagname,
1953 DT_LIB_TAGGING_COL_ID, &tagid, -1);
1954
1955 gint tag_count = 0;
1956 gint img_count = 0;
1957 dt_tag_count_tags_images(tagname, &tag_count, &img_count);
1958 if(tag_count == 0) return;
1959
1961 GtkWidget *dialog = gtk_dialog_new_with_buttons(_("change path"), GTK_WINDOW(win), GTK_DIALOG_DESTROY_WITH_PARENT,
1962 _("cancel"), GTK_RESPONSE_NONE, _("save"), GTK_RESPONSE_YES, NULL);
1963 gtk_window_set_default_size(GTK_WINDOW(dialog), 300, -1);
1964 GtkWidget *area = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
1965 GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
1966 gtk_container_set_border_width(GTK_CONTAINER(vbox), 8);
1967 gtk_container_add(GTK_CONTAINER(area), vbox);
1968 text = g_strdup_printf(_("selected: %s"), tagname);
1969 label = gtk_label_new(text);
1970 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, TRUE, 0);
1971 dt_free(text);
1972
1973 GtkWidget *box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
1974 gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, TRUE, 0);
1975 text = g_strdup_printf(ngettext("<u>%d</u> tag will be updated", "<u>%d</u> tags will be updated", tag_count), tag_count);
1976 label = gtk_label_new(NULL);
1977 gtk_label_set_markup(GTK_LABEL(label), text);
1978 gtk_box_pack_start(GTK_BOX(box), label, FALSE, TRUE, 0);
1979 dt_free(text);
1980 text = g_strdup_printf(ngettext("<u>%d</u> image will be updated", "<u>%d</u> images will be updated", img_count), img_count);
1981 label = gtk_label_new(NULL);
1982 gtk_label_set_markup(GTK_LABEL(label), text);
1983 gtk_box_pack_start(GTK_BOX(box), label, FALSE, TRUE, 0);
1984 dt_free(text);
1985
1986 GtkWidget *entry = gtk_entry_new();
1988 gtk_entry_set_text(GTK_ENTRY(entry), tagname);
1989 gtk_box_pack_start(GTK_BOX(vbox), entry, FALSE, TRUE, 0);
1990
1991#ifdef GDK_WINDOWING_QUARTZ
1993#endif
1994 gtk_widget_show_all(dialog);
1995
1996 if(gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_YES)
1997 {
1998 const char *newtag = gtk_entry_get_text(GTK_ENTRY(entry));
1999 if(g_strcmp0(newtag, tagname) == 0)
2000 return; // no change
2001 char *message = NULL;
2002 if(!newtag[0])
2003 message = _("empty tag is not allowed, aborting");
2004 if(strchr(newtag, '|') == &newtag[0] || strchr(newtag, '|') == &newtag[strlen(newtag)-1] || strstr(newtag, "||"))
2005 message = _("'|' misplaced, empty tag is not allowed, aborting");
2006 if(message)
2007 {
2008 GtkWidget *warning_dialog = gtk_message_dialog_new(GTK_WINDOW(dialog), GTK_DIALOG_MODAL,
2009 GTK_MESSAGE_INFO, GTK_BUTTONS_CLOSE, "%s", message);
2010 gtk_dialog_run(GTK_DIALOG(warning_dialog));
2011 gtk_widget_destroy(warning_dialog);
2012 gtk_widget_destroy(dialog);
2013 dt_free(tagname);
2014 return;
2015 }
2016 _apply_rename_path(dialog, tagname, newtag, self);
2017 }
2018 gtk_widget_destroy(dialog);
2019 dt_free(tagname);
2020}
2021
2023{
2025 GtkTreeIter iter;
2026 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(d->dictionary_view));
2027 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(d->dictionary_view));
2028 if(_selection_get_first(selection, &model, &iter))
2029 {
2030 char *path;
2031 guint count;
2032 gtk_tree_model_get(model, &iter, DT_LIB_TAGGING_COL_PATH, &path, DT_LIB_TAGGING_COL_COUNT, &count, -1);
2033 if(count)
2034 {
2035 if(!d->collection[0]) dt_collection_serialize(d->collection, 4096);
2036 gchar *tag_collection = g_strdup_printf("1:0:%d:%s$", DT_COLLECTION_PROP_TAG, path);
2038 dt_collection_deserialize(tag_collection);
2040 dt_free(tag_collection);
2041 }
2042 dt_free(path);
2043 }
2044}
2045
2047{
2049 if(d->collection[0])
2050 {
2052 dt_collection_deserialize(d->collection);
2054 d->collection[0] = '\0';
2055 }
2056}
2057
2059{
2061 GtkTreeIter iter;
2062 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(d->dictionary_view));
2063 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(d->dictionary_view));
2064 if(_selection_get_first(selection, &model, &iter))
2065 {
2066 char *tag;
2067 gtk_tree_model_get(model, &iter, DT_LIB_TAGGING_COL_PATH, &tag, -1);
2068 gtk_entry_set_text(d->dict_entry, tag);
2069 dt_free(tag);
2070 gtk_entry_grab_focus_without_selecting(d->dict_entry);
2071 }
2072}
2073
2074// delete the given tag ids from the database (after a single confirmation), then refresh
2075static void _delete_tagids(GList *tagids, dt_lib_module_t *self)
2076{
2077 if(IS_NULL_PTR(tagids)) return;
2078 const guint nb = g_list_length(tagids);
2079
2080 // dry-run removal (FALSE) to count how many images would lose a tag
2081 guint img_count = 0;
2082 for(GList *t = tagids; t; t = g_list_next(t))
2083 img_count += dt_tag_remove(GPOINTER_TO_UINT(t->data), FALSE);
2084
2085 if(img_count > 0 || dt_conf_get_bool("plugins/lighttable/tagging/ask_before_delete_tag"))
2086 {
2088 GtkWidget *dialog = gtk_dialog_new_with_buttons(_("delete tag?"), GTK_WINDOW(win), GTK_DIALOG_DESTROY_WITH_PARENT,
2089 _("cancel"), GTK_RESPONSE_NONE, _("delete"), GTK_RESPONSE_YES, NULL);
2090 gtk_window_set_default_size(GTK_WINDOW(dialog), 300, -1);
2091 GtkWidget *area = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
2092 gchar *text = g_strdup_printf(ngettext("do you really want to delete %d tag?\n%d image is assigned these tags!",
2093 "do you really want to delete %d tags?\n%d images are assigned these tags!",
2094 img_count), nb, img_count);
2095 GtkWidget *label = gtk_label_new(text);
2096 gtk_container_set_border_width(GTK_CONTAINER(area), 8);
2097 gtk_container_add(GTK_CONTAINER(area), label);
2098 dt_free(text);
2099#ifdef GDK_WINDOWING_QUARTZ
2101#endif
2102 gtk_widget_show_all(dialog);
2103 const int res = gtk_dialog_run(GTK_DIALOG(dialog));
2104 gtk_widget_destroy(dialog);
2105 if(res != GTK_RESPONSE_YES) return;
2106 }
2107
2108 GList *affected = NULL;
2109 for(GList *t = tagids; t; t = g_list_next(t))
2110 {
2111 const guint tagid = GPOINTER_TO_UINT(t->data);
2112 affected = g_list_concat(affected, dt_tag_get_images(tagid));
2113 gchar *name = dt_tag_get_name(tagid);
2114 dt_tag_remove(tagid, TRUE);
2115 if(name)
2116 {
2117 dt_control_log(_("tag %s removed"), name);
2118 dt_free(name);
2119 }
2120 }
2121
2122 _init_treeview(self, 0);
2123 _init_treeview(self, 1);
2126 dt_image_synch_xmps(affected);
2127 g_list_free(affected);
2129}
2130
2132{
2134 GList *tagids = _selected_tagids(d->dictionary_view);
2135 _delete_tagids(tagids, self);
2136 g_list_free(tagids);
2137}
2138
2140{
2142 char *tagname;
2143 guint new_tagid;
2144
2145 GtkTreeIter iter;
2146 GtkTreeModel *model = NULL;
2147 GtkTreeView *view = d->dictionary_view;
2148 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
2149 if(!_selection_get_first(selection, &model, &iter)) return;
2150
2151 gtk_tree_model_get(model, &iter, DT_LIB_TAGGING_COL_PATH, &tagname, -1);
2152
2153 dt_tag_new(tagname, &new_tagid);
2154 dt_control_log(_("tag %s created"), tagname);
2155
2156 _init_treeview(self, 1);
2157 _show_tag_on_view(d->dictionary_view, tagname, FALSE, TRUE);
2158 dt_free(tagname);
2159
2160}
2161
2162static void _pop_menu_dictionary(GtkWidget *treeview, GdkEventButton *event, dt_lib_module_t *self)
2163{
2165 GtkTreeIter iter, child;
2166 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(d->dictionary_view));
2167 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(d->dictionary_view));
2168 const gint sel_cnt = gtk_tree_selection_count_selected_rows(selection);
2169 if(sel_cnt == 0) return;
2170
2171 GtkWidget *menu = gtk_menu_new();
2172 GtkWidget *menuitem;
2173
2174 // with several tags selected, only bulk CRUD makes sense
2175 if(sel_cnt > 1)
2176 {
2177 menuitem = gtk_menu_item_new_with_label(_("delete tags"));
2178 g_signal_connect(menuitem, "activate", (GCallback)_pop_menu_dictionary_delete_selected, self);
2179 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
2180 gtk_widget_show_all(GTK_WIDGET(menu));
2181 gtk_menu_popup_at_pointer(GTK_MENU(menu), (GdkEvent *)event);
2182 return;
2183 }
2184
2185 if(_selection_get_first(selection, &model, &iter))
2186 {
2187 guint tagid, count;
2188 gtk_tree_model_get(model, &iter,
2189 DT_LIB_TAGGING_COL_ID, &tagid,
2190 DT_LIB_TAGGING_COL_COUNT, &count, -1);
2191
2192 if(d->tree_flag || !d->suggestion_flag)
2193 {
2194 menuitem = gtk_menu_item_new_with_label(_("create tag..."));
2195 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
2196 g_signal_connect(menuitem, "activate", (GCallback)_pop_menu_dictionary_create_tag, self);
2197
2198 if(tagid)
2199 {
2200 menuitem = gtk_menu_item_new_with_label(_("delete tag"));
2201 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
2202 g_signal_connect(menuitem, "activate", (GCallback)_pop_menu_dictionary_delete_selected, self);
2203 }
2204
2205 if(gtk_tree_model_iter_children(model, &child, &iter))
2206 {
2207 menuitem = gtk_menu_item_new_with_label(_("delete node"));
2208 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
2209 g_signal_connect(menuitem, "activate", (GCallback)_pop_menu_dictionary_delete_node, self);
2210 }
2211
2212 menuitem = gtk_menu_item_new_with_label(_("edit..."));
2213 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
2214 g_signal_connect(menuitem, "activate", (GCallback)_pop_menu_dictionary_edit_tag, self);
2215
2216 }
2217
2218 if(d->tree_flag)
2219 {
2220 menuitem = gtk_menu_item_new_with_label(_("change path..."));
2221 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
2222 g_signal_connect(menuitem, "activate", (GCallback)_pop_menu_dictionary_change_path, self);
2223 }
2224
2225 if(d->tree_flag && !tagid)
2226 {
2227 menuitem = gtk_separator_menu_item_new();
2228 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
2229
2230 menuitem = gtk_menu_item_new_with_label(_("set as a tag"));
2231 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
2232 g_signal_connect(menuitem, "activate", (GCallback)_pop_menu_dictionary_set_as_tag, self);
2233 }
2234
2235 if(!d->suggestion_flag)
2236 {
2237 menuitem = gtk_separator_menu_item_new();
2238 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
2239 }
2240
2241 menuitem = gtk_menu_item_new_with_label(_("copy to entry"));
2242 g_signal_connect(menuitem, "activate", (GCallback)_pop_menu_dictionary_copy_tag, self);
2243 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
2244
2245 if(d->collection[0])
2246 {
2247 char *collection = g_malloc(4096);
2248 dt_collection_serialize(collection, 4096);
2249 if(g_strcmp0(d->collection, collection) == 0) d->collection[0] = '\0';
2250 dt_free(collection);
2251 }
2252 if(count || d->collection[0])
2253 {
2254 menuitem = gtk_separator_menu_item_new();
2255 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
2256 if(count)
2257 {
2258 menuitem = gtk_menu_item_new_with_label(_("go to tag collection"));
2259 g_signal_connect(menuitem, "activate", (GCallback)_pop_menu_dictionary_goto_tag_collection, self);
2260 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
2261 }
2262 if(d->collection[0])
2263 {
2264 menuitem = gtk_menu_item_new_with_label(_("go back to work"));
2265 g_signal_connect(menuitem, "activate", (GCallback)_pop_menu_dictionary_goto_collection_back, self);
2266 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
2267 }
2268 }
2269 gtk_widget_show_all(GTK_WIDGET(menu));
2270
2271 gtk_menu_popup_at_pointer(GTK_MENU(menu), (GdkEvent *)event);
2272 }
2273}
2274
2275static gboolean _click_on_view_dictionary(GtkWidget *view, GdkEventButton *event, dt_lib_module_t *self)
2276{
2278 _unselect_all_in_view(d->attached_view);
2279
2280 const int button_pressed = (event->type == GDK_BUTTON_PRESS) ? event->button : 0;
2281 const gboolean shift_pressed = dt_modifier_is(event->state, GDK_SHIFT_MASK);
2282 if((button_pressed == 3) // contextual menu
2283 || (d->tree_flag && button_pressed == 1)) // expand & drag
2284 {
2285 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
2286 GtkTreePath *path = NULL;
2287 // Get tree path for row that was clicked
2288 if(gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(view), (gint)event->x, (gint)event->y, &path, NULL, NULL, NULL))
2289 {
2290 if(d->tree_flag && button_pressed == 1 && !shift_pressed)
2291 {
2292 GtkTreeModel *model = gtk_tree_view_get_model(d->dictionary_view);
2293 GtkTreeIter iter;
2294 gtk_tree_model_get_iter(model, &iter, path);
2295 char *tagname;
2296 gtk_tree_model_get(model, &iter,
2297 DT_LIB_TAGGING_COL_PATH, &tagname, -1);
2298 if(d->drag.tagname)
2299 {
2300 dt_free(d->drag.tagname);
2301 }
2302 d->drag.tagname = tagname;
2303 if(d->drag.path) gtk_tree_path_free(d->drag.path);
2304 d->drag.path = path;
2305 if(d->drag.lastpath) gtk_tree_path_free(d->drag.lastpath);
2306 d->drag.lastpath = NULL;
2307 return FALSE;
2308 }
2309 else if(button_pressed == 3)
2310 {
2311 // keep an existing multi-selection so the menu can act on it in bulk
2312 if(!gtk_tree_selection_path_is_selected(selection, path))
2313 {
2314 gtk_tree_selection_unselect_all(selection);
2315 gtk_tree_selection_select_path(selection, path);
2316 }
2317 _pop_menu_dictionary(view, event, self);
2318 gtk_tree_path_free(path);
2319 return TRUE;
2320 }
2321 else if(d->tree_flag && button_pressed == 1 && shift_pressed)
2322 {
2323 gtk_tree_selection_select_path(selection, path);
2324 gtk_tree_view_expand_row(GTK_TREE_VIEW(view), path, TRUE);
2325 gtk_tree_path_free(path);
2326 return TRUE;
2327 }
2328 }
2329 gtk_tree_path_free(path);
2330 }
2331 return FALSE;
2332}
2333
2334static gboolean _dictionary_key_pressed(GtkWidget *view, GdkEventKey *event, dt_lib_module_t *self)
2335{
2337 _unselect_all_in_view(d->attached_view);
2338 GtkTreeIter iter;
2339 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(d->dictionary_view));
2340 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
2341 gboolean res = FALSE;
2342 guint key = dt_keys_mainpad_alternatives(event->keyval);
2343
2344 if(_selection_get_first(selection, &model, &iter))
2345 {
2346 GtkTreePath *path = gtk_tree_model_get_path(model, &iter);
2347 switch(key)
2348 {
2349 case GDK_KEY_Left:
2350 if(path)
2351 {
2352 if(dt_modifier_is(event->state, GDK_SHIFT_MASK))
2353 gtk_tree_view_collapse_all(GTK_TREE_VIEW(view));
2354 else
2355 gtk_tree_view_collapse_row(GTK_TREE_VIEW(view), path);
2356 res = TRUE;
2357 }
2358 break;
2359 case GDK_KEY_Right:
2360 if(path)
2361 {
2362 gtk_tree_view_expand_row(GTK_TREE_VIEW(view), path,
2363 dt_modifier_is(event->state, GDK_SHIFT_MASK));
2364 res = TRUE;
2365 }
2366 break;
2367 default:
2368 break;
2369 }
2370 gtk_tree_path_free(path);
2371 }
2372 if(event->keyval == GDK_KEY_Tab)
2373 {
2374 gtk_tree_selection_unselect_all(selection);
2375 res = TRUE;
2376 }
2377 else if(event->keyval == GDK_KEY_ISO_Left_Tab)
2378 {
2379 gtk_tree_selection_unselect_all(selection);
2380 gtk_widget_grab_focus(GTK_WIDGET(d->dict_entry));
2381 res = TRUE;
2382 }
2383 return res;
2384}
2385
2386static gboolean _row_tooltip_setup(GtkWidget *treeview, gint x, gint y, gboolean kb_mode,
2387 GtkTooltip* tooltip, dt_lib_module_t *self)
2388{
2389 gboolean res = FALSE;
2390 GtkTreePath *path = NULL;
2391 // Get tree path mouse position
2392 if(gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(treeview), x, y, &path, NULL, NULL, NULL))
2393 {
2394 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(treeview));
2395 GtkTreeIter iter;
2396 if(gtk_tree_model_get_iter(model, &iter, path))
2397 {
2398 char *tagname;
2399 guint tagid;
2400 guint flags;
2401 char *synonyms = NULL;
2402 gtk_tree_model_get(model, &iter, DT_LIB_TAGGING_COL_ID, &tagid, DT_LIB_TAGGING_COL_TAG, &tagname,
2404 if(tagid)
2405 {
2406 if((flags & DT_TF_PRIVATE) || (synonyms && synonyms[0]))
2407 {
2408 gchar *text = g_strdup_printf(_("%s"), tagname);
2409 text = dt_util_dstrcat(text, " %s\n", (flags & DT_TF_PRIVATE) ? _("(private)") : "");
2410 text = dt_util_dstrcat(text, "synonyms: %s", (synonyms && synonyms[0]) ? synonyms : " - ");
2411 gtk_tooltip_set_text(tooltip, text);
2412 dt_free(text);
2413 res = TRUE;
2414 }
2415 }
2416 dt_free(synonyms);
2417 dt_free(tagname);
2418 }
2419 }
2420 gtk_tree_path_free(path);
2421
2422 return res;
2423}
2424
2425static void _import_button_clicked(GtkButton *button, dt_lib_module_t *self)
2426{
2427 const char *last_dirname = dt_conf_get_string_const("plugins/lighttable/tagging/last_import_export_location");
2428 if(IS_NULL_PTR(last_dirname) || !*last_dirname)
2429 {
2430 last_dirname = g_get_home_dir();
2431 }
2432
2434 GtkFileChooserNative *filechooser = gtk_file_chooser_native_new(
2435 _("select a keyword file"), GTK_WINDOW(win), GTK_FILE_CHOOSER_ACTION_OPEN,
2436 _("_import"), _("_cancel"));
2437
2438 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(filechooser), last_dirname);
2439 gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(filechooser), FALSE);
2440
2441 if(gtk_native_dialog_run(GTK_NATIVE_DIALOG(filechooser)) == GTK_RESPONSE_ACCEPT)
2442 {
2443 char *filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(filechooser));
2444 char *dirname = g_path_get_dirname(filename);
2445 dt_conf_set_string("plugins/lighttable/tagging/last_import_export_location", dirname);
2446 uint32_t count = dt_tag_import(filename);
2447 dt_control_log(_("%u tags imported"), count);
2448 dt_free(filename);
2449 dt_free(dirname);
2450 }
2451
2452 g_object_unref(filechooser);
2453 _init_treeview(self, 1);
2454}
2455
2456static void _export_button_clicked(GtkButton *button, dt_lib_module_t *self)
2457{
2458 GDateTime *now = g_date_time_new_now_local();
2459 char *export_filename = g_date_time_format(now, "darktable_tags_%F_%H-%M.txt");
2460 const char *last_dirname = dt_conf_get_string_const("plugins/lighttable/tagging/last_import_export_location");
2461 if(IS_NULL_PTR(last_dirname) || !*last_dirname)
2462 {
2463 last_dirname = g_get_home_dir();
2464 }
2465
2467 GtkFileChooserNative *filechooser = gtk_file_chooser_native_new(
2468 _("select file to export to"), GTK_WINDOW(win), GTK_FILE_CHOOSER_ACTION_SAVE,
2469 _("_export"), _("_cancel"));
2470
2471 gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(filechooser), TRUE);
2472 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(filechooser), last_dirname);
2473 gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(filechooser), export_filename);
2474
2475 if(gtk_native_dialog_run(GTK_NATIVE_DIALOG(filechooser)) == GTK_RESPONSE_ACCEPT)
2476 {
2477 char *filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(filechooser));
2478 char *dirname = g_path_get_dirname(filename);
2479 dt_conf_set_string("plugins/lighttable/tagging/last_import_export_location", dirname);
2480 const uint32_t count = dt_tag_export(filename);
2481 dt_control_log(_("%u tags exported"), count);
2482 dt_free(filename);
2483 dt_free(dirname);
2484 }
2485
2486 g_date_time_unref(now);
2487 dt_free(export_filename);
2488 g_object_unref(filechooser);
2489}
2490
2492{
2494 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(d->dictionary_view));
2495
2496 ++darktable.gui->reset;
2497
2498 d->suggestion_flag = dt_conf_get_bool("plugins/lighttable/tagging/nosuggestion");
2499 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->toggle_suggestion_button), d->suggestion_flag);
2500 d->tree_flag = dt_conf_get_bool("plugins/lighttable/tagging/treeview");
2501 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->toggle_tree_button), d->tree_flag);
2502
2503 if(d->tree_flag)
2504 {
2505 if(model == GTK_TREE_MODEL(d->dictionary_listfilter))
2506 {
2507 g_object_ref(model);
2508 gtk_tree_view_set_model(GTK_TREE_VIEW(d->dictionary_view), NULL);
2509 GtkTreeModel *store = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
2510 gtk_list_store_clear(GTK_LIST_STORE(store));
2511 gtk_tree_view_set_model(GTK_TREE_VIEW(d->dictionary_view), GTK_TREE_MODEL(d->dictionary_treefilter));
2512 g_object_unref(d->dictionary_treefilter);
2513 }
2514 gtk_widget_set_sensitive(GTK_WIDGET(d->toggle_suggestion_button), FALSE);
2515 }
2516 else
2517 {
2518 if(model == GTK_TREE_MODEL(d->dictionary_treefilter))
2519 {
2520 g_object_ref(model);
2521 gtk_tree_view_set_model(GTK_TREE_VIEW(d->dictionary_view), NULL);
2522 GtkTreeModel *store = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
2523 gtk_tree_store_clear(GTK_TREE_STORE(store));
2524 gtk_tree_view_set_model(GTK_TREE_VIEW(d->dictionary_view), GTK_TREE_MODEL(d->dictionary_listfilter));
2525 g_object_unref(d->dictionary_listfilter);
2526 }
2527 gtk_widget_set_sensitive(GTK_WIDGET(d->toggle_suggestion_button), TRUE);
2528 }
2529
2530 // drag & drop
2531 if(d->tree_flag)
2532 gtk_drag_source_set(GTK_WIDGET(d->dictionary_view), GDK_BUTTON1_MASK,
2533 target_list_tags, n_targets_tags, GDK_ACTION_MOVE);
2534 else
2535 gtk_drag_source_unset(GTK_WIDGET(d->dictionary_view));
2536
2537 d->sort_count_flag = dt_conf_get_bool("plugins/lighttable/tagging/listsortedbycount");
2538 dt_bauhaus_combobox_set(d->sort_combo, d->sort_count_flag ? 1 : 0);
2539
2540 // controls the dictionary list rendering (leaf vs full path); the sidebar attached
2541 // view has its own tree/list "view" combobox below
2542 d->hide_path_flag = dt_conf_get_bool("plugins/lighttable/tagging/hidehierarchy");
2543
2544 d->attached_tree_flag = dt_conf_get_bool("plugins/lighttable/tagging/attached_treeview");
2545 dt_bauhaus_combobox_set(d->attached_view_combo, d->attached_tree_flag ? 1 : 0);
2546
2547 d->dttags_flag = dt_conf_get_bool("plugins/lighttable/tagging/dttags");
2548 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->dttags_check), d->dttags_flag);
2549
2550 --darktable.gui->reset;
2551}
2552
2553static void _toggle_suggestion_button_callback(GtkToggleButton *source, dt_lib_module_t *self)
2554{
2555 if(darktable.gui->reset) return;
2556 const gboolean new_state = !dt_conf_get_bool("plugins/lighttable/tagging/nosuggestion");
2557 dt_conf_set_bool("plugins/lighttable/tagging/nosuggestion", new_state);
2558 _update_layout(self);
2559 _init_treeview(self, 1);
2560}
2561
2562static void _toggle_tree_button_callback(GtkToggleButton *source, dt_lib_module_t *self)
2563{
2564 if(darktable.gui->reset) return;
2565 const gboolean new_state = !dt_conf_get_bool("plugins/lighttable/tagging/treeview");
2566 dt_conf_set_bool("plugins/lighttable/tagging/treeview", new_state);
2567 _update_layout(self);
2568 _init_treeview(self, 1);
2569}
2570
2571static gint _sort_tree_count_func(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, dt_lib_module_t *self)
2572{
2573 guint count_a = 0;
2574 guint count_b = 0;
2575 gtk_tree_model_get(model, a, DT_LIB_TAGGING_COL_COUNT, &count_a, -1);
2576 gtk_tree_model_get(model, b, DT_LIB_TAGGING_COL_COUNT, &count_b, -1);
2577 return (count_b - count_a);
2578}
2579
2580static gint _sort_tree_tag_func(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, dt_lib_module_t *self)
2581{
2582 char *tag_a = NULL;
2583 char *tag_b = NULL;
2584 gtk_tree_model_get(model, a, DT_LIB_TAGGING_COL_TAG, &tag_a, -1);
2585 gtk_tree_model_get(model, b, DT_LIB_TAGGING_COL_TAG, &tag_b, -1);
2586 if(IS_NULL_PTR(tag_a)) tag_a = g_strdup("");
2587 if(IS_NULL_PTR(tag_b)) tag_b = g_strdup("");
2588 const gboolean sort = g_strcmp0(tag_a, tag_b);
2589 dt_free(tag_a);
2590 dt_free(tag_b);
2591 return sort;
2592}
2593
2594static gint _sort_tree_path_func(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, dt_lib_module_t *self)
2595{
2596 char *tag_a = NULL;
2597 char *tag_b = NULL;
2598 gtk_tree_model_get(model, a, DT_LIB_TAGGING_COL_PATH, &tag_a, -1);
2599 gtk_tree_model_get(model, b, DT_LIB_TAGGING_COL_PATH, &tag_b, -1);
2600 if(tag_a)
2601 {
2602 for(char *letter = tag_a; *letter; letter++)
2603 if(*letter == '|') *letter = '\1';
2604 }
2605 else
2606 tag_a = g_strdup("");
2607
2608 if(tag_b)
2609 {
2610 for(char *letter = tag_b; *letter; letter++)
2611 if(*letter == '|') *letter = '\1';
2612 }
2613 else
2614 tag_b = g_strdup("");
2615
2616 const gboolean sort = g_strcmp0(tag_a, tag_b);
2617 dt_free(tag_a);
2618 dt_free(tag_b);
2619 return sort;
2620}
2621
2622// "sort" combobox in the sidebar: by name (0) or by count (1)
2624{
2625 if(darktable.gui->reset) return;
2626 dt_conf_set_bool("plugins/lighttable/tagging/listsortedbycount", dt_bauhaus_combobox_get(combo) == 1);
2627 _update_layout(self);
2630}
2631
2632// "view" combobox in the sidebar: attached list (0) or attached tree (1)
2634{
2635 if(darktable.gui->reset) return;
2636 dt_conf_set_bool("plugins/lighttable/tagging/attached_treeview", dt_bauhaus_combobox_get(combo) == 1);
2637 _update_layout(self);
2638 _init_treeview(self, 0);
2639}
2640
2641// "show system tags" checkbox in the sidebar
2642static void _dttags_check_toggled(GtkToggleButton *source, dt_lib_module_t *self)
2643{
2644 if(darktable.gui->reset) return;
2646 d->dttags_flag = gtk_toggle_button_get_active(source);
2647 dt_conf_set_bool("plugins/lighttable/tagging/dttags", d->dttags_flag);
2648 _init_treeview(self, 0);
2649}
2650
2652{
2654 // clear entry boxes and query
2655 gtk_entry_set_text(d->entry, "");
2656 gtk_entry_set_text(d->dict_entry, "");
2657 _set_keyword(self);
2658 _init_treeview(self, 1);
2660}
2661
2663{
2664 return 3;
2665}
2666
2667// Single-column model feeding the tag entry autocompletion (full tag path).
2669
2670// (Re)populate the entry autocompletion store with every known tag.
2672{
2674 if(!d->completion_store) return;
2675 gtk_list_store_clear(d->completion_store);
2676 GList *tags = NULL;
2677 dt_tag_get_with_usage(&tags);
2678 for(GList *t = tags; t; t = g_list_next(t))
2679 {
2680 const dt_tag_t *tag = (const dt_tag_t *)t->data;
2681 if(IS_NULL_PTR(tag->tag)) continue;
2682 GtkTreeIter iter;
2683 gtk_list_store_append(d->completion_store, &iter);
2684 gtk_list_store_set(d->completion_store, &iter, DT_COMPL_COL_PATH, tag->tag, -1);
2685 }
2686 dt_tag_free_result(&tags);
2687}
2688
2689// (Re)build the set of tags used by the current collection. Used to seed the
2690// entry autocompletion when nothing has been typed yet.
2692{
2694 if(!d->collection_tags) return;
2695 g_hash_table_remove_all(d->collection_tags);
2696 GList *tags = NULL;
2698 for(GList *t = tags; t; t = g_list_next(t))
2699 {
2700 const dt_tag_t *tag = (const dt_tag_t *)t->data;
2701 if(IS_NULL_PTR(tag->tag)) continue;
2702 g_hash_table_add(d->collection_tags, g_strdup(tag->tag));
2703 }
2704 dt_tag_free_result(&tags);
2705}
2706
2707static gboolean _match_selected_func(GtkEntryCompletion *completion, GtkTreeModel *model, GtkTreeIter *iter, gpointer user_data)
2708{
2709 const int column = gtk_entry_completion_get_text_column(completion);
2710 char *tag = NULL;
2711
2712 if(gtk_tree_model_get_column_type(model, column) != G_TYPE_STRING) return TRUE;
2713
2714 GtkEditable *e = (GtkEditable *)gtk_entry_completion_get_entry(completion);
2715 if(!GTK_IS_EDITABLE(e))
2716 {
2717 return FALSE;
2718 }
2719
2720 gtk_tree_model_get(model, iter, column, &tag, -1);
2721
2722 gint cut_off, cur_pos = gtk_editable_get_position(e);
2723
2724 gchar *currentText = gtk_editable_get_chars(e, 0, -1);
2725 const gchar *lastTag = g_strrstr(currentText, ",");
2726 if(IS_NULL_PTR(lastTag))
2727 {
2728 cut_off = 0;
2729 }
2730 else
2731 {
2732 cut_off = (int)(g_utf8_strlen(currentText, -1) - g_utf8_strlen(lastTag, -1))+1;
2733 }
2734 dt_free(currentText);
2735
2736 gtk_editable_delete_text(e, cut_off, cur_pos);
2737 cur_pos = cut_off;
2738 gtk_editable_insert_text(e, tag, -1, &cur_pos);
2739 gtk_editable_set_position(e, cur_pos);
2740 dt_free(tag); // release result of gtk_tree_model_get
2741 return TRUE;
2742}
2743
2744static gboolean _completion_match_func(GtkEntryCompletion *completion, const gchar *key, GtkTreeIter *iter,
2745 gpointer user_data)
2746{
2747 gboolean res = FALSE;
2748 dt_lib_module_t *self = (dt_lib_module_t *)user_data;
2749 dt_lib_tagging_t *d = self ? (dt_lib_tagging_t *)self->data : NULL;
2750
2751 GtkEditable *e = (GtkEditable *)gtk_entry_completion_get_entry(completion);
2752
2753 if(!GTK_IS_EDITABLE(e))
2754 {
2755 return FALSE;
2756 }
2757
2758 const gint cur_pos = gtk_editable_get_position(e);
2759 const gboolean onLastTag = (g_strstr_len(&key[cur_pos], -1, ",") == NULL);
2760 if(!onLastTag)
2761 {
2762 return FALSE;
2763 }
2764
2765 GtkTreeModel *model = gtk_entry_completion_get_model(completion);
2766 const int column = gtk_entry_completion_get_text_column(completion);
2767
2768 if(gtk_tree_model_get_column_type(model, column) != G_TYPE_STRING)
2769 {
2770 return FALSE;
2771 }
2772
2773 const gchar *lastTag = g_strrstr(key, ",");
2774 if(!IS_NULL_PTR(lastTag))
2775 {
2776 lastTag++;
2777 }
2778 else
2779 {
2780 lastTag = key;
2781 }
2782
2783 char *tag = NULL;
2784 gtk_tree_model_get(model, iter, column, &tag, -1);
2785
2786 // nothing typed yet on the active tag token: default the suggestions to the
2787 // tags used by the current collection opened in lighttable
2788 if(lastTag[0] == '\0')
2789 {
2790 res = (d && d->collection_tags && tag) ? g_hash_table_contains(d->collection_tags, tag) : FALSE;
2791 dt_free(tag);
2792 return res;
2793 }
2794
2795 if(tag)
2796 {
2797 char *normalized = g_utf8_normalize(tag, -1, G_NORMALIZE_ALL);
2798 if(normalized)
2799 {
2800 char *casefold = g_utf8_casefold(normalized, -1);
2801 if(casefold)
2802 {
2803 res = g_strstr_len(casefold, -1, lastTag) != NULL;
2804 }
2805 dt_free(casefold);
2806 }
2807 dt_free(normalized);
2808 dt_free(tag);
2809 }
2810
2811 return res;
2812}
2813
2814static void _tree_selection_changed(GtkTreeSelection *treeselection, gpointer data)
2815{
2817}
2818
2820{
2822 if(!d->drag.root) return;
2823 GtkTreeModel *model = GTK_TREE_MODEL(d->dictionary_treestore);
2824 GtkTreeIter iter;
2825 gtk_tree_model_get_iter_first(model, &iter);
2826 char *name;
2827 gtk_tree_model_get(model, &iter, DT_LIB_TAGGING_COL_PATH, &name, -1);
2828 if(name && name[0] == '\0')
2829 gtk_tree_store_remove(d->dictionary_treestore, &iter);
2830 dt_free(name);
2831 d->drag.root = FALSE;
2832}
2833
2834static void _event_dnd_begin(GtkWidget *widget, GdkDragContext *context, dt_lib_module_t *self)
2835{
2837 GtkTreeView *tree = GTK_TREE_VIEW(widget);
2838
2839 if(d->drag.path)
2840 {
2841 cairo_surface_t *row_pix = gtk_tree_view_create_row_drag_icon(tree, d->drag.path);
2842 // FIXME: try to put the anchor point on left bottom corner as for images
2843// cairo_surface_set_device_offset(row_pix,
2844// // the + 1 is for the black border in the icon
2845// - (double)(cairo_image_surface_get_width(row_pix) + 1),
2846// - (double)(cairo_image_surface_get_height(row_pix)+ 1));
2847 gtk_drag_set_icon_surface(context, row_pix);
2848 cairo_surface_destroy(row_pix);
2849 gtk_tree_path_free(d->drag.path);
2850 d->drag.path = NULL;
2851 d->drag.tag_source = TRUE;
2852 }
2853}
2854
2855static void _event_dnd_get(GtkWidget *widget, GdkDragContext *context, GtkSelectionData *selection_data,
2856 const guint target_type, const guint time, dt_lib_module_t *self)
2857{
2858 if(target_type == DND_TARGET_TAG)
2859 gtk_selection_data_set(selection_data, gtk_selection_data_get_target(selection_data),
2860 _DWORD, NULL, 0);
2861}
2862
2863static void _event_dnd_received(GtkWidget *widget, GdkDragContext *context, gint x, gint y,
2864 GtkSelectionData *selection_data, guint target_type, guint time,
2865 dt_lib_module_t *self)
2866{
2868 GtkTreeView *tree = GTK_TREE_VIEW(widget);
2869 // disable the default handler
2870 g_signal_stop_emission_by_name(tree, "drag-data-received");
2871 gboolean success = FALSE;
2872
2873 if(target_type == DND_TARGET_TAG)
2874 {
2875 GtkTreePath *path = NULL;
2876 if(gtk_tree_view_get_path_at_pos(tree, x, y, &path, NULL, NULL, NULL))
2877 {
2878 char *name;
2879 GtkTreeModel *model = gtk_tree_view_get_model(tree);
2880 GtkTreeIter iter;
2881
2882 gtk_tree_model_get_iter(model, &iter, path);
2883 gtk_tree_model_get(model, &iter, DT_LIB_TAGGING_COL_PATH, &name, -1);
2884 _dnd_clear_root(self);
2885 const gboolean root = (name && name[0] == '\0');
2886 char *leave = g_strrstr(d->drag.tagname, "|");
2887 if(leave) leave++;
2888 name = dt_util_dstrcat(name, "%s%s", root ? "" : "|", leave ? leave : d->drag.tagname);
2889 _apply_rename_path(NULL, d->drag.tagname, name, self);
2890
2891 dt_free(name);
2892 dt_free(d->drag.tagname);
2893 gtk_tree_path_free(path); // release result of gtk_tree_view_get_path_at_pos above
2894 success = TRUE;
2895 }
2896 }
2897 else if((target_type == DND_TARGET_IMGID) && (!IS_NULL_PTR(selection_data)))
2898 {
2899 GtkTreePath *path = NULL;
2900 const int imgs_nb = gtk_selection_data_get_length(selection_data) / sizeof(uint32_t);
2901 if(imgs_nb && gtk_tree_view_get_path_at_pos(tree, x, y, &path, NULL, NULL, NULL))
2902 {
2903 const uint32_t *imgt = (uint32_t *)gtk_selection_data_get_data(selection_data);
2904 GList *imgs = NULL;
2905 for(int i = 0; i < imgs_nb; i++)
2906 {
2907 imgs = g_list_prepend(imgs, GINT_TO_POINTER(imgt[i]));
2908 }
2909 GtkTreeModel *model = gtk_tree_view_get_model(tree);
2910 GtkTreeIter iter;
2911 gtk_tree_model_get_iter(model, &iter, path);
2912 gint tagid;
2913 gtk_tree_model_get(model, &iter, DT_LIB_TAGGING_COL_ID, &tagid, -1);
2914 if(tagid)
2915 dt_tag_attach_images(tagid, imgs, TRUE);
2916 g_list_free(imgs);
2917 imgs = NULL;
2918 _update_attached_count(tagid, d->dictionary_view, d->tree_flag);
2919 _init_treeview(self, 0);
2922 gtk_tree_path_free(path); // release result of gtk_tree_view_get_path_at_pos above
2923 success = TRUE;
2924 }
2925 }
2926 gtk_drag_finish(context, success, FALSE, time);
2927}
2928
2930{
2932 if(d->drag.lastpath)
2933 {
2934 gtk_tree_view_expand_row(d->dictionary_view, d->drag.lastpath, FALSE);
2935 }
2936 return FALSE;
2937}
2938
2940{
2942 if(d->drag.scroll_timeout)
2943 {
2944 GdkRectangle visible;
2945 gtk_tree_view_get_visible_rect(d->dictionary_view, &visible);
2946 gint top_ty;
2947 gtk_tree_view_convert_bin_window_to_tree_coords(d->dictionary_view,
2948 0, 0, NULL, &top_ty);
2949
2950 // temporary drop root
2951 if(d->drag.tag_source && !d->drag.root && d->drag.last_y < 5 && top_ty < 1)
2952 {
2953 GtkTreeIter iter;
2954 gtk_tree_store_prepend(d->dictionary_treestore, &iter, NULL);
2955 gtk_tree_store_set(d->dictionary_treestore, &iter,
2956 DT_LIB_TAGGING_COL_TAG, _("drop to root"),
2963 -1);
2964 d->drag.root = TRUE;
2965 }
2966 else if(d->drag.root && d->drag.last_y >= 20)
2967 _dnd_clear_root(self);
2968
2969 if(d->drag.last_y < 5)
2970 {
2971 // scroll up
2972 gtk_tree_view_scroll_to_point(d->dictionary_view, 0, top_ty - 25 < 0 ? 0 : top_ty - 25);
2973 }
2974 else if(d->drag.last_y > visible.height - 5)
2975 // scroll down
2976 gtk_tree_view_scroll_to_point(d->dictionary_view, 0, top_ty + 25);
2977 return TRUE;
2978 }
2979 return FALSE;
2980}
2981
2982static gboolean _event_dnd_motion(GtkWidget *widget, GdkDragContext *context,
2983 gint x, gint y, guint time, dt_lib_module_t *self)
2984{
2986 GtkTreeView *tree = GTK_TREE_VIEW(widget);
2987 GtkTreePath *path = NULL;
2988
2989 if(gtk_tree_view_get_path_at_pos(tree, x, y, &path, NULL, NULL, NULL))
2990 {
2991 if(IS_NULL_PTR(d->drag.lastpath) || ((d->drag.lastpath) && gtk_tree_path_compare(d->drag.lastpath, path) != 0))
2992 {
2993 GtkTreeViewColumn *col = gtk_tree_view_get_column(d->dictionary_view, 0);
2994 const int sel_width = gtk_tree_view_column_get_width(col);
2995 if(x >= sel_width)
2996 {
2997 if(!gtk_tree_view_row_expanded(tree, path))
2998 d->drag.expand_timeout = g_timeout_add(200, (GSourceFunc)_dnd_expand_timeout, self);
2999 }
3000 }
3001
3002 GtkTreeSelection *selection = gtk_tree_view_get_selection(d->dictionary_view);
3003 gtk_tree_selection_select_path(selection, path);
3004 d->drag.last_y = y;
3005 if(d->drag.scroll_timeout == 0)
3006 {
3007 d->drag.scroll_timeout = g_timeout_add(100, (GSourceFunc)_dnd_scroll_timeout, self);
3008 }
3009 }
3010
3011 // FIXME - no row highlihted... workartound with selection above
3012// gtk_tree_view_set_drag_dest_row(tree, path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
3013
3014 if(d->drag.lastpath)
3015 gtk_tree_path_free(d->drag.lastpath);
3016 d->drag.lastpath = path;
3017
3018 return TRUE;
3019}
3020
3021static void _event_dnd_end(GtkWidget *widget, GdkDragContext *context, dt_lib_module_t *self)
3022{
3024 // FIXME (see gtk_tree_view_set_drag_dest_row above)
3025// gtk_tree_view_set_drag_dest_row(d->dictionary_view, NULL, GTK_TREE_VIEW_DROP_BEFORE);
3026 GtkTreeSelection *selection = gtk_tree_view_get_selection(d->dictionary_view);
3027 gtk_tree_selection_unselect_all(selection);
3028 if(d->drag.scroll_timeout)
3029 g_source_remove(d->drag.scroll_timeout);
3030 d->drag.scroll_timeout = 0;
3031 d->drag.tag_source = FALSE;
3032 _dnd_clear_root(self);
3033}
3034
3035// open the tag management window (from the module's gear menu)
3036static void _show_manage_window(GtkMenuItem *menuitem, dt_lib_module_t *self)
3037{
3039 gtk_widget_show_all(d->manage_window);
3040 gtk_window_present(GTK_WINDOW(d->manage_window));
3041}
3042
3043// after a suggestion setting changed, rebuild the dictionary if it shows suggestions
3045{
3047 if(!d->tree_flag && d->suggestion_flag)
3048 _init_treeview(self, 1);
3049}
3050
3051static void _confidence_changed(GtkSpinButton *spin, dt_lib_module_t *self)
3052{
3053 if(darktable.gui->reset) return;
3054 dt_conf_set_int("plugins/lighttable/tagging/confidence", gtk_spin_button_get_value_as_int(spin));
3056}
3057
3058static void _recent_tags_changed(GtkSpinButton *spin, dt_lib_module_t *self)
3059{
3060 if(darktable.gui->reset) return;
3061 dt_conf_set_int("plugins/lighttable/tagging/nb_recent_tags", gtk_spin_button_get_value_as_int(spin));
3064}
3065
3066// clear the GtkEntry when its built-in secondary (clear) icon is pressed
3067static void _entry_clear_icon(GtkEntry *entry, GtkEntryIconPosition pos, GdkEvent *event, gpointer user_data)
3068{
3069 if(pos == GTK_ENTRY_ICON_SECONDARY) gtk_entry_set_text(entry, "");
3070}
3071
3072// validate button next to the entry: attach the typed/picked tag (same as pressing Enter)
3073static void _validate_button_clicked(GtkButton *button, dt_lib_module_t *self)
3074{
3076 _create_tag_from_entry(self, d->entry);
3077}
3078
3080{
3081 dt_lib_tagging_t *d = (dt_lib_tagging_t *)calloc(sizeof(dt_lib_tagging_t),1);
3082 self->data = (void *)d;
3083 d->last_tag = NULL;
3084 self->timeout_handle = 0;
3085
3086 self->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
3087
3088 GtkBox *box, *hbox;
3089 GtkWidget *button;
3090 GtkWidget *w;
3091 GtkTreeView *view;
3092 GtkTreeModel *model;
3093 GtkListStore *liststore;
3094 GtkTreeStore *treestore;
3095 GtkTreeViewColumn *col;
3096 GtkCellRenderer *renderer;
3097
3098 // ── line 1: "view" (list/tree) and "sort" (name/count) comboboxes ──
3099 hbox = GTK_BOX(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING));
3100
3101 // "view" combobox: render the attached list above as a flat list, or as a hierarchical tree
3102 d->attached_view_combo = dt_bauhaus_combobox_new(darktable.bauhaus, DT_GUI_MODULE(NULL));
3103 dt_bauhaus_widget_set_label(d->attached_view_combo, _("view"));
3104 dt_bauhaus_combobox_add(d->attached_view_combo, _("list"));
3105 dt_bauhaus_combobox_add(d->attached_view_combo, _("tree"));
3107 gtk_widget_set_valign(d->attached_view_combo, GTK_ALIGN_CENTER);
3108 gtk_widget_set_tooltip_text(d->attached_view_combo,
3109 _("show the attached tags as a flat list or as a hierarchical tree"));
3110 g_signal_connect(G_OBJECT(d->attached_view_combo), "value-changed", G_CALLBACK(_attached_view_combo_changed), self);
3111 gtk_box_pack_start(hbox, d->attached_view_combo, TRUE, TRUE, 0);
3112
3113 // "sort" combobox: by name or by image count
3115 dt_bauhaus_widget_set_label(d->sort_combo, _("Sort by"));
3116 dt_bauhaus_combobox_add(d->sort_combo, _("name"));
3117 dt_bauhaus_combobox_add(d->sort_combo, _("count"));
3119 gtk_widget_set_valign(d->sort_combo, GTK_ALIGN_CENTER);
3120 gtk_widget_set_tooltip_text(d->sort_combo, _("sort the tags by name or by image count"));
3121 g_signal_connect(G_OBJECT(d->sort_combo), "value-changed", G_CALLBACK(_sort_combo_changed), self);
3122 gtk_box_pack_start(hbox, d->sort_combo, TRUE, TRUE, 0);
3123
3124 gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(hbox), FALSE, TRUE, 0);
3125
3126 // ── line 3: scrolled list / tree of attached tags ──
3127 // (packed here in code, but reordered below to sit under the entry — see the reorder call)
3128 box = GTK_BOX(gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING));
3129
3130 gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(box), TRUE, TRUE, 0);
3131 view = GTK_TREE_VIEW(gtk_tree_view_new());
3132 // Static: the attached-tags list tracks the hovered/selected images, so keep a fixed height.
3133 w = dt_ui_scroll_wrap(GTK_WIDGET(view), 200, "plugins/lighttable/tagging/heightattachedwindow",
3135 gtk_box_pack_start(box, w, TRUE, TRUE, 0);
3136 d->attached_view = view;
3137 gtk_tree_view_set_enable_search(view, FALSE);
3138 gtk_tree_view_set_headers_visible(view, FALSE);
3139 liststore = gtk_list_store_new(DT_LIB_TAGGING_NUM_COLS, G_TYPE_STRING, G_TYPE_UINT, G_TYPE_STRING,
3140 G_TYPE_STRING, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_BOOLEAN);
3141 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(liststore), DT_TAG_SORT_PATH_ID,
3142 (GtkTreeIterCompareFunc)_sort_tree_path_func, self, NULL);
3143 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(liststore), DT_TAG_SORT_NAME_ID,
3144 (GtkTreeIterCompareFunc)_sort_tree_tag_func, self, NULL);
3145 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(liststore), DT_TAG_SORT_COUNT_ID,
3146 (GtkTreeIterCompareFunc)_sort_tree_count_func, self, NULL);
3147 d->attached_liststore = liststore; // kept alive by d's ref (the view alternates between the two stores)
3148 // tree variant of the attached view, used when the "view" combobox is set to "tree"
3149 treestore = gtk_tree_store_new(DT_LIB_TAGGING_NUM_COLS, G_TYPE_STRING, G_TYPE_UINT, G_TYPE_STRING,
3150 G_TYPE_STRING, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_BOOLEAN);
3151 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(treestore), DT_TAG_SORT_PATH_ID,
3152 (GtkTreeIterCompareFunc)_sort_tree_path_func, self, NULL);
3153 d->attached_treestore = treestore;
3154 g_object_set(G_OBJECT(view), "has-tooltip", TRUE, NULL);
3155 g_signal_connect(G_OBJECT(view), "query-tooltip", G_CALLBACK(_row_tooltip_setup), (gpointer)self);
3156
3157 col = gtk_tree_view_column_new();
3158 gtk_tree_view_append_column(view, col);
3159 renderer = gtk_cell_renderer_text_new();
3160 g_object_set(renderer, "ellipsize", PANGO_ELLIPSIZE_MIDDLE, (gchar *)0);
3161 gtk_tree_view_column_pack_start(col, renderer, TRUE);
3162 gtk_tree_view_column_set_cell_data_func(col, renderer, _tree_tagname_show_attached, (gpointer)self, NULL);
3163 gtk_tree_view_column_set_expand(col, TRUE);
3164 gtk_tree_view_set_expander_column(view, col);
3165
3166 // per-row "delete" icon to detach a single tag in one click
3167 col = gtk_tree_view_column_new();
3168 gtk_tree_view_append_column(view, col);
3169 renderer = dtgtk_cell_renderer_button_new();
3170 g_object_set(renderer, "mode", GTK_CELL_RENDERER_MODE_ACTIVATABLE, NULL);
3171 gtk_tree_view_column_pack_end(col, renderer, FALSE);
3172 gtk_tree_view_column_set_cell_data_func(col, renderer, _attached_delete_icon_show, NULL, NULL);
3173 g_signal_connect(renderer, "activate", G_CALLBACK(_attached_delete_icon_activated), (gpointer)self);
3174
3175 gtk_tree_selection_set_mode(gtk_tree_view_get_selection(view), GTK_SELECTION_MULTIPLE);
3176 gtk_tree_view_set_model(view, GTK_TREE_MODEL(liststore));
3177 // NB: liststore and treestore are not unref'd here — d keeps a ref on each so they
3178 // both survive while the view switches between list and tree mode (freed in gui_cleanup)
3179 gtk_widget_set_tooltip_text(GTK_WIDGET(view), _("attached tags,"
3180 "\nclick the trash icon, press Delete or double-click to detach"
3181 "\nselect several tags and right-click to detach them in bulk,"
3182 "\npress Tab to give the focus to entry,"
3183 "\nctrl-wheel scroll to resize the window"));
3184 g_signal_connect(G_OBJECT(view), "button-press-event", G_CALLBACK(_click_on_view_attached), (gpointer)self);
3185 g_signal_connect(G_OBJECT(view), "key-press-event", G_CALLBACK(_attached_key_pressed), (gpointer)self);
3186 g_signal_connect(gtk_tree_view_get_selection(view), "changed", G_CALLBACK(_tree_selection_changed), self);
3187
3188#define NEW_TOGGLE_BUTTON(paint, callback, tooltip, action) \
3189 button = dtgtk_togglebutton_new(paint, 0, NULL); \
3190 gtk_widget_set_tooltip_text(button, tooltip); \
3191 gtk_box_pack_end(GTK_BOX(hbox), button, FALSE, TRUE, 0); \
3192 g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(callback), self); \
3193
3194 // ── line 2: tag input entry (with autocompletion) ──
3195 box = GTK_BOX(gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING));
3196 gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(box), FALSE, TRUE, 0);
3197 // the attached-tags list is built before this entry, so move the entry up to index 1
3198 // (just under the combos at index 0): final order is combos, entry, attached list, checkbox
3199 gtk_box_reorder_child(GTK_BOX(self->widget), GTK_WIDGET(box), 1);
3200
3201 hbox = GTK_BOX(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING));
3202
3203 w = gtk_entry_new();
3205 gtk_entry_set_text(GTK_ENTRY(w), "");
3206 gtk_entry_set_width_chars(GTK_ENTRY(w), 0);
3207 // no widget tooltip here: it would pop up over the autocompletion list and hide it
3208 gtk_entry_set_placeholder_text(GTK_ENTRY(w), _("enter or pick a tag, Enter to attach"));
3209 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(w), GTK_ENTRY_ICON_SECONDARY, "edit-clear-symbolic");
3210 gtk_entry_set_icon_tooltip_text(GTK_ENTRY(w), GTK_ENTRY_ICON_SECONDARY, _("clear entry"));
3211 g_signal_connect(G_OBJECT(w), "icon-press", G_CALLBACK(_entry_clear_icon), NULL);
3212 gtk_box_pack_start(hbox, w, TRUE, TRUE, 0);
3213 gtk_widget_add_events(GTK_WIDGET(w), GDK_KEY_RELEASE_MASK);
3214 g_signal_connect(G_OBJECT(w), "key-press-event", G_CALLBACK(_enter_key_pressed), (gpointer)self);
3215 d->entry = GTK_ENTRY(w);
3216
3217 // validate button: attach the typed/picked tag without having to hit Enter
3218 d->validate_button = dtgtk_button_new(dtgtk_cairo_paint_check_mark, 0, NULL);
3219 gtk_widget_set_tooltip_text(d->validate_button, _("attach this tag to the selected images"));
3220 gtk_box_pack_end(GTK_BOX(hbox), d->validate_button, FALSE, TRUE, 0);
3221 g_signal_connect(G_OBJECT(d->validate_button), "clicked", G_CALLBACK(_validate_button_clicked), (gpointer)self);
3222 gtk_box_pack_start(box, GTK_WIDGET(hbox), FALSE, TRUE, 0);
3223
3224 // autocompletion: every known tag feeds the model, suggestions default to the
3225 // current collection's tags when the entry holds a single token (see _completion_match_func).
3226 // minimum key length 1: an empty entry shows no popup, so the list does not hover over the
3227 // attached-tags view (e.g. right after a tag was attached and the entry cleared).
3228 d->completion_store = gtk_list_store_new(DT_COMPL_NUM_COLS, G_TYPE_STRING);
3229 {
3230 GtkEntryCompletion *completion = gtk_entry_completion_new();
3231 gtk_entry_completion_set_model(completion, GTK_TREE_MODEL(d->completion_store));
3232 gtk_entry_completion_set_text_column(completion, DT_COMPL_COL_PATH);
3233 gtk_entry_completion_set_inline_completion(completion, TRUE);
3234 gtk_entry_completion_set_popup_set_width(completion, FALSE);
3235 gtk_entry_completion_set_minimum_key_length(completion, 1);
3236 g_signal_connect(G_OBJECT(completion), "match-selected", G_CALLBACK(_match_selected_func), self);
3237 gtk_entry_completion_set_match_func(completion, _completion_match_func, self, NULL);
3238 gtk_entry_set_completion(d->entry, completion);
3239 g_object_unref(completion);
3240 }
3241 g_object_unref(d->completion_store); // kept alive by the completion above
3242
3243 // ── tag management popup window (persistent, hidden until requested) ────
3244 d->manage_window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
3245#ifdef GDK_WINDOWING_QUARTZ
3246 dt_osx_disallow_fullscreen(d->manage_window);
3247#endif
3248 gtk_window_set_title(GTK_WINDOW(d->manage_window), _("manage tags"));
3249 gtk_window_set_transient_for(GTK_WINDOW(d->manage_window), GTK_WINDOW(dt_ui_main_window(darktable.gui->ui)));
3250 gtk_window_set_default_size(GTK_WINDOW(d->manage_window), DT_PIXEL_APPLY_DPI(400), DT_PIXEL_APPLY_DPI(600));
3251 // hide instead of destroy so the dictionary widgets/state persist across openings
3252 g_signal_connect(G_OBJECT(d->manage_window), "delete-event", G_CALLBACK(gtk_widget_hide_on_delete), NULL);
3253
3254 box = GTK_BOX(gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING));
3255 gtk_container_set_border_width(GTK_CONTAINER(box), DT_PIXEL_APPLY_DPI(8));
3256 gtk_container_add(GTK_CONTAINER(d->manage_window), GTK_WIDGET(box));
3257
3258 // search entry to filter the dictionary
3259 w = gtk_entry_new();
3261 gtk_entry_set_text(GTK_ENTRY(w), "");
3262 gtk_entry_set_width_chars(GTK_ENTRY(w), 0);
3263 gtk_entry_set_placeholder_text(GTK_ENTRY(w), _("type to filter the tag dictionary below"));
3264 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(w), GTK_ENTRY_ICON_SECONDARY, "edit-clear-symbolic");
3265 gtk_entry_set_icon_tooltip_text(GTK_ENTRY(w), GTK_ENTRY_ICON_SECONDARY, _("clear entry"));
3266 g_signal_connect(G_OBJECT(w), "icon-press", G_CALLBACK(_entry_clear_icon), NULL);
3267 gtk_widget_add_events(GTK_WIDGET(w), GDK_KEY_RELEASE_MASK);
3268 g_signal_connect(G_OBJECT(w), "changed", G_CALLBACK(_tag_name_changed), (gpointer)self);
3269 d->dict_entry = GTK_ENTRY(w);
3270 gtk_box_pack_start(box, w, FALSE, TRUE, 0);
3271
3272 // dictionary_view tree view
3273 view = GTK_TREE_VIEW(gtk_tree_view_new());
3274 // Static: keep a fixed height for stability alongside the attached-tags list above.
3275 w = dt_ui_scroll_wrap(GTK_WIDGET(view), 200, "plugins/lighttable/tagging/heightdictionarywindow",
3277 gtk_box_pack_start(box, w, TRUE, TRUE, 0);
3278 d->dictionary_view = view;
3279 gtk_tree_view_set_enable_search(view, FALSE);
3280 gtk_tree_view_set_headers_visible(view, FALSE);
3281 liststore = gtk_list_store_new(DT_LIB_TAGGING_NUM_COLS, G_TYPE_STRING, G_TYPE_UINT, G_TYPE_STRING,
3282 G_TYPE_STRING, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_BOOLEAN);
3283 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(liststore), DT_TAG_SORT_PATH_ID,
3284 (GtkTreeIterCompareFunc)_sort_tree_path_func, self, NULL);
3285 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(liststore), DT_TAG_SORT_NAME_ID,
3286 (GtkTreeIterCompareFunc)_sort_tree_tag_func, self, NULL);
3287 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(liststore), DT_TAG_SORT_COUNT_ID,
3288 (GtkTreeIterCompareFunc)_sort_tree_count_func, self, NULL);
3289 d->dictionary_liststore = liststore;
3290 model = gtk_tree_model_filter_new(GTK_TREE_MODEL(liststore), NULL);
3291 gtk_tree_model_filter_set_visible_column(GTK_TREE_MODEL_FILTER(model), DT_LIB_TAGGING_COL_VISIBLE);
3292 d->dictionary_listfilter = GTK_TREE_MODEL_FILTER(model);
3293 treestore = gtk_tree_store_new(DT_LIB_TAGGING_NUM_COLS, G_TYPE_STRING, G_TYPE_UINT, G_TYPE_STRING,
3294 G_TYPE_STRING, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_BOOLEAN);
3295 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(treestore), DT_TAG_SORT_PATH_ID,
3296 (GtkTreeIterCompareFunc)_sort_tree_path_func, self, NULL);
3297 d->dictionary_treestore = treestore;
3298 model = gtk_tree_model_filter_new(GTK_TREE_MODEL(treestore), NULL);
3299 gtk_tree_model_filter_set_visible_column(GTK_TREE_MODEL_FILTER(model), DT_LIB_TAGGING_COL_VISIBLE);
3300 d->dictionary_treefilter = GTK_TREE_MODEL_FILTER(model);
3301
3302 col = gtk_tree_view_column_new();
3303 gtk_tree_view_append_column(view, col);
3304 renderer = gtk_cell_renderer_text_new();
3305 g_object_set(renderer, "ellipsize", PANGO_ELLIPSIZE_MIDDLE, (gchar *)0);
3306 gtk_tree_view_column_pack_start(col, renderer, TRUE);
3307 gtk_tree_view_column_set_cell_data_func(col, renderer, _tree_tagname_show_dictionary, (gpointer)self, NULL);
3308 gtk_tree_view_set_expander_column(view, col);
3309
3310 gtk_tree_selection_set_mode(gtk_tree_view_get_selection(view), GTK_SELECTION_MULTIPLE);
3311 gtk_widget_set_tooltip_text(GTK_WIDGET(view), _("tag dictionary,"
3312 "\nright-click to create, edit, delete tags,"
3313 "\nselect several tags and right-click to delete them in bulk,"
3314 "\nshift+click to fully expand the selected tag,"
3315 "\nctrl-scroll to resize the window"));
3316
3317 g_signal_connect(G_OBJECT(view), "button-press-event", G_CALLBACK(_click_on_view_dictionary), (gpointer)self);
3318 g_signal_connect(G_OBJECT(view), "key-press-event", G_CALLBACK(_dictionary_key_pressed), (gpointer)self);
3319 gtk_tree_view_set_model(view, GTK_TREE_MODEL(d->dictionary_listfilter));
3320 g_object_unref(d->dictionary_listfilter);
3321 g_object_set(G_OBJECT(view), "has-tooltip", TRUE, NULL);
3322 g_signal_connect(G_OBJECT(view), "query-tooltip", G_CALLBACK(_row_tooltip_setup), (gpointer)self);
3323 g_signal_connect(gtk_tree_view_get_selection(view), "changed", G_CALLBACK(_tree_selection_changed), self);
3324
3325 // drag & drop
3326 {
3327 d->drag.path = NULL;
3328 d->drag.tagname = NULL;
3329 d->drag.scroll_timeout = 0;
3330 d->drag.expand_timeout = 0;
3331 d->drag.root = FALSE;
3332 d->drag.tag_source = FALSE;
3333 gtk_drag_dest_set(GTK_WIDGET(d->dictionary_view), GTK_DEST_DEFAULT_ALL,
3334 target_list_tags_dest, n_targets_tags_dest, GDK_ACTION_MOVE);
3335 g_signal_connect(d->dictionary_view, "drag-data-get", G_CALLBACK(_event_dnd_get), self);
3336 g_signal_connect(d->dictionary_view, "drag-data-received", G_CALLBACK(_event_dnd_received), self);
3337 g_signal_connect_after(d->dictionary_view, "drag-begin", G_CALLBACK(_event_dnd_begin), self);
3338 g_signal_connect_after(d->dictionary_view, "drag-end", G_CALLBACK(_event_dnd_end), self);
3339 g_signal_connect(d->dictionary_view, "drag-motion", G_CALLBACK(_event_dnd_motion), self);
3340 }
3341
3342 // buttons
3343 hbox = GTK_BOX(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING));
3344
3345 d->new_button = dt_action_button_new(self, N_("new"), _new_button_clicked, self, _("create a new tag with the\nname you entered"), 0, 0);
3346 gtk_box_pack_start(hbox, d->new_button, TRUE, TRUE, 0);
3347
3348 d->import_button = dt_action_button_new(self, N_("import..."), _import_button_clicked, self, _("import tags from a Lightroom keyword file"), 0, 0);
3349 gtk_box_pack_start(hbox, d->import_button, TRUE, TRUE, 0);
3350
3351 d->export_button = dt_action_button_new(self, N_("export..."), _export_button_clicked, self, _("export all tags to a Lightroom keyword file"), 0, 0);
3352 gtk_box_pack_start(hbox, d->export_button, TRUE, TRUE, 0);
3353
3355 _("toggle list / tree view"), N_("tree"));
3357 _("toggle list with / without suggestion"), N_("suggestion"));
3358
3359 gtk_box_pack_start(box, GTK_WIDGET(hbox), FALSE, TRUE, 0);
3360
3361 // ── tag-suggestion settings (formerly the module "preferences" dialog), applied live ──
3362 gtk_box_pack_start(box, gtk_separator_new(GTK_ORIENTATION_HORIZONTAL), FALSE, FALSE, DT_PIXEL_APPLY_DPI(4));
3363 {
3364 GtkWidget *grid = gtk_grid_new();
3365 gtk_grid_set_row_spacing(GTK_GRID(grid), DT_GUI_BOX_SPACING);
3366 gtk_grid_set_column_spacing(GTK_GRID(grid), DT_GUI_BOX_SPACING);
3367
3368 GtkWidget *lbl = gtk_label_new(_("suggested tags level of confidence"));
3369 gtk_widget_set_halign(lbl, GTK_ALIGN_START);
3370 gtk_widget_set_hexpand(lbl, TRUE);
3371 GtkWidget *spin = gtk_spin_button_new_with_range(0, 100, 1);
3372 gtk_spin_button_set_value(GTK_SPIN_BUTTON(spin), dt_conf_get_int("plugins/lighttable/tagging/confidence"));
3373 gtk_widget_set_tooltip_text(spin, _("level of confidence to include a tag in the suggestions list:"
3374 "\n0: all associated tags, 99: 99% matching tags,"
3375 "\n100: only the recently-used tags (faster)"));
3376 g_signal_connect(G_OBJECT(spin), "value-changed", G_CALLBACK(_confidence_changed), self);
3377 gtk_grid_attach(GTK_GRID(grid), lbl, 0, 0, 1, 1);
3378 gtk_grid_attach(GTK_GRID(grid), spin, 1, 0, 1, 1);
3379
3380 GtkWidget *lbl2 = gtk_label_new(_("number of recently attached tags"));
3381 gtk_widget_set_halign(lbl2, GTK_ALIGN_START);
3382 gtk_widget_set_hexpand(lbl2, TRUE);
3383 GtkWidget *spin2 = gtk_spin_button_new_with_range(-1, 1000, 1);
3384 gtk_spin_button_set_value(GTK_SPIN_BUTTON(spin2), dt_conf_get_int("plugins/lighttable/tagging/nb_recent_tags"));
3385 gtk_widget_set_tooltip_text(spin2, _("number of recently attached tags shown in the suggestions list"
3386 "\n(-1 disables the recent-tags list)"));
3387 g_signal_connect(G_OBJECT(spin2), "value-changed", G_CALLBACK(_recent_tags_changed), self);
3388 gtk_grid_attach(GTK_GRID(grid), lbl2, 0, 1, 1, 1);
3389 gtk_grid_attach(GTK_GRID(grid), spin2, 1, 1, 1, 1);
3390
3391 gtk_box_pack_start(box, grid, FALSE, FALSE, 0);
3392 }
3393
3394 // "show system tags" checkbox: reveal the tags Ansel manages automatically (Ansel|…)
3395 d->dttags_check = gtk_check_button_new_with_label(_("Show system tags"));
3396 gtk_widget_set_tooltip_text(d->dttags_check, _("show the tags automatically attached by Ansel (names starting with “Ansel|”)"));
3397 d->dttags_flag = FALSE;
3398 g_signal_connect(G_OBJECT(d->dttags_check), "toggled", G_CALLBACK(_dttags_check_toggled), self);
3399 gtk_box_pack_start(GTK_BOX(self->widget), d->dttags_check, FALSE, TRUE, 0);
3400
3401#undef NEW_TOGGLE_BUTTON
3402
3403 /* connect to mouse over id */
3405 G_CALLBACK(_lib_tagging_redraw_callback), self);
3407 G_CALLBACK(_lib_tagging_tags_changed_callback), self);
3409 G_CALLBACK(_lib_selection_changed_callback), self);
3411 G_CALLBACK(_collection_updated_callback), self);
3412
3413 d->collection = g_malloc(4096);
3414 d->collection_tags = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
3415 _update_layout(self);
3416 _init_treeview(self, 0);
3417 _set_keyword(self);
3418 _init_treeview(self, 1);
3422
3424 darktable.gui->accels->lighttable_accels, N_("Lighttable/Tags"),
3425 N_("Tag"), GDK_KEY_t, GDK_CONTROL_MASK, FALSE,
3426 _("Opens the quick tagging entry"));
3428 darktable.gui->accels->lighttable_accels, N_("Lighttable/Tags"),
3429 N_("Redo last tag"), GDK_KEY_t, GDK_MOD1_MASK, FALSE,
3430 _("Re-applies the last used tag"));
3432 darktable.gui->accels->map_accels, N_("Map/Tags"),
3433 N_("Tag"), GDK_KEY_t, GDK_CONTROL_MASK, FALSE,
3434 _("Opens the quick tagging entry"));
3436 darktable.gui->accels->map_accels, N_("Map/Tags"),
3437 N_("Redo last tag"), GDK_KEY_t, GDK_MOD1_MASK, FALSE,
3438 _("Re-applies the last used tag"));
3439}
3440
3442{
3443 if(IS_NULL_PTR(self->data)) return;
3446
3447 gchar *path = dt_accels_build_path(N_("Lighttable/Tags"), N_("Tag"));
3449 dt_free(path);
3450 path = dt_accels_build_path(N_("Lighttable/Tags"), N_("Redo last tag"));
3452 dt_free(path);
3453 path = dt_accels_build_path(N_("Map/Tags"), N_("Tag"));
3455 dt_free(path);
3456 path = dt_accels_build_path(N_("Map/Tags"), N_("Redo last tag"));
3458 dt_free(path);
3459
3464 if(d->manage_window) gtk_widget_destroy(d->manage_window);
3465 if(d->collection_tags) g_hash_table_destroy(d->collection_tags);
3466 // d kept its own ref on both attached stores so the view could switch between them
3467 if(d->attached_liststore) g_object_unref(d->attached_liststore);
3468 if(d->attached_treestore) g_object_unref(d->attached_treestore);
3469 dt_free(d->collection);
3470 if(d->drag.tagname)
3471 {
3472 dt_free(d->drag.tagname);
3473 }
3474 if(d->drag.path) gtk_tree_path_free(d->drag.path);
3475 dt_free(self->data);
3476}
3477
3478// http://stackoverflow.com/questions/4631388/transparent-floating-gtkentry
3479static gboolean _lib_tagging_tag_key_press(GtkWidget *entry, GdkEventKey *event, dt_lib_module_t *self)
3480{
3482 guint key = dt_keys_mainpad_alternatives(event->keyval);
3483
3484 switch(key)
3485 {
3486 case GDK_KEY_Escape:
3487 g_list_free(d->floating_tag_imgs);
3488 d->floating_tag_imgs = NULL;
3489 gtk_widget_destroy(d->floating_tag_window);
3490 gtk_window_present(GTK_WINDOW(dt_ui_main_window(darktable.gui->ui)));
3491 return TRUE;
3492 case GDK_KEY_Tab:
3493 return TRUE;
3494 case GDK_KEY_Return:
3495 {
3496 const gchar *tag = gtk_entry_get_text(GTK_ENTRY(entry));
3497 const gboolean res = dt_tag_attach_string_list(tag, d->floating_tag_imgs, TRUE);
3498 if(res) dt_image_synch_xmps(d->floating_tag_imgs);
3499 g_list_free(d->floating_tag_imgs);
3500 d->floating_tag_imgs = NULL;
3501
3503 _save_last_tag_used(tag, d);
3504
3505 _init_treeview(self, 0);
3506 _init_treeview(self, 1);
3507 gtk_widget_destroy(d->floating_tag_window);
3508 gtk_window_present(GTK_WINDOW(dt_ui_main_window(darktable.gui->ui)));
3509 if(res) _raise_signal_tag_changed(self);
3510
3511 return TRUE;
3512 }
3513 }
3514 return FALSE; /* event not handled */
3515}
3516
3517static gboolean _lib_tagging_tag_destroy(GtkWidget *widget, GdkEvent *event, gpointer user_data)
3518{
3519 gtk_widget_destroy(GTK_WIDGET(user_data));
3520 return FALSE;
3521}
3522
3523
3527static gboolean _lib_tagging_tag_redo_accel(GtkAccelGroup *accel_group, GObject *accelerable, guint keyval,
3528 GdkModifierType mods, gpointer user_data)
3529{
3530 dt_lib_module_t *self = (dt_lib_module_t *)user_data;
3532
3533 if(d->last_tag)
3534 {
3535 GList *imgs = dt_act_on_get_images();
3536 const gboolean res = dt_tag_attach_string_list(d->last_tag, imgs, TRUE);
3537 if(res) dt_image_synch_xmps(imgs);
3538 g_list_free(imgs);
3539 imgs = NULL;
3540 _init_treeview(self, 0);
3541 _init_treeview(self, 1);
3542 if(res) _raise_signal_tag_changed(self);
3543 }
3544
3545 return TRUE;
3546}
3547
3552static gboolean _lib_tagging_tag_show_accel(GtkAccelGroup *accel_group, GObject *accelerable, guint keyval,
3553 GdkModifierType mods, gpointer user_data)
3554{
3555 dt_lib_module_t *self = (dt_lib_module_t *)user_data;
3557 if(d->tree_flag)
3558 {
3559 dt_control_log(_("tag shortcut is not active with tag tree view. please switch to list view"));
3560 return TRUE; // doesn't work properly with tree treeview
3561 }
3562
3563 d->floating_tag_imgs = dt_act_on_get_images();
3564 gint x, y;
3565 gint px, py, w, h;
3568 gdk_window_get_origin(gtk_widget_get_window(center), &px, &py);
3569
3570 w = gdk_window_get_width(gtk_widget_get_window(center));
3571 h = gdk_window_get_height(gtk_widget_get_window(center));
3572
3573 x = px + 0.5 * (w - FLOATING_ENTRY_WIDTH);
3574 y = py + h - 50;
3575
3576 /* put the floating box at the mouse pointer */
3577 // gint pointerx, pointery;
3578 // GdkDevice *device =
3579 // gdk_device_manager_get_client_pointer(gdk_display_get_device_manager(gtk_widget_get_display(widget)));
3580 // gdk_window_get_device_position (gtk_widget_get_window (widget), device, &pointerx, &pointery, NULL);
3581 // x = px + pointerx + 1;
3582 // y = py + pointery + 1;
3583
3584 d->floating_tag_window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
3585#ifdef GDK_WINDOWING_QUARTZ
3586 dt_osx_disallow_fullscreen(d->floating_tag_window);
3587#endif
3588 /* stackoverflow.com/questions/1925568/how-to-give-keyboard-focus-to-a-pop-up-gtk-window */
3589 gtk_widget_set_can_focus(d->floating_tag_window, TRUE);
3590 gtk_window_set_decorated(GTK_WINDOW(d->floating_tag_window), FALSE);
3591 gtk_window_set_type_hint(GTK_WINDOW(d->floating_tag_window), GDK_WINDOW_TYPE_HINT_POPUP_MENU);
3592 gtk_window_set_transient_for(GTK_WINDOW(d->floating_tag_window), GTK_WINDOW(window));
3593 gtk_widget_set_opacity(d->floating_tag_window, 0.8);
3594 gtk_window_move(GTK_WINDOW(d->floating_tag_window), x, y);
3595
3596 GtkWidget *entry = gtk_entry_new();
3598 gtk_widget_set_size_request(entry, FLOATING_ENTRY_WIDTH, -1);
3599 gtk_widget_add_events(entry, GDK_FOCUS_CHANGE_MASK);
3600
3601 GtkEntryCompletion *completion = gtk_entry_completion_new();
3602 gtk_entry_completion_set_model(completion, GTK_TREE_MODEL(d->completion_store));
3603 gtk_entry_completion_set_text_column(completion, DT_COMPL_COL_PATH);
3604 gtk_entry_completion_set_inline_completion(completion, TRUE);
3605 gtk_entry_completion_set_popup_set_width(completion, FALSE);
3606 gtk_entry_completion_set_minimum_key_length(completion, 0);
3607 g_signal_connect(G_OBJECT(completion), "match-selected", G_CALLBACK(_match_selected_func), self);
3608 gtk_entry_completion_set_match_func(completion, _completion_match_func, self, NULL);
3609 gtk_entry_set_completion(GTK_ENTRY(entry), completion);
3610
3611 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
3612 gtk_container_add(GTK_CONTAINER(d->floating_tag_window), entry);
3613 g_signal_connect(entry, "focus-out-event", G_CALLBACK(_lib_tagging_tag_destroy), d->floating_tag_window);
3614 g_signal_connect(entry, "key-press-event", G_CALLBACK(_lib_tagging_tag_key_press), self);
3615
3616 gtk_widget_show_all(d->floating_tag_window);
3617 gtk_widget_grab_focus(entry);
3618 gtk_window_present(GTK_WINDOW(d->floating_tag_window));
3619
3620 return TRUE;
3621}
3622
3624{
3625 const int length = dt_conf_get_int("plugins/lighttable/tagging/nb_recent_tags");
3626 if(length == -1) return length;
3627 else if(length >= 10/2) return length * 2;
3628 else return 10;
3629}
3630
3632{
3633 const char *list = dt_conf_get_string_const("plugins/lighttable/tagging/recent_tags");
3634 if(!list[0])
3635 return;
3636 const int length = _get_recent_tags_list_length();
3637 if(length == -1)
3638 {
3639 dt_conf_set_string("plugins/lighttable/tagging/recent_tags", "");
3640 return;
3641 }
3642
3643 char *p = (char *)list;
3644 int nb = 1;
3645 for(;*p != '\0'; p++)
3646 {
3647 if(*p == ',') nb++;
3648 }
3649
3650 if(nb > length)
3651 {
3652 nb -= length;
3653 char *list2 = g_strdup(list);
3654 for(; nb > 0; nb--)
3655 {
3656 p = g_strrstr(list2, "','");
3657 if(p) *p = '\0';
3658 }
3659 dt_conf_set_string("plugins/lighttable/tagging/recent_tags", list2);
3660 dt_free(list2);
3661 }
3662}
3663
3664// the module's gear menu opens the tag management window, which now also hosts the
3665// tag-suggestion settings that used to live in a separate "preferences" dialog
3666void set_preferences(void *menu, dt_lib_module_t *self)
3667{
3668 GtkWidget *mi = gtk_menu_item_new_with_label(_("manage tags..."));
3669 g_signal_connect(G_OBJECT(mi), "activate", G_CALLBACK(_show_manage_window), self);
3670 gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi);
3671}
3672
3673static void _save_last_tag_used(const char *tagnames, dt_lib_tagging_t *d)
3674{
3675 dt_free(d->last_tag);
3676 d->last_tag = g_strdup(tagnames);
3677
3678 const int nb_recent = _get_recent_tags_list_length();
3679
3680 if(nb_recent != -1)
3681 {
3682 GList *ntags = dt_util_str_to_glist(",", tagnames);
3683 if(ntags)
3684 {
3685 const char *sl = dt_conf_get_string_const("plugins/lighttable/tagging/recent_tags");
3686 // use "','" instead of "," to use the list as is in the query (dt_tag_get_suggestions)
3687 GList *tags = dt_util_str_to_glist("','", sl);
3688 for(GList *tag = ntags; tag; tag = g_list_next(tag))
3689 {
3690 char *escaped = sqlite3_mprintf("%q", (char *)tag->data);
3691 GList *found = g_list_find_custom(tags, escaped, (GCompareFunc)g_strcmp0);
3692 if(found)
3693 {
3694 tags = g_list_remove_link(tags, found);
3695 dt_free(found->data);
3696 g_list_free(found);
3697 found = NULL;
3698 }
3699 tags = g_list_prepend(tags, g_strdup(escaped));
3700 sqlite3_free(escaped);
3701 }
3702 g_list_free_full(ntags, dt_free_gpointer);
3703 ntags = NULL;
3704
3705 char *nsl = dt_util_glist_to_str("','", tags);
3706 dt_conf_set_string("plugins/lighttable/tagging/recent_tags", nsl);
3707 dt_free(nsl);
3708 if(g_list_length(tags) > nb_recent)
3710 g_list_free_full(tags, dt_free_gpointer);
3711 tags = NULL;
3712 }
3713 }
3714}
3715
3716// clang-format off
3717// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
3718// vim: shiftwidth=2 expandtab tabstop=2 cindent
3719// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
3720// clang-format on
gchar * dt_accels_build_path(const gchar *scope, const gchar *feature)
void dt_accels_remove_accel(dt_accels_t *accels, const char *path, gpointer data)
Recursively remove all accels for all shortcuts containing path. This is unneeded for accels attached...
void dt_accels_new_action_shortcut(dt_accels_t *accels, gboolean(*action_callback)(GtkAccelGroup *group, GObject *acceleratable, guint keyval, GdkModifierType mods, gpointer user_data), gpointer data, GtkAccelGroup *accel_group, const gchar *action_scope, const gchar *action_name, guint key_val, GdkModifierType accel_mods, const gboolean lock, const char *description)
Register a new shortcut for a generic action, setting up its path, default keys and accel group....
GList * dt_act_on_get_images()
Definition act_on.c:39
int button_pressed(struct dt_iop_module_t *self, double x, double y, double pressure, int which, int type, uint32_t state)
Definition ashift.c:4460
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
void dt_bauhaus_combobox_set_selected_text_align(GtkWidget *widget, const dt_bauhaus_combobox_alignment_t text_align)
Definition bauhaus.c:2092
int dt_bauhaus_combobox_get(GtkWidget *widget)
Definition bauhaus.c:2347
void dt_bauhaus_combobox_set(GtkWidget *widget, const int pos)
Definition bauhaus.c:2301
void dt_bauhaus_widget_set_label(GtkWidget *widget, const char *label)
Definition bauhaus.c:1653
GtkWidget * dt_bauhaus_combobox_new(dt_bauhaus_t *bh, dt_gui_module_t *self)
Definition bauhaus.c:1842
void dt_bauhaus_combobox_add(GtkWidget *widget, const char *text)
Definition bauhaus.c:2016
@ DT_BAUHAUS_COMBOBOX_ALIGN_RIGHT
Definition bauhaus.h:125
GtkWidget * dtgtk_button_new(DTGTKCairoPaintIconFunc paint, gint paintflags, void *paintdata)
Definition button.c:134
static const dt_aligned_pixel_simd_t const dt_adaptation_t const float p
void dt_collection_deserialize(const char *buf)
int dt_collection_serialize(char *buf, int bufsize)
dt_collection_properties_t
Definition collection.h:107
@ DT_COLLECTION_PROP_TAG
Definition collection.h:127
dt_collection_change_t
Definition collection.h:147
void dt_image_synch_xmps(const GList *img)
void dt_image_synch_xmp(const int selected)
char * key
char * name
void dt_conf_set_bool(const char *name, int val)
int dt_conf_get_bool(const char *name)
void dt_conf_set_int(const char *name, int val)
int dt_conf_get_int(const char *name)
void dt_conf_set_string(const char *name, const char *val)
const char * dt_conf_get_string_const(const char *name)
int32_t dt_control_get_mouse_over_id()
Definition control.c:923
void dt_control_log(const char *msg,...)
Definition control.c:761
void leave(dt_view_t *self)
Definition darkroom.c:2644
uint32_t view(const dt_view_t *self)
Definition darkroom.c:227
darktable_t darktable
Definition darktable.c:181
#define DT_MODULE(MODVER)
Definition darktable.h:140
static void dt_free_gpointer(gpointer ptr)
Definition darktable.h:463
#define dt_free(ptr)
Definition darktable.h:456
static gboolean dt_modifier_is(const GdkModifierType state, const GdkModifierType desired_modifier_mask)
Definition darktable.h:893
#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
int store(dt_imageio_module_storage_t *self, dt_imageio_module_data_t *sdata, const int32_t imgid, dt_imageio_module_format_t *format, dt_imageio_module_data_t *fdata, const int num, const int total, const gboolean high_quality, const gboolean export_masks, dt_colorspaces_color_profile_type_t icc_type, const gchar *icc_filename, dt_iop_color_intent_t icc_intent, dt_export_metadata_t *metadata)
Definition disk.c:252
#define _DWORD
@ DND_TARGET_IMGID
@ DND_TARGET_TAG
static const GtkTargetEntry target_list_tags_dest[]
static const guint n_targets_tags
static const guint n_targets_tags_dest
static const GtkTargetEntry target_list_tags[]
void dtgtk_cairo_paint_treelist(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_check_mark(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_plus_simple(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
dt_lib_tagging_cols_t
static guint dt_keys_mainpad_alternatives(const guint key_val)
Remap keypad keys to usual mainpad ones.
Definition gdkkeys.h:113
void dt_gui_textview_set_padding(GtkTextView *textview)
Apply the standard recessed-input text padding to a GtkTextView.
Definition gtk.c:2687
GtkWidget * dt_ui_scroll_wrap(GtkWidget *w, gint min_size, char *config_str, dt_ui_resize_mode_t mode)
Wrap a scrollable content widget in a recessed, vertically resizable scrolled window.
Definition gtk.c:2713
void dt_accels_disconnect_on_text_input(GtkWidget *widget)
Disconnects accels when a text or search entry gets the focus, and reconnects them when it looses it....
Definition gtk.c:3225
GtkWidget * dt_ui_center(dt_ui_t *ui)
get the center drawable widget
GtkWidget * dt_ui_main_window(dt_ui_t *ui)
get the main window widget
@ DT_UI_RESIZE_STATIC
Definition gtk.h:264
#define DT_GUI_BOX_SPACING
Definition gtk.h:109
#define DT_PIXEL_APPLY_DPI(value)
Definition gtk.h:90
#define DT_GUI_MODULE(x)
GtkCellRenderer * dtgtk_cell_renderer_button_new(void)
const char * tooltip
Definition image.h:251
const char * model
static const float x
const int t
const float v
void dt_lib_cancel_postponed_update(dt_lib_module_t *mod)
Definition lib.c:1536
GtkWidget * dt_action_button_new(dt_lib_module_t *self, const gchar *label, gpointer callback, gpointer data, const gchar *tooltip, guint accel_key, GdkModifierType mods)
Definition lib.c:1563
void dt_lib_queue_postponed_update(dt_lib_module_t *mod, void(*update_fn)(dt_lib_module_t *self))
Definition lib.c:1524
size_t size
Definition mipmap_cache.c:3
dt_mipmap_buffer_dsc_flags flags
Definition mipmap_cache.c:4
void dt_osx_disallow_fullscreen(GtkWidget *widget)
Definition osx.mm:104
int dt_selection_get_length(struct dt_selection_t *selection)
Definition selection.c:179
GList * dt_selection_get_list(struct dt_selection_t *selection)
Definition selection.c:172
void dt_control_signal_unblock_by_func(const struct dt_control_signal_t *ctlsig, GCallback cb, gpointer user_data)
Definition signal.c:481
void dt_control_signal_block_by_func(const struct dt_control_signal_t *ctlsig, GCallback cb, gpointer user_data)
Definition signal.c:476
#define DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(ctlsig, cb, user_data)
Definition signal.h:368
#define DT_DEBUG_CONTROL_SIGNAL_RAISE(ctlsig, signal,...)
Definition signal.h:347
@ DT_SIGNAL_MOUSE_OVER_IMAGE_CHANGE
This signal is raised when mouse hovers over image thumbs both on lighttable and in the filmstrip....
Definition signal.h:59
@ DT_SIGNAL_TAG_CHANGED
This signal is raised when a tag is added/deleted/changed
Definition signal.h:130
@ DT_SIGNAL_SELECTION_CHANGED
This signal is raised when the selection is changed no param, no returned value.
Definition signal.h:127
@ DT_SIGNAL_COLLECTION_CHANGED
This signal is raised when collection changed. To avoid leaking the list, dt_collection_t is connecte...
Definition signal.h:122
#define DT_DEBUG_CONTROL_SIGNAL_CONNECT(ctlsig, signal, cb, user_data)
Definition signal.h:357
struct _GtkWidget GtkWidget
Definition splash.h:29
const float uint32_t state[4]
const float r
struct dt_gui_gtk_t * gui
Definition darktable.h:775
struct dt_selection_t * selection
Definition darktable.h:782
struct dt_control_signal_t * signals
Definition darktable.h:774
struct dt_bauhaus_t * bauhaus
Definition darktable.h:778
GtkAccelGroup * map_accels
GtkAccelGroup * lighttable_accels
int32_t reset
Definition gtk.h:172
dt_accels_t * accels
Definition gtk.h:194
dt_ui_t * ui
Definition gtk.h:164
GModule *void * data
Definition lib.h:80
GtkWidget * widget
Definition lib.h:84
guint timeout_handle
Definition lib.h:90
gchar * tagname
Definition tagging.c:98
GtkWidget * floating_tag_window
Definition tagging.c:90
GtkWidget * manage_window
Definition tagging.c:81
gboolean dttags_flag
Definition tagging.c:92
GtkListStore * attached_liststore
Definition tagging.c:86
GList * floating_tag_imgs
Definition tagging.c:91
GtkWidget * toggle_suggestion_button
Definition tagging.c:84
GtkTreeStore * attached_treestore
Definition tagging.c:87
GtkWidget * export_button
Definition tagging.c:83
char * collection
Definition tagging.c:94
GtkWidget * attached_view_combo
Definition tagging.c:85
GtkTreePath * lastpath
Definition tagging.c:99
GtkTreeModelFilter * dictionary_listfilter
Definition tagging.c:88
GHashTable * collection_tags
Definition tagging.c:89
gboolean attached_tree_flag
Definition tagging.c:93
GtkTreeView * attached_view
Definition tagging.c:82
GtkEntry * dict_entry
Definition tagging.c:80
gboolean root
Definition tagging.c:101
char * last_tag
Definition tagging.c:95
gboolean tree_flag
Definition tagging.c:110
char * newtagname
Definition tagging.c:108
gint tagid
Definition tagging.c:107
char * oldtagname
Definition tagging.c:109
static void _lib_tagging_redraw_callback(gpointer instance, dt_lib_module_t *self)
Definition tagging.c:629
static gboolean _attached_key_pressed(GtkWidget *view, GdkEventKey *event, dt_lib_module_t *self)
Definition tagging.c:1328
static gint _sort_tree_tag_func(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, dt_lib_module_t *self)
Definition tagging.c:2580
static gint _sort_tree_count_func(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, dt_lib_module_t *self)
Definition tagging.c:2571
static void _export_button_clicked(GtkButton *button, dt_lib_module_t *self)
Definition tagging.c:2456
void gui_reset(dt_lib_module_t *self)
Definition tagging.c:2651
static gboolean _enter_key_pressed(GtkWidget *entry, GdkEventKey *event, dt_lib_module_t *self)
Definition tagging.c:1394
int set_params(dt_lib_module_t *self, const void *params, int size)
Definition tagging.c:1019
static void _lib_tagging_tags_changed_callback(gpointer instance, dt_lib_module_t *self)
Definition tagging.c:635
static GList * _selected_tagids(GtkTreeView *view)
Definition tagging.c:1064
static gboolean _dnd_scroll_timeout(dt_lib_module_t *self)
Definition tagging.c:2939
static gboolean _select_previous_user_attached_tag(const int index, GtkTreeView *view)
Definition tagging.c:363
static void _pop_menu_dictionary_delete_node(GtkWidget *menuitem, dt_lib_module_t *self)
Definition tagging.c:1436
void * get_params(dt_lib_module_t *self, int *size)
Definition tagging.c:997
static void _tree_tagname_show(GtkTreeViewColumn *col, GtkCellRenderer *renderer, GtkTreeModel *model, GtkTreeIter *iter, gpointer data, gboolean dictionary_view)
Definition tagging.c:572
void set_preferences(void *menu, dt_lib_module_t *self)
Definition tagging.c:3666
static void _pop_menu_attached_detach(GtkWidget *menuitem, dt_lib_module_t *self)
Definition tagging.c:1160
static gboolean _find_tag_iter_tagname(GtkTreeModel *model, GtkTreeIter *iter, const char *tagname, const gboolean needle)
Definition tagging.c:290
static gint _sort_tree_path_func(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, dt_lib_module_t *self)
Definition tagging.c:2594
static void _toggle_tree_button_callback(GtkToggleButton *source, dt_lib_module_t *self)
Definition tagging.c:2562
static gboolean _set_matching_tag_visibility(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, dt_lib_module_t *self)
Definition tagging.c:210
static void _update_sel_on_tree(GtkTreeModel *model)
Definition tagging.c:798
static void _sort_combo_changed(GtkWidget *combo, dt_lib_module_t *self)
Definition tagging.c:2623
static void _validate_button_clicked(GtkButton *button, dt_lib_module_t *self)
Definition tagging.c:3073
static gboolean _row_tooltip_setup(GtkWidget *treeview, gint x, gint y, gboolean kb_mode, GtkTooltip *tooltip, dt_lib_module_t *self)
Definition tagging.c:2386
static gboolean _dictionary_key_pressed(GtkWidget *view, GdkEventKey *event, dt_lib_module_t *self)
Definition tagging.c:2334
static void _refresh_collection_tags(dt_lib_module_t *self)
Definition tagging.c:2691
static gboolean _update_tag_name_per_name(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, dt_tag_op_t *to)
Definition tagging.c:920
static void _event_dnd_get(GtkWidget *widget, GdkDragContext *context, GtkSelectionData *selection_data, const guint target_type, const guint time, dt_lib_module_t *self)
Definition tagging.c:2855
static void _new_button_clicked(GtkButton *button, dt_lib_module_t *self)
Definition tagging.c:1388
static void _dnd_clear_root(dt_lib_module_t *self)
Definition tagging.c:2819
static void _tag_name_changed(GtkEntry *entry, dt_lib_module_t *self)
Definition tagging.c:1422
static gboolean _lib_tagging_tag_redo_accel(GtkAccelGroup *accel_group, GObject *accelerable, guint keyval, GdkModifierType mods, gpointer user_data)
Re-apply the most recent tag expression to the current target images.
Definition tagging.c:3527
static void _detach_tagids(GList *tagids, dt_lib_module_t *self)
Definition tagging.c:1084
static void _update_attached_count(const int tagid, GtkTreeView *view, const gboolean tree_flag)
Definition tagging.c:965
#define NEW_TOGGLE_BUTTON(paint, callback, tooltip, action)
static void _unselect_all_in_view(GtkTreeView *view)
Definition tagging.c:167
static void _import_button_clicked(GtkButton *button, dt_lib_module_t *self)
Definition tagging.c:2425
static void _pop_menu_dictionary_change_path(GtkWidget *menuitem, dt_lib_module_t *self)
Definition tagging.c:1938
static gboolean _tree_reveal_func(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
Definition tagging.c:237
static void _size_recent_tags_list()
Definition tagging.c:3631
static void _propagate_sel_to_parents(GtkTreeModel *model, GtkTreeIter *iter)
Definition tagging.c:196
static void _update_layout(dt_lib_module_t *self)
Definition tagging.c:2491
static void _create_tag_from_entry(dt_lib_module_t *self, GtkEntry *src)
Definition tagging.c:1357
static void _pop_menu_dictionary_delete_selected(GtkWidget *menuitem, dt_lib_module_t *self)
Definition tagging.c:2131
static void _tree_tagname_show_attached(GtkTreeViewColumn *col, GtkCellRenderer *renderer, GtkTreeModel *model, GtkTreeIter *iter, gpointer data)
Definition tagging.c:608
void gui_cleanup(dt_lib_module_t *self)
Definition tagging.c:3441
static void _attached_delete_icon_show(GtkTreeViewColumn *col, GtkCellRenderer *renderer, GtkTreeModel *model, GtkTreeIter *iter, gpointer data)
Definition tagging.c:1258
static void _pop_menu_dictionary_edit_tag(GtkWidget *menuitem, dt_lib_module_t *self)
Definition tagging.c:1646
static gboolean _select_next_user_attached_tag(const int index, GtkTreeView *view)
Definition tagging.c:384
static void _show_manage_window(GtkMenuItem *menuitem, dt_lib_module_t *self)
Definition tagging.c:3036
static void _pop_menu_attached_attach_to_all(GtkWidget *menuitem, dt_lib_module_t *self)
Definition tagging.c:1112
static void _entry_clear_icon(GtkEntry *entry, GtkEntryIconPosition pos, GdkEvent *event, gpointer user_data)
Definition tagging.c:3067
static void _pop_menu_dictionary(GtkWidget *treeview, GdkEventButton *event, dt_lib_module_t *self)
Definition tagging.c:2162
static void _set_keyword(dt_lib_module_t *self)
Definition tagging.c:905
static void _reset_sel_on_path_full(GtkTreeModel *model, GtkTreeIter *iter, const gboolean root)
Definition tagging.c:732
static void _dttags_check_toggled(GtkToggleButton *source, dt_lib_module_t *self)
Definition tagging.c:2642
static void _sort_attached_list(dt_lib_module_t *self, gboolean force)
Definition tagging.c:254
static void _event_dnd_begin(GtkWidget *widget, GdkDragContext *context, dt_lib_module_t *self)
Definition tagging.c:2834
static void _recent_tags_changed(GtkSpinButton *spin, dt_lib_module_t *self)
Definition tagging.c:3058
static void _show_keyword_on_view(GtkTreeView *view, const char *keyword, const gboolean select)
Definition tagging.c:356
static gboolean _apply_rename_path(GtkWidget *dialog, const char *tagname, const char *newtag, dt_lib_module_t *self)
Definition tagging.c:1881
static void _calculate_sel_on_tree(GtkTreeModel *model, GtkTreeIter *iter)
Definition tagging.c:776
static void _lib_selection_changed_callback(gpointer instance, dt_lib_module_t *self)
Definition tagging.c:890
static void _find_root_iter_iter(GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *parent)
Definition tagging.c:753
@ DT_COMPL_NUM_COLS
Definition tagging.c:2668
@ DT_COMPL_COL_PATH
Definition tagging.c:2668
void init_presets(dt_lib_module_t *self)
Definition tagging.c:992
static void _pop_menu_dictionary_goto_collection_back(GtkWidget *menuitem, dt_lib_module_t *self)
Definition tagging.c:2046
static gboolean _completion_match_func(GtkEntryCompletion *completion, const gchar *key, GtkTreeIter *iter, gpointer user_data)
Definition tagging.c:2744
dt_tag_sort_id
Definition tagging.c:127
@ DT_TAG_SORT_COUNT_ID
Definition tagging.c:130
@ DT_TAG_SORT_NAME_ID
Definition tagging.c:129
@ DT_TAG_SORT_PATH_ID
Definition tagging.c:128
static void _pop_menu_dictionary_set_as_tag(GtkWidget *menuitem, dt_lib_module_t *self)
Definition tagging.c:2139
static gboolean _is_user_tag(GtkTreeModel *model, GtkTreeIter *iter)
Definition tagging.c:158
static void _pop_menu_dictionary_create_tag(GtkWidget *menuitem, dt_lib_module_t *self)
Definition tagging.c:1524
static void _attached_view_combo_changed(GtkWidget *combo, dt_lib_module_t *self)
Definition tagging.c:2633
static void _show_tag_on_view(GtkTreeView *view, const char *tagname, const gboolean needle, const gboolean select)
Definition tagging.c:338
static void _confidence_changed(GtkSpinButton *spin, dt_lib_module_t *self)
Definition tagging.c:3051
static void _refresh_completion_store(dt_lib_module_t *self)
Definition tagging.c:2671
static void _init_treeview(dt_lib_module_t *self, const int which)
Definition tagging.c:405
static void _collection_updated_callback(gpointer instance, dt_collection_change_t query_change, dt_collection_properties_t changed_property, gpointer imgs, int next, dt_lib_module_t *self)
Definition tagging.c:644
uint32_t container(dt_lib_module_t *self)
Definition tagging.c:153
static void _postponed_update(dt_lib_module_t *self)
Definition tagging.c:623
dt_lib_tagging_cols_t
Definition tagging.c:114
@ DT_LIB_TAGGING_COL_SYNONYM
Definition tagging.c:118
@ DT_LIB_TAGGING_NUM_COLS
Definition tagging.c:123
@ DT_LIB_TAGGING_COL_VISIBLE
Definition tagging.c:122
@ DT_LIB_TAGGING_COL_PATH
Definition tagging.c:117
@ DT_LIB_TAGGING_COL_COUNT
Definition tagging.c:119
@ DT_LIB_TAGGING_COL_TAG
Definition tagging.c:115
@ DT_LIB_TAGGING_COL_ID
Definition tagging.c:116
@ DT_LIB_TAGGING_COL_FLAGS
Definition tagging.c:121
@ DT_LIB_TAGGING_COL_SEL
Definition tagging.c:120
#define FLOATING_ENTRY_WIDTH
Definition tagging.c:73
static void _reset_sel_on_path(GtkTreeModel *model, GtkTreeIter *iter, const gboolean root)
Definition tagging.c:712
static void _pop_menu_attached_detach_all(GtkWidget *menuitem, dt_lib_module_t *self)
Definition tagging.c:1169
static gboolean _selection_get_first(GtkTreeSelection *selection, GtkTreeModel **model, GtkTreeIter *iter)
Definition tagging.c:176
static void _delete_tagids(GList *tagids, dt_lib_module_t *self)
Definition tagging.c:2075
static int _get_recent_tags_list_length()
Definition tagging.c:3623
static void _refresh_suggestions(dt_lib_module_t *self)
Definition tagging.c:3044
static void _toggle_suggestion_button_callback(GtkToggleButton *source, dt_lib_module_t *self)
Definition tagging.c:2553
static void _event_dnd_received(GtkWidget *widget, GdkDragContext *context, gint x, gint y, GtkSelectionData *selection_data, guint target_type, guint time, dt_lib_module_t *self)
Definition tagging.c:2863
static void _tree_selection_changed(GtkTreeSelection *treeselection, gpointer data)
Definition tagging.c:2814
static void _event_dnd_end(GtkWidget *widget, GdkDragContext *context, dt_lib_module_t *self)
Definition tagging.c:3021
static void _pop_menu_dictionary_goto_tag_collection(GtkWidget *menuitem, dt_lib_module_t *self)
Definition tagging.c:2022
void gui_init(dt_lib_module_t *self)
Definition tagging.c:3079
int position()
Definition tagging.c:2662
static gboolean _lib_tagging_tag_key_press(GtkWidget *entry, GdkEventKey *event, dt_lib_module_t *self)
Definition tagging.c:3479
static gboolean _lib_tagging_tag_show_accel(GtkAccelGroup *accel_group, GObject *accelerable, guint keyval, GdkModifierType mods, gpointer user_data)
Open the floating quick-tag entry used by the historical tagging shortcut, now registered in the acti...
Definition tagging.c:3552
static void _delete_tree_path(GtkTreeModel *model, GtkTreeIter *iter, gboolean root, gboolean tree)
Definition tagging.c:831
const char ** views(dt_lib_module_t *self)
Definition tagging.c:147
static gboolean _lib_tagging_tag_destroy(GtkWidget *widget, GdkEvent *event, gpointer user_data)
Definition tagging.c:3517
static void _calculate_sel_on_path(GtkTreeModel *model, GtkTreeIter *iter, const gboolean root)
Definition tagging.c:695
static gboolean _attached_delete_icon_activated(GtkCellRenderer *cell, GdkEvent *event, GtkWidget *treeview, const gchar *path_str, GdkRectangle *background, GdkRectangle *cell_area, GtkCellRendererState flags, gpointer user_data)
Definition tagging.c:1232
static void _tree_tagname_show_dictionary(GtkTreeViewColumn *col, GtkCellRenderer *renderer, GtkTreeModel *model, GtkTreeIter *iter, gpointer data)
Definition tagging.c:615
static gboolean _click_on_view_dictionary(GtkWidget *view, GdkEventButton *event, dt_lib_module_t *self)
Definition tagging.c:2275
static gboolean _dnd_expand_timeout(dt_lib_module_t *self)
Definition tagging.c:2929
static void _raise_signal_tag_changed(dt_lib_module_t *self)
Definition tagging.c:655
static void _save_last_tag_used(const char *tags, dt_lib_tagging_t *d)
Definition tagging.c:3673
static gboolean _event_dnd_motion(GtkWidget *widget, GdkDragContext *context, gint x, gint y, guint time, dt_lib_module_t *self)
Definition tagging.c:2982
static gboolean _match_selected_func(GtkEntryCompletion *completion, GtkTreeModel *model, GtkTreeIter *iter, gpointer user_data)
Definition tagging.c:2707
static void _pop_menu_attached(GtkWidget *treeview, GdkEventButton *event, dt_lib_module_t *self)
Definition tagging.c:1189
static void _pop_menu_dictionary_copy_tag(GtkWidget *menuitem, dt_lib_module_t *self)
Definition tagging.c:2058
static gboolean _find_tag_iter_tagid(GtkTreeModel *model, GtkTreeIter *iter, const gint tagid)
Definition tagging.c:673
static void _update_atdetach_buttons(dt_lib_module_t *self)
Definition tagging.c:191
static void _sort_dictionary_list(dt_lib_module_t *self, gboolean force)
Definition tagging.c:272
static void _show_iter_on_view(GtkTreeView *view, GtkTreeIter *iter, const gboolean select)
Definition tagging.c:323
static gboolean _click_on_view_attached(GtkWidget *view, GdkEventButton *event, dt_lib_module_t *self)
Definition tagging.c:1267
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
void dt_tag_get_tags_images(const gchar *keyword, GList **tag_list, GList **img_list)
Definition tags.c:1302
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
uint32_t dt_tag_get_attached(const int32_t imgid, GList **result, const gboolean ignore_dt_tags)
Definition tags.c:635
gboolean dt_tag_attach_images(const guint tagid, const GList *img, const gboolean undo_on)
Definition tags.c:463
gint dt_tag_get_flags(gint tagid)
Definition tags.c:1531
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
gboolean dt_tag_new(const char *name, guint *tagid)
Definition tags.c:179
uint32_t dt_tag_get_collection_tags(GList **result)
Definition tags.c:1436
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
uint32_t dt_tag_import(const char *filename)
Definition tags.c:1614
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
gboolean dt_tag_detach_images(const guint tagid, const GList *img, const gboolean undo_on)
Definition tags.c:570
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
uint32_t dt_tag_export(const char *filename)
Definition tags.c:1749
void dt_tag_count_tags_images(const gchar *keyword, int *tag_count, int *img_count)
Definition tags.c:1254
uint32_t dt_tag_get_with_usage(GList **result)
Definition tags.c:1377
@ 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_CATEGORY
Definition tags.h:63
@ DT_TF_PRIVATE
Definition tags.h:64
GList * dt_util_str_to_glist(const gchar *separator, const gchar *text)
Definition utility.c:830
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
@ DT_UI_CONTAINER_PANEL_LEFT_CENTER