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", "studio_capture", 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 GtkWindow *dialog_parent = gtk_window_get_transient_for(GTK_WINDOW(dialog));
1493 gtk_widget_destroy(dialog);
1494 dt_gui_refocus_parent(dialog_parent);
1495 if(res != GTK_RESPONSE_YES)
1496 {
1497 dt_free(tagname);
1498 return;
1499 }
1500
1501 GList *tag_family = NULL;
1502 GList *tagged_images = NULL;
1503 dt_tag_get_tags_images(tagname, &tag_family, &tagged_images);
1504
1506 tag_count = dt_tag_remove_list(tag_family);
1508 dt_control_log(_("%d tags removed"), tag_count);
1509
1510 GtkTreeIter store_iter;
1511 GtkTreeModel *store = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
1512 gtk_tree_model_filter_convert_iter_to_child_iter(GTK_TREE_MODEL_FILTER(model),
1513 &store_iter, &iter);
1514 _delete_tree_path(GTK_TREE_MODEL(store), &store_iter, TRUE, d->tree_flag);
1515 _init_treeview(self, 0);
1516
1517 dt_tag_free_result(&tag_family);
1518 dt_image_synch_xmps(tagged_images);
1519 g_list_free(tagged_images);
1520 tagged_images = NULL;
1522 dt_free(tagname);
1523}
1524
1525// create tag allows the user to create a single tag, which can be an element of the hierarchy or not
1527{
1529
1530 char *tagname;
1531 char *path;
1532 gint tagid;
1533 gchar *text;
1534 GtkWidget *label;
1535 GtkTreeIter iter;
1536 GtkTreeModel *model = NULL;
1537 GtkTreeView *view = d->dictionary_view;
1538 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
1539 if(!_selection_get_first(selection, &model, &iter)) return;
1540
1541 gtk_tree_model_get(model, &iter, DT_LIB_TAGGING_COL_TAG, &tagname,
1543
1545 GtkWidget *dialog = gtk_dialog_new_with_buttons(_("create tag"), GTK_WINDOW(win), GTK_DIALOG_DESTROY_WITH_PARENT,
1546 _("cancel"), GTK_RESPONSE_NONE, _("save"), GTK_RESPONSE_YES, NULL);
1547 gtk_window_set_default_size(GTK_WINDOW(dialog), 300, -1);
1548 GtkWidget *area = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
1549 GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
1550 gtk_container_set_border_width(GTK_CONTAINER(vbox), 8);
1551 gtk_container_add(GTK_CONTAINER(area), vbox);
1552
1553 GtkWidget *box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
1554 gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, TRUE, 0);
1555 label = gtk_label_new(_("name: "));
1556 gtk_box_pack_start(GTK_BOX(box), label, FALSE, TRUE, 0);
1557 GtkWidget *entry = gtk_entry_new();
1559 gtk_box_pack_end(GTK_BOX(box), entry, TRUE, TRUE, 0);
1560
1561 GtkWidget *category;
1562 GtkWidget *private;
1563 GtkWidget *parent;
1564 GtkTextBuffer *buffer = NULL;
1565 GtkWidget *vbox2 = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
1566 gtk_box_pack_start(GTK_BOX(vbox), vbox2, FALSE, TRUE, 0);
1567
1568 text = g_strdup_printf(_("add to: \"%s\" "), path);
1569 parent = gtk_check_button_new_with_label(text);
1570 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(parent), TRUE);
1571 gtk_box_pack_end(GTK_BOX(vbox2), parent, FALSE, TRUE, 0);
1572 dt_free(text);
1573
1574 category = gtk_check_button_new_with_label(_("category"));
1575 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(category), FALSE);
1576 gtk_box_pack_end(GTK_BOX(vbox2), category, FALSE, TRUE, 0);
1577 private = gtk_check_button_new_with_label(_("private"));
1578 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(private), FALSE);
1579 gtk_box_pack_end(GTK_BOX(vbox2), private, FALSE, TRUE, 0);
1580
1581 box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
1582 gtk_box_pack_end(GTK_BOX(vbox), box, TRUE, TRUE, 0);
1583 label = gtk_label_new(_("synonyms: "));
1584 gtk_box_pack_start(GTK_BOX(box), label, FALSE, TRUE, 0);
1585 GtkWidget *synonyms = gtk_text_view_new();
1587 dt_gui_textview_set_padding(GTK_TEXT_VIEW(synonyms));
1588 gtk_box_pack_end(GTK_BOX(box), synonyms, TRUE, TRUE, 0);
1589 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(synonyms), GTK_WRAP_WORD);
1590 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(synonyms));
1591
1592#ifdef GDK_WINDOWING_QUARTZ
1594#endif
1595 gtk_widget_show_all(dialog);
1596
1597 if(gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_YES)
1598 {
1599 const char *newtag = gtk_entry_get_text(GTK_ENTRY(entry));
1600 char *message = NULL;
1601 if(!newtag[0])
1602 message = _("empty tag is not allowed, aborting");
1603 char *new_tagname = NULL;
1604 const gboolean root = !gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(parent));
1605 if(!root)
1606 {
1607 new_tagname = g_strdup(path);
1608 new_tagname = dt_util_dstrcat(new_tagname, "|%s", newtag);
1609 }
1610 else new_tagname = g_strdup(newtag);
1611
1612 if(dt_tag_exists(new_tagname, NULL))
1613 message = _("tag name already exists. aborting.");
1614 if(message)
1615 {
1616 GtkWidget *warning_dialog = gtk_message_dialog_new(GTK_WINDOW(dialog), GTK_DIALOG_MODAL,
1617 GTK_MESSAGE_INFO, GTK_BUTTONS_CLOSE, "%s", message);
1618 gtk_dialog_run(GTK_DIALOG(warning_dialog));
1619 gtk_widget_destroy(warning_dialog);
1620 GtkWindow *dialog_parent = gtk_window_get_transient_for(GTK_WINDOW(dialog));
1621 gtk_widget_destroy(dialog);
1622 dt_gui_refocus_parent(dialog_parent);
1623 dt_free(tagname);
1624 return;
1625 }
1626 guint new_tagid = 0;
1627 if(dt_tag_new(new_tagname, &new_tagid))
1628 {
1629 const gint new_flags = ((gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(category)) ? DT_TF_CATEGORY : 0) |
1630 (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(private)) ? DT_TF_PRIVATE : 0));
1631 if(new_tagid) dt_tag_set_flags(new_tagid, new_flags);
1632 GtkTextIter start, end;
1633 gtk_text_buffer_get_start_iter(buffer, &start);
1634 gtk_text_buffer_get_end_iter(buffer, &end);
1635 gchar *new_synonyms_list = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1636 if(new_tagid && new_synonyms_list && new_synonyms_list[0])
1637 dt_tag_set_synonyms(new_tagid, new_synonyms_list);
1638 dt_free(new_synonyms_list);
1639 _init_treeview(self, 1);
1640 _show_tag_on_view(view, new_tagname, FALSE, TRUE);
1641 }
1642 dt_free(new_tagname);
1643 }
1644 _init_treeview(self, 0);
1645 GtkWindow *dialog_parent = gtk_window_get_transient_for(GTK_WINDOW(dialog));
1646 gtk_widget_destroy(dialog);
1647 dt_gui_refocus_parent(dialog_parent);
1648 dt_free(tagname);
1649}
1650
1651// edit tag allows the user to rename a single tag, which can be an element of the hierarchy and change other parameters
1653{
1655
1656 char *tagname;
1657 char *synonyms_list;
1658 gint tagid;
1659 gchar *text;
1660 GtkWidget *label;
1661 GtkTreeIter iter;
1662 GtkTreeModel *model = NULL;
1663 GtkTreeView *view = d->dictionary_view;
1664 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
1665 if(!_selection_get_first(selection, &model, &iter)) return;
1666
1667 gtk_tree_model_get(model, &iter, DT_LIB_TAGGING_COL_PATH, &tagname,
1668 DT_LIB_TAGGING_COL_SYNONYM, &synonyms_list, DT_LIB_TAGGING_COL_ID, &tagid, -1);
1669 char *subtag = g_strrstr(tagname, "|");
1670 if(subtag) subtag = subtag + 1;
1671 gint tag_count;
1672 gint img_count;
1673 dt_tag_count_tags_images(tagname, &tag_count, &img_count);
1674 if(tag_count == 0)
1675 {
1676 dt_free(tagname);
1677 dt_free(synonyms_list);
1678 return;
1679 }
1680
1682 GtkWidget *dialog = gtk_dialog_new_with_buttons(_("edit"), GTK_WINDOW(win), GTK_DIALOG_DESTROY_WITH_PARENT,
1683 _("cancel"), GTK_RESPONSE_NONE, _("save"), GTK_RESPONSE_YES, NULL);
1684 gtk_window_set_default_size(GTK_WINDOW(dialog), 300, -1);
1685 GtkWidget *area = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
1686 GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
1687 gtk_container_set_border_width(GTK_CONTAINER(vbox), 8);
1688 gtk_container_add(GTK_CONTAINER(area), vbox);
1689 text = g_strdup_printf(_("selected: %s"), tagname);
1690 label = gtk_label_new(text);
1691 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, TRUE, 0);
1692 dt_free(text);
1693
1694 GtkWidget *box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
1695 gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, TRUE, 0);
1696 text = g_strdup_printf(ngettext("<u>%d</u> tag will be updated", "<u>%d</u> tags will be updated", tag_count), tag_count);
1697 label = gtk_label_new(NULL);
1698 gtk_label_set_markup(GTK_LABEL(label), text);
1699 gtk_box_pack_start(GTK_BOX(box), label, FALSE, TRUE, 0);
1700 dt_free(text);
1701 text = g_strdup_printf(ngettext("<u>%d</u> image will be updated", "<u>%d</u> images will be updated", img_count), img_count);
1702 label = gtk_label_new(NULL);
1703 gtk_label_set_markup(GTK_LABEL(label), text);
1704 gtk_box_pack_start(GTK_BOX(box), label, FALSE, TRUE, 0);
1705 dt_free(text);
1706
1707 box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
1708 gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, TRUE, 0);
1709 label = gtk_label_new(_("name: "));
1710 gtk_box_pack_start(GTK_BOX(box), label, FALSE, TRUE, 0);
1711 GtkWidget *entry = gtk_entry_new();
1713 gtk_entry_set_text(GTK_ENTRY(entry), subtag ? subtag : tagname);
1714 gtk_box_pack_end(GTK_BOX(box), entry, TRUE, TRUE, 0);
1715
1716 gint flags = 0;
1717 GtkWidget *category = NULL;
1718 GtkWidget *private = gtk_check_button_new_with_label(_("private"));
1719 GtkTextBuffer *buffer = NULL;
1720
1721 if(tagid)
1722 {
1723 GtkWidget *vbox2 = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
1724 gtk_box_pack_start(GTK_BOX(vbox), vbox2, FALSE, TRUE, 0);
1725 flags = dt_tag_get_flags(tagid);
1726 category = gtk_check_button_new_with_label(_("category"));
1727 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(category), flags & DT_TF_CATEGORY);
1728 gtk_box_pack_end(GTK_BOX(vbox2), category, FALSE, TRUE, 0);
1729 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(private), flags & DT_TF_PRIVATE);
1730 gtk_box_pack_end(GTK_BOX(vbox2), private, FALSE, TRUE, 0);
1731
1732 box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
1733 gtk_box_pack_end(GTK_BOX(vbox), box, TRUE, TRUE, 0);
1734 label = gtk_label_new(_("synonyms: "));
1735 gtk_box_pack_start(GTK_BOX(box), label, FALSE, TRUE, 0);
1736 GtkWidget *synonyms = gtk_text_view_new();
1738 dt_gui_textview_set_padding(GTK_TEXT_VIEW(synonyms));
1739 gtk_box_pack_end(GTK_BOX(box), synonyms, TRUE, TRUE, 0);
1740 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(synonyms), GTK_WRAP_WORD);
1741 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(synonyms));
1742 if(synonyms_list) gtk_text_buffer_set_text(buffer, synonyms_list, -1);
1743 }
1744
1745#ifdef GDK_WINDOWING_QUARTZ
1747#endif
1748 gtk_widget_show_all(dialog);
1749
1750 if(gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_YES)
1751 {
1752 const char *newtag = gtk_entry_get_text(GTK_ENTRY(entry));
1753 if(g_strcmp0(newtag, subtag ? subtag : tagname) != 0)
1754 {
1755 // tag name has changed
1756 char *message = NULL;
1757 if(!newtag[0])
1758 message = _("empty tag is not allowed, aborting");
1759 if(strchr(newtag, '|') != 0)
1760 message = _("'|' character is not allowed for renaming tag.\nto modify the hierarchy use rename path instead. Aborting.");
1761 if(message)
1762 {
1763 GtkWidget *warning_dialog = gtk_message_dialog_new(GTK_WINDOW(dialog), GTK_DIALOG_MODAL,
1764 GTK_MESSAGE_INFO, GTK_BUTTONS_CLOSE, "%s", message);
1765 gtk_dialog_run(GTK_DIALOG(warning_dialog));
1766 gtk_widget_destroy(warning_dialog);
1767 GtkWindow *dialog_parent = gtk_window_get_transient_for(GTK_WINDOW(dialog));
1768 gtk_widget_destroy(dialog);
1769 dt_gui_refocus_parent(dialog_parent);
1770 dt_free(tagname);
1771 return;
1772 }
1773
1774 GList *tag_family = NULL;
1775 GList *tagged_images = NULL;
1776 dt_tag_get_tags_images(tagname, &tag_family, &tagged_images);
1777
1778 const int tagname_len = strlen(tagname);
1779 char *new_prefix_tag;
1780 if(subtag)
1781 {
1782 const int subtag_len = strlen(subtag);
1783 const char letter = tagname[tagname_len - subtag_len];
1784 tagname[tagname_len - subtag_len] = '\0';
1785 new_prefix_tag = g_strconcat(tagname, newtag, NULL);
1786 tagname[tagname_len - subtag_len] = letter;
1787 }
1788 else
1789 new_prefix_tag = (char *)newtag;
1790
1791 // check if one of the new tagnames already exists.
1792 gboolean tagname_exists = FALSE;
1793 for (GList *taglist = tag_family; taglist && !tagname_exists; taglist = g_list_next(taglist))
1794 {
1795 char *new_tagname = g_strconcat(new_prefix_tag, &((dt_tag_t *)taglist->data)->tag[tagname_len], NULL);
1796 tagname_exists = dt_tag_exists(new_tagname, NULL);
1797 if(tagname_exists)
1798 {
1799 GtkWidget *warning_dialog = gtk_message_dialog_new(GTK_WINDOW(dialog), GTK_DIALOG_MODAL,
1800 GTK_MESSAGE_INFO, GTK_BUTTONS_CLOSE,
1801 _("at least one new tag name (%s) already exists, aborting"), new_tagname);
1802 gtk_dialog_run(GTK_DIALOG(warning_dialog));
1803 gtk_widget_destroy(warning_dialog);
1804 dt_free(new_tagname);
1805 if(subtag)
1806 {
1807 dt_free(new_prefix_tag);
1808 }
1809 GtkWindow *dialog_parent = gtk_window_get_transient_for(GTK_WINDOW(dialog));
1810 gtk_widget_destroy(dialog);
1811 dt_gui_refocus_parent(dialog_parent);
1812 dt_free(tagname);
1813 return;
1814 };
1815 dt_free(new_tagname);
1816 }
1817
1818 // rename related tags
1819 for (GList *taglist = tag_family; taglist; taglist = g_list_next(taglist))
1820 {
1821 char *new_tagname = g_strconcat(new_prefix_tag, &((dt_tag_t *)taglist->data)->tag[tagname_len], NULL);
1822 dt_tag_rename(((dt_tag_t *)taglist->data)->id, new_tagname);
1823 dt_free(new_tagname);
1824 }
1825
1826 // update the store
1827 GtkTreeModel *store = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
1828 dt_tag_op_t *to = g_malloc(sizeof(dt_tag_op_t));
1829 to->tree_flag = d->tree_flag;
1830 to->oldtagname = tagname;
1831 to->newtagname = new_prefix_tag;
1832 gint sort_column;
1833 GtkSortType sort_order;
1834 gtk_tree_sortable_get_sort_column_id(GTK_TREE_SORTABLE(store), &sort_column, &sort_order);
1835 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(store), GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, GTK_SORT_ASCENDING);
1836 gtk_tree_model_foreach(store, (GtkTreeModelForeachFunc)_update_tag_name_per_name, to);
1837 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(store), sort_column, sort_order);
1838 dt_free(to);
1839 if(subtag)
1840 {
1841 dt_free(new_prefix_tag);
1842 }
1843
1845 dt_tag_free_result(&tag_family);
1846 dt_image_synch_xmps(tagged_images);
1847 g_list_free(tagged_images);
1848 tagged_images = NULL;
1849 }
1850
1851 if(tagid)
1852 {
1853 gint new_flags = ((gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(category)) ? DT_TF_CATEGORY : 0) |
1854 (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(private)) ? DT_TF_PRIVATE : 0));
1855 GtkTextIter start, end;
1856 gtk_text_buffer_get_start_iter(buffer, &start);
1857 gtk_text_buffer_get_end_iter(buffer, &end);
1858 gchar *new_synonyms_list = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1859 // refresh iter
1860 _selection_get_first(selection, &model, &iter);
1861 GtkTreeIter store_iter;
1862 GtkTreeModel *store = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
1863 gtk_tree_model_filter_convert_iter_to_child_iter(GTK_TREE_MODEL_FILTER(model),
1864 &store_iter, &iter);
1865 if(new_flags != (flags & (DT_TF_CATEGORY | DT_TF_PRIVATE)))
1866 {
1867 new_flags = (flags & ~(DT_TF_CATEGORY | DT_TF_PRIVATE)) | new_flags;
1868 dt_tag_set_flags(tagid, new_flags);
1869 if(!d->tree_flag)
1870 gtk_list_store_set(GTK_LIST_STORE(store), &store_iter, DT_LIB_TAGGING_COL_FLAGS, new_flags, -1);
1871 else
1872 gtk_tree_store_set(GTK_TREE_STORE(store), &store_iter, DT_LIB_TAGGING_COL_FLAGS, new_flags, -1);
1873 }
1874 if(new_synonyms_list && g_strcmp0(synonyms_list, new_synonyms_list) != 0)
1875 {
1876 dt_tag_set_synonyms(tagid, new_synonyms_list);
1877 if(!d->tree_flag)
1878 gtk_list_store_set(GTK_LIST_STORE(store), &store_iter, DT_LIB_TAGGING_COL_SYNONYM, new_synonyms_list, -1);
1879 else
1880 gtk_tree_store_set(GTK_TREE_STORE(store), &store_iter, DT_LIB_TAGGING_COL_SYNONYM, new_synonyms_list, -1);
1881 }
1882 dt_free(new_synonyms_list);
1883 }
1884 }
1885 _init_treeview(self, 0);
1886 GtkWindow *dialog_parent = gtk_window_get_transient_for(GTK_WINDOW(dialog));
1887 gtk_widget_destroy(dialog);
1888 dt_gui_refocus_parent(dialog_parent);
1889 dt_free(synonyms_list);
1890 dt_free(tagname);
1891}
1892
1893static gboolean _apply_rename_path(GtkWidget *dialog, const char *tagname,
1894 const char *newtag, dt_lib_module_t *self)
1895{
1897
1898 gboolean success = FALSE;
1899 GList *tag_family = NULL;
1900 GList *tagged_images = NULL;
1901 dt_tag_get_tags_images(tagname, &tag_family, &tagged_images);
1902
1903 // check if one of the new tagnames already exists.
1904 const int tagname_len = strlen(tagname);
1905 gboolean tagname_exists = FALSE;
1906 for(GList *taglist = tag_family; taglist && !tagname_exists; taglist = g_list_next(taglist))
1907 {
1908 char *new_tagname = g_strconcat(newtag, &((dt_tag_t *)taglist->data)->tag[tagname_len], NULL);
1909 tagname_exists = dt_tag_exists(new_tagname, NULL);
1910 if(tagname_exists)
1911 {
1912 GtkWidget *win;
1913 if(IS_NULL_PTR(dialog))
1915 else
1916 win = dialog;
1917
1918 GtkWidget *warning_dialog = gtk_message_dialog_new(GTK_WINDOW(win), GTK_DIALOG_MODAL,
1919 GTK_MESSAGE_INFO, GTK_BUTTONS_CLOSE,
1920 _("at least one new tagname (%s) already exists, aborting."), new_tagname);
1921 gtk_dialog_run(GTK_DIALOG(warning_dialog));
1922 gtk_widget_destroy(warning_dialog);
1923 }
1924 dt_free(new_tagname);
1925 }
1926
1927 if(!tagname_exists)
1928 {
1929 for (GList *taglist = tag_family; taglist; taglist = g_list_next(taglist))
1930 {
1931 char *new_tagname = g_strconcat(newtag, &((dt_tag_t *)taglist->data)->tag[tagname_len], NULL);
1932 dt_tag_rename(((dt_tag_t *)taglist->data)->id, new_tagname);
1933 dt_free(new_tagname);
1934 }
1935 _init_treeview(self, 0);
1936 _init_treeview(self, 1);
1937 dt_image_synch_xmps(tagged_images);
1939 _show_tag_on_view(d->dictionary_view, newtag, FALSE, TRUE);
1940 success = TRUE;
1941 }
1942 dt_tag_free_result(&tag_family);
1943 g_list_free(tagged_images);
1944 tagged_images = NULL;
1945
1946 return success;
1947}
1948
1949// rename path allows the user to redefine a hierarchy
1951{
1953
1954 char *tagname;
1955 gint tagid;
1956 gchar *text;
1957 GtkWidget *label;
1958 GtkTreeIter iter;
1959 GtkTreeModel *model = NULL;
1960 GtkTreeView *view = d->dictionary_view;
1961 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
1962 if(!_selection_get_first(selection, &model, &iter)) return;
1963
1964 gtk_tree_model_get(model, &iter, DT_LIB_TAGGING_COL_PATH, &tagname,
1965 DT_LIB_TAGGING_COL_ID, &tagid, -1);
1966
1967 gint tag_count = 0;
1968 gint img_count = 0;
1969 dt_tag_count_tags_images(tagname, &tag_count, &img_count);
1970 if(tag_count == 0) return;
1971
1973 GtkWidget *dialog = gtk_dialog_new_with_buttons(_("change path"), GTK_WINDOW(win), GTK_DIALOG_DESTROY_WITH_PARENT,
1974 _("cancel"), GTK_RESPONSE_NONE, _("save"), GTK_RESPONSE_YES, NULL);
1975 gtk_window_set_default_size(GTK_WINDOW(dialog), 300, -1);
1976 GtkWidget *area = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
1977 GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
1978 gtk_container_set_border_width(GTK_CONTAINER(vbox), 8);
1979 gtk_container_add(GTK_CONTAINER(area), vbox);
1980 text = g_strdup_printf(_("selected: %s"), tagname);
1981 label = gtk_label_new(text);
1982 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, TRUE, 0);
1983 dt_free(text);
1984
1985 GtkWidget *box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
1986 gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, TRUE, 0);
1987 text = g_strdup_printf(ngettext("<u>%d</u> tag will be updated", "<u>%d</u> tags will be updated", tag_count), tag_count);
1988 label = gtk_label_new(NULL);
1989 gtk_label_set_markup(GTK_LABEL(label), text);
1990 gtk_box_pack_start(GTK_BOX(box), label, FALSE, TRUE, 0);
1991 dt_free(text);
1992 text = g_strdup_printf(ngettext("<u>%d</u> image will be updated", "<u>%d</u> images will be updated", img_count), img_count);
1993 label = gtk_label_new(NULL);
1994 gtk_label_set_markup(GTK_LABEL(label), text);
1995 gtk_box_pack_start(GTK_BOX(box), label, FALSE, TRUE, 0);
1996 dt_free(text);
1997
1998 GtkWidget *entry = gtk_entry_new();
2000 gtk_entry_set_text(GTK_ENTRY(entry), tagname);
2001 gtk_box_pack_start(GTK_BOX(vbox), entry, FALSE, TRUE, 0);
2002
2003#ifdef GDK_WINDOWING_QUARTZ
2005#endif
2006 gtk_widget_show_all(dialog);
2007
2008 if(gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_YES)
2009 {
2010 const char *newtag = gtk_entry_get_text(GTK_ENTRY(entry));
2011 if(g_strcmp0(newtag, tagname) == 0)
2012 return; // no change
2013 char *message = NULL;
2014 if(!newtag[0])
2015 message = _("empty tag is not allowed, aborting");
2016 if(strchr(newtag, '|') == &newtag[0] || strchr(newtag, '|') == &newtag[strlen(newtag)-1] || strstr(newtag, "||"))
2017 message = _("'|' misplaced, empty tag is not allowed, aborting");
2018 if(message)
2019 {
2020 GtkWidget *warning_dialog = gtk_message_dialog_new(GTK_WINDOW(dialog), GTK_DIALOG_MODAL,
2021 GTK_MESSAGE_INFO, GTK_BUTTONS_CLOSE, "%s", message);
2022 gtk_dialog_run(GTK_DIALOG(warning_dialog));
2023 gtk_widget_destroy(warning_dialog);
2024 GtkWindow *dialog_parent = gtk_window_get_transient_for(GTK_WINDOW(dialog));
2025 gtk_widget_destroy(dialog);
2026 dt_gui_refocus_parent(dialog_parent);
2027 dt_free(tagname);
2028 return;
2029 }
2030 _apply_rename_path(dialog, tagname, newtag, self);
2031 }
2032 GtkWindow *dialog_parent = gtk_window_get_transient_for(GTK_WINDOW(dialog));
2033 gtk_widget_destroy(dialog);
2034 dt_gui_refocus_parent(dialog_parent);
2035 dt_free(tagname);
2036}
2037
2039{
2041 GtkTreeIter iter;
2042 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(d->dictionary_view));
2043 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(d->dictionary_view));
2044 if(_selection_get_first(selection, &model, &iter))
2045 {
2046 char *path;
2047 guint count;
2048 gtk_tree_model_get(model, &iter, DT_LIB_TAGGING_COL_PATH, &path, DT_LIB_TAGGING_COL_COUNT, &count, -1);
2049 if(count)
2050 {
2051 if(!d->collection[0]) dt_collection_serialize(d->collection, 4096);
2052 gchar *tag_collection = g_strdup_printf("1:0:%d:%s$", DT_COLLECTION_PROP_TAG, path);
2054 dt_collection_deserialize(tag_collection);
2056 dt_free(tag_collection);
2057 }
2058 dt_free(path);
2059 }
2060}
2061
2063{
2065 if(d->collection[0])
2066 {
2068 dt_collection_deserialize(d->collection);
2070 d->collection[0] = '\0';
2071 }
2072}
2073
2075{
2077 GtkTreeIter iter;
2078 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(d->dictionary_view));
2079 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(d->dictionary_view));
2080 if(_selection_get_first(selection, &model, &iter))
2081 {
2082 char *tag;
2083 gtk_tree_model_get(model, &iter, DT_LIB_TAGGING_COL_PATH, &tag, -1);
2084 gtk_entry_set_text(d->dict_entry, tag);
2085 dt_free(tag);
2086 gtk_entry_grab_focus_without_selecting(d->dict_entry);
2087 }
2088}
2089
2090// delete the given tag ids from the database (after a single confirmation), then refresh
2091static void _delete_tagids(GList *tagids, dt_lib_module_t *self)
2092{
2093 if(IS_NULL_PTR(tagids)) return;
2094 const guint nb = g_list_length(tagids);
2095
2096 // dry-run removal (FALSE) to count how many images would lose a tag
2097 guint img_count = 0;
2098 for(GList *t = tagids; t; t = g_list_next(t))
2099 img_count += dt_tag_remove(GPOINTER_TO_UINT(t->data), FALSE);
2100
2101 if(img_count > 0 || dt_conf_get_bool("plugins/lighttable/tagging/ask_before_delete_tag"))
2102 {
2104 GtkWidget *dialog = gtk_dialog_new_with_buttons(_("delete tag?"), GTK_WINDOW(win), GTK_DIALOG_DESTROY_WITH_PARENT,
2105 _("cancel"), GTK_RESPONSE_NONE, _("delete"), GTK_RESPONSE_YES, NULL);
2106 gtk_window_set_default_size(GTK_WINDOW(dialog), 300, -1);
2107 GtkWidget *area = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
2108 gchar *text = g_strdup_printf(ngettext("do you really want to delete %d tag?\n%d image is assigned these tags!",
2109 "do you really want to delete %d tags?\n%d images are assigned these tags!",
2110 img_count), nb, img_count);
2111 GtkWidget *label = gtk_label_new(text);
2112 gtk_container_set_border_width(GTK_CONTAINER(area), 8);
2113 gtk_container_add(GTK_CONTAINER(area), label);
2114 dt_free(text);
2115#ifdef GDK_WINDOWING_QUARTZ
2117#endif
2118 gtk_widget_show_all(dialog);
2119 const int res = gtk_dialog_run(GTK_DIALOG(dialog));
2120 GtkWindow *dialog_parent = gtk_window_get_transient_for(GTK_WINDOW(dialog));
2121 gtk_widget_destroy(dialog);
2122 dt_gui_refocus_parent(dialog_parent);
2123 if(res != GTK_RESPONSE_YES) return;
2124 }
2125
2126 GList *affected = NULL;
2127 for(GList *t = tagids; t; t = g_list_next(t))
2128 {
2129 const guint tagid = GPOINTER_TO_UINT(t->data);
2130 affected = g_list_concat(affected, dt_tag_get_images(tagid));
2131 gchar *name = dt_tag_get_name(tagid);
2132 dt_tag_remove(tagid, TRUE);
2133 if(name)
2134 {
2135 dt_control_log(_("tag %s removed"), name);
2136 dt_free(name);
2137 }
2138 }
2139
2140 _init_treeview(self, 0);
2141 _init_treeview(self, 1);
2144 dt_image_synch_xmps(affected);
2145 g_list_free(affected);
2147}
2148
2150{
2152 GList *tagids = _selected_tagids(d->dictionary_view);
2153 _delete_tagids(tagids, self);
2154 g_list_free(tagids);
2155}
2156
2158{
2160 char *tagname;
2161 guint new_tagid;
2162
2163 GtkTreeIter iter;
2164 GtkTreeModel *model = NULL;
2165 GtkTreeView *view = d->dictionary_view;
2166 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
2167 if(!_selection_get_first(selection, &model, &iter)) return;
2168
2169 gtk_tree_model_get(model, &iter, DT_LIB_TAGGING_COL_PATH, &tagname, -1);
2170
2171 dt_tag_new(tagname, &new_tagid);
2172 dt_control_log(_("tag %s created"), tagname);
2173
2174 _init_treeview(self, 1);
2175 _show_tag_on_view(d->dictionary_view, tagname, FALSE, TRUE);
2176 dt_free(tagname);
2177
2178}
2179
2180static void _pop_menu_dictionary(GtkWidget *treeview, GdkEventButton *event, dt_lib_module_t *self)
2181{
2183 GtkTreeIter iter, child;
2184 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(d->dictionary_view));
2185 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(d->dictionary_view));
2186 const gint sel_cnt = gtk_tree_selection_count_selected_rows(selection);
2187 if(sel_cnt == 0) return;
2188
2189 GtkWidget *menu = gtk_menu_new();
2190 GtkWidget *menuitem;
2191
2192 // with several tags selected, only bulk CRUD makes sense
2193 if(sel_cnt > 1)
2194 {
2195 menuitem = gtk_menu_item_new_with_label(_("delete tags"));
2196 g_signal_connect(menuitem, "activate", (GCallback)_pop_menu_dictionary_delete_selected, self);
2197 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
2198 gtk_widget_show_all(GTK_WIDGET(menu));
2199 gtk_menu_popup_at_pointer(GTK_MENU(menu), (GdkEvent *)event);
2200 return;
2201 }
2202
2203 if(_selection_get_first(selection, &model, &iter))
2204 {
2205 guint tagid, count;
2206 gtk_tree_model_get(model, &iter,
2207 DT_LIB_TAGGING_COL_ID, &tagid,
2208 DT_LIB_TAGGING_COL_COUNT, &count, -1);
2209
2210 if(d->tree_flag || !d->suggestion_flag)
2211 {
2212 menuitem = gtk_menu_item_new_with_label(_("create tag..."));
2213 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
2214 g_signal_connect(menuitem, "activate", (GCallback)_pop_menu_dictionary_create_tag, self);
2215
2216 if(tagid)
2217 {
2218 menuitem = gtk_menu_item_new_with_label(_("delete tag"));
2219 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
2220 g_signal_connect(menuitem, "activate", (GCallback)_pop_menu_dictionary_delete_selected, self);
2221 }
2222
2223 if(gtk_tree_model_iter_children(model, &child, &iter))
2224 {
2225 menuitem = gtk_menu_item_new_with_label(_("delete node"));
2226 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
2227 g_signal_connect(menuitem, "activate", (GCallback)_pop_menu_dictionary_delete_node, self);
2228 }
2229
2230 menuitem = gtk_menu_item_new_with_label(_("edit..."));
2231 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
2232 g_signal_connect(menuitem, "activate", (GCallback)_pop_menu_dictionary_edit_tag, self);
2233
2234 }
2235
2236 if(d->tree_flag)
2237 {
2238 menuitem = gtk_menu_item_new_with_label(_("change path..."));
2239 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
2240 g_signal_connect(menuitem, "activate", (GCallback)_pop_menu_dictionary_change_path, self);
2241 }
2242
2243 if(d->tree_flag && !tagid)
2244 {
2245 menuitem = gtk_separator_menu_item_new();
2246 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
2247
2248 menuitem = gtk_menu_item_new_with_label(_("set as a tag"));
2249 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
2250 g_signal_connect(menuitem, "activate", (GCallback)_pop_menu_dictionary_set_as_tag, self);
2251 }
2252
2253 if(!d->suggestion_flag)
2254 {
2255 menuitem = gtk_separator_menu_item_new();
2256 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
2257 }
2258
2259 menuitem = gtk_menu_item_new_with_label(_("copy to entry"));
2260 g_signal_connect(menuitem, "activate", (GCallback)_pop_menu_dictionary_copy_tag, self);
2261 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
2262
2263 if(d->collection[0])
2264 {
2265 char *collection = g_malloc(4096);
2266 dt_collection_serialize(collection, 4096);
2267 if(g_strcmp0(d->collection, collection) == 0) d->collection[0] = '\0';
2268 dt_free(collection);
2269 }
2270 if(count || d->collection[0])
2271 {
2272 menuitem = gtk_separator_menu_item_new();
2273 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
2274 if(count)
2275 {
2276 menuitem = gtk_menu_item_new_with_label(_("go to tag collection"));
2277 g_signal_connect(menuitem, "activate", (GCallback)_pop_menu_dictionary_goto_tag_collection, self);
2278 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
2279 }
2280 if(d->collection[0])
2281 {
2282 menuitem = gtk_menu_item_new_with_label(_("go back to work"));
2283 g_signal_connect(menuitem, "activate", (GCallback)_pop_menu_dictionary_goto_collection_back, self);
2284 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
2285 }
2286 }
2287 gtk_widget_show_all(GTK_WIDGET(menu));
2288
2289 gtk_menu_popup_at_pointer(GTK_MENU(menu), (GdkEvent *)event);
2290 }
2291}
2292
2293static gboolean _click_on_view_dictionary(GtkWidget *view, GdkEventButton *event, dt_lib_module_t *self)
2294{
2296 _unselect_all_in_view(d->attached_view);
2297
2298 const int button_pressed = (event->type == GDK_BUTTON_PRESS) ? event->button : 0;
2299 const gboolean shift_pressed = dt_modifier_is(event->state, GDK_SHIFT_MASK);
2300 if((button_pressed == 3) // contextual menu
2301 || (d->tree_flag && button_pressed == 1)) // expand & drag
2302 {
2303 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
2304 GtkTreePath *path = NULL;
2305 // Get tree path for row that was clicked
2306 if(gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(view), (gint)event->x, (gint)event->y, &path, NULL, NULL, NULL))
2307 {
2308 if(d->tree_flag && button_pressed == 1 && !shift_pressed)
2309 {
2310 GtkTreeModel *model = gtk_tree_view_get_model(d->dictionary_view);
2311 GtkTreeIter iter;
2312 gtk_tree_model_get_iter(model, &iter, path);
2313 char *tagname;
2314 gtk_tree_model_get(model, &iter,
2315 DT_LIB_TAGGING_COL_PATH, &tagname, -1);
2316 if(d->drag.tagname)
2317 {
2318 dt_free(d->drag.tagname);
2319 }
2320 d->drag.tagname = tagname;
2321 if(d->drag.path) gtk_tree_path_free(d->drag.path);
2322 d->drag.path = path;
2323 if(d->drag.lastpath) gtk_tree_path_free(d->drag.lastpath);
2324 d->drag.lastpath = NULL;
2325 return FALSE;
2326 }
2327 else if(button_pressed == 3)
2328 {
2329 // keep an existing multi-selection so the menu can act on it in bulk
2330 if(!gtk_tree_selection_path_is_selected(selection, path))
2331 {
2332 gtk_tree_selection_unselect_all(selection);
2333 gtk_tree_selection_select_path(selection, path);
2334 }
2335 _pop_menu_dictionary(view, event, self);
2336 gtk_tree_path_free(path);
2337 return TRUE;
2338 }
2339 else if(d->tree_flag && button_pressed == 1 && shift_pressed)
2340 {
2341 gtk_tree_selection_select_path(selection, path);
2342 gtk_tree_view_expand_row(GTK_TREE_VIEW(view), path, TRUE);
2343 gtk_tree_path_free(path);
2344 return TRUE;
2345 }
2346 }
2347 gtk_tree_path_free(path);
2348 }
2349 return FALSE;
2350}
2351
2352static gboolean _dictionary_key_pressed(GtkWidget *view, GdkEventKey *event, dt_lib_module_t *self)
2353{
2355 _unselect_all_in_view(d->attached_view);
2356 GtkTreeIter iter;
2357 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(d->dictionary_view));
2358 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
2359 gboolean res = FALSE;
2360 guint key = dt_keys_mainpad_alternatives(event->keyval);
2361
2362 if(_selection_get_first(selection, &model, &iter))
2363 {
2364 GtkTreePath *path = gtk_tree_model_get_path(model, &iter);
2365 switch(key)
2366 {
2367 case GDK_KEY_Left:
2368 if(path)
2369 {
2370 if(dt_modifier_is(event->state, GDK_SHIFT_MASK))
2371 gtk_tree_view_collapse_all(GTK_TREE_VIEW(view));
2372 else
2373 gtk_tree_view_collapse_row(GTK_TREE_VIEW(view), path);
2374 res = TRUE;
2375 }
2376 break;
2377 case GDK_KEY_Right:
2378 if(path)
2379 {
2380 gtk_tree_view_expand_row(GTK_TREE_VIEW(view), path,
2381 dt_modifier_is(event->state, GDK_SHIFT_MASK));
2382 res = TRUE;
2383 }
2384 break;
2385 default:
2386 break;
2387 }
2388 gtk_tree_path_free(path);
2389 }
2390 if(event->keyval == GDK_KEY_Tab)
2391 {
2392 gtk_tree_selection_unselect_all(selection);
2393 res = TRUE;
2394 }
2395 else if(event->keyval == GDK_KEY_ISO_Left_Tab)
2396 {
2397 gtk_tree_selection_unselect_all(selection);
2398 gtk_widget_grab_focus(GTK_WIDGET(d->dict_entry));
2399 res = TRUE;
2400 }
2401 return res;
2402}
2403
2404static gboolean _row_tooltip_setup(GtkWidget *treeview, gint x, gint y, gboolean kb_mode,
2405 GtkTooltip* tooltip, dt_lib_module_t *self)
2406{
2407 gboolean res = FALSE;
2408 GtkTreePath *path = NULL;
2409 // Get tree path mouse position
2410 if(gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(treeview), x, y, &path, NULL, NULL, NULL))
2411 {
2412 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(treeview));
2413 GtkTreeIter iter;
2414 if(gtk_tree_model_get_iter(model, &iter, path))
2415 {
2416 char *tagname;
2417 guint tagid;
2418 guint flags;
2419 char *synonyms = NULL;
2420 gtk_tree_model_get(model, &iter, DT_LIB_TAGGING_COL_ID, &tagid, DT_LIB_TAGGING_COL_TAG, &tagname,
2422 if(tagid)
2423 {
2424 if((flags & DT_TF_PRIVATE) || (synonyms && synonyms[0]))
2425 {
2426 gchar *text = g_strdup_printf(_("%s"), tagname);
2427 text = dt_util_dstrcat(text, " %s\n", (flags & DT_TF_PRIVATE) ? _("(private)") : "");
2428 text = dt_util_dstrcat(text, "synonyms: %s", (synonyms && synonyms[0]) ? synonyms : " - ");
2429 gtk_tooltip_set_text(tooltip, text);
2430 dt_free(text);
2431 res = TRUE;
2432 }
2433 }
2434 dt_free(synonyms);
2435 dt_free(tagname);
2436 }
2437 }
2438 gtk_tree_path_free(path);
2439
2440 return res;
2441}
2442
2443static void _import_button_clicked(GtkButton *button, dt_lib_module_t *self)
2444{
2445 const char *last_dirname = dt_conf_get_string_const("plugins/lighttable/tagging/last_import_export_location");
2446 if(IS_NULL_PTR(last_dirname) || !*last_dirname)
2447 {
2448 last_dirname = g_get_home_dir();
2449 }
2450
2452 GtkFileChooserNative *filechooser = gtk_file_chooser_native_new(
2453 _("select a keyword file"), GTK_WINDOW(win), GTK_FILE_CHOOSER_ACTION_OPEN,
2454 _("_import"), _("_cancel"));
2455
2456 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(filechooser), last_dirname);
2457 gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(filechooser), FALSE);
2458
2459 if(gtk_native_dialog_run(GTK_NATIVE_DIALOG(filechooser)) == GTK_RESPONSE_ACCEPT)
2460 {
2461 char *filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(filechooser));
2462 char *dirname = g_path_get_dirname(filename);
2463 dt_conf_set_string("plugins/lighttable/tagging/last_import_export_location", dirname);
2464 uint32_t count = dt_tag_import(filename);
2465 dt_control_log(_("%u tags imported"), count);
2466 dt_free(filename);
2467 dt_free(dirname);
2468 }
2469
2470 g_object_unref(filechooser);
2471 _init_treeview(self, 1);
2472}
2473
2474static void _export_button_clicked(GtkButton *button, dt_lib_module_t *self)
2475{
2476 GDateTime *now = g_date_time_new_now_local();
2477 char *export_filename = g_date_time_format(now, "darktable_tags_%F_%H-%M.txt");
2478 const char *last_dirname = dt_conf_get_string_const("plugins/lighttable/tagging/last_import_export_location");
2479 if(IS_NULL_PTR(last_dirname) || !*last_dirname)
2480 {
2481 last_dirname = g_get_home_dir();
2482 }
2483
2485 GtkFileChooserNative *filechooser = gtk_file_chooser_native_new(
2486 _("select file to export to"), GTK_WINDOW(win), GTK_FILE_CHOOSER_ACTION_SAVE,
2487 _("_export"), _("_cancel"));
2488
2489 gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(filechooser), TRUE);
2490 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(filechooser), last_dirname);
2491 gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(filechooser), export_filename);
2492
2493 if(gtk_native_dialog_run(GTK_NATIVE_DIALOG(filechooser)) == GTK_RESPONSE_ACCEPT)
2494 {
2495 char *filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(filechooser));
2496 char *dirname = g_path_get_dirname(filename);
2497 dt_conf_set_string("plugins/lighttable/tagging/last_import_export_location", dirname);
2498 const uint32_t count = dt_tag_export(filename);
2499 dt_control_log(_("%u tags exported"), count);
2500 dt_free(filename);
2501 dt_free(dirname);
2502 }
2503
2504 g_date_time_unref(now);
2505 dt_free(export_filename);
2506 g_object_unref(filechooser);
2507}
2508
2510{
2512 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(d->dictionary_view));
2513
2515
2516 d->suggestion_flag = dt_conf_get_bool("plugins/lighttable/tagging/nosuggestion");
2517 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->toggle_suggestion_button), d->suggestion_flag);
2518 d->tree_flag = dt_conf_get_bool("plugins/lighttable/tagging/treeview");
2519 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->toggle_tree_button), d->tree_flag);
2520
2521 if(d->tree_flag)
2522 {
2523 if(model == GTK_TREE_MODEL(d->dictionary_listfilter))
2524 {
2525 g_object_ref(model);
2526 gtk_tree_view_set_model(GTK_TREE_VIEW(d->dictionary_view), NULL);
2527 GtkTreeModel *store = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
2528 gtk_list_store_clear(GTK_LIST_STORE(store));
2529 gtk_tree_view_set_model(GTK_TREE_VIEW(d->dictionary_view), GTK_TREE_MODEL(d->dictionary_treefilter));
2530 g_object_unref(d->dictionary_treefilter);
2531 }
2532 gtk_widget_set_sensitive(GTK_WIDGET(d->toggle_suggestion_button), FALSE);
2533 }
2534 else
2535 {
2536 if(model == GTK_TREE_MODEL(d->dictionary_treefilter))
2537 {
2538 g_object_ref(model);
2539 gtk_tree_view_set_model(GTK_TREE_VIEW(d->dictionary_view), NULL);
2540 GtkTreeModel *store = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
2541 gtk_tree_store_clear(GTK_TREE_STORE(store));
2542 gtk_tree_view_set_model(GTK_TREE_VIEW(d->dictionary_view), GTK_TREE_MODEL(d->dictionary_listfilter));
2543 g_object_unref(d->dictionary_listfilter);
2544 }
2545 gtk_widget_set_sensitive(GTK_WIDGET(d->toggle_suggestion_button), TRUE);
2546 }
2547
2548 // drag & drop
2549 if(d->tree_flag)
2550 gtk_drag_source_set(GTK_WIDGET(d->dictionary_view), GDK_BUTTON1_MASK,
2551 target_list_tags, n_targets_tags, GDK_ACTION_MOVE);
2552 else
2553 gtk_drag_source_unset(GTK_WIDGET(d->dictionary_view));
2554
2555 d->sort_count_flag = dt_conf_get_bool("plugins/lighttable/tagging/listsortedbycount");
2556 dt_bauhaus_combobox_set(d->sort_combo, d->sort_count_flag ? 1 : 0);
2557
2558 // controls the dictionary list rendering (leaf vs full path); the sidebar attached
2559 // view has its own tree/list "view" combobox below
2560 d->hide_path_flag = dt_conf_get_bool("plugins/lighttable/tagging/hidehierarchy");
2561
2562 d->attached_tree_flag = dt_conf_get_bool("plugins/lighttable/tagging/attached_treeview");
2563 dt_bauhaus_combobox_set(d->attached_view_combo, d->attached_tree_flag ? 1 : 0);
2564
2565 d->dttags_flag = dt_conf_get_bool("plugins/lighttable/tagging/dttags");
2566 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->dttags_check), d->dttags_flag);
2567
2569}
2570
2571static void _toggle_suggestion_button_callback(GtkToggleButton *source, dt_lib_module_t *self)
2572{
2573 if(dt_gui_widgets_suppressed()) return;
2574 const gboolean new_state = !dt_conf_get_bool("plugins/lighttable/tagging/nosuggestion");
2575 dt_conf_set_bool("plugins/lighttable/tagging/nosuggestion", new_state);
2576 _update_layout(self);
2577 _init_treeview(self, 1);
2578}
2579
2580static void _toggle_tree_button_callback(GtkToggleButton *source, dt_lib_module_t *self)
2581{
2582 if(dt_gui_widgets_suppressed()) return;
2583 const gboolean new_state = !dt_conf_get_bool("plugins/lighttable/tagging/treeview");
2584 dt_conf_set_bool("plugins/lighttable/tagging/treeview", new_state);
2585 _update_layout(self);
2586 _init_treeview(self, 1);
2587}
2588
2589static gint _sort_tree_count_func(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, dt_lib_module_t *self)
2590{
2591 guint count_a = 0;
2592 guint count_b = 0;
2593 gtk_tree_model_get(model, a, DT_LIB_TAGGING_COL_COUNT, &count_a, -1);
2594 gtk_tree_model_get(model, b, DT_LIB_TAGGING_COL_COUNT, &count_b, -1);
2595 return (count_b - count_a);
2596}
2597
2598static gint _sort_tree_tag_func(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, dt_lib_module_t *self)
2599{
2600 char *tag_a = NULL;
2601 char *tag_b = NULL;
2602 gtk_tree_model_get(model, a, DT_LIB_TAGGING_COL_TAG, &tag_a, -1);
2603 gtk_tree_model_get(model, b, DT_LIB_TAGGING_COL_TAG, &tag_b, -1);
2604 if(IS_NULL_PTR(tag_a)) tag_a = g_strdup("");
2605 if(IS_NULL_PTR(tag_b)) tag_b = g_strdup("");
2606 const gboolean sort = g_strcmp0(tag_a, tag_b);
2607 dt_free(tag_a);
2608 dt_free(tag_b);
2609 return sort;
2610}
2611
2612static gint _sort_tree_path_func(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, dt_lib_module_t *self)
2613{
2614 char *tag_a = NULL;
2615 char *tag_b = NULL;
2616 gtk_tree_model_get(model, a, DT_LIB_TAGGING_COL_PATH, &tag_a, -1);
2617 gtk_tree_model_get(model, b, DT_LIB_TAGGING_COL_PATH, &tag_b, -1);
2618 if(tag_a)
2619 {
2620 for(char *letter = tag_a; *letter; letter++)
2621 if(*letter == '|') *letter = '\1';
2622 }
2623 else
2624 tag_a = g_strdup("");
2625
2626 if(tag_b)
2627 {
2628 for(char *letter = tag_b; *letter; letter++)
2629 if(*letter == '|') *letter = '\1';
2630 }
2631 else
2632 tag_b = g_strdup("");
2633
2634 const gboolean sort = g_strcmp0(tag_a, tag_b);
2635 dt_free(tag_a);
2636 dt_free(tag_b);
2637 return sort;
2638}
2639
2640// "sort" combobox in the sidebar: by name (0) or by count (1)
2642{
2643 if(dt_gui_widgets_suppressed()) return;
2644 dt_conf_set_bool("plugins/lighttable/tagging/listsortedbycount", dt_bauhaus_combobox_get(combo) == 1);
2645 _update_layout(self);
2648}
2649
2650// "view" combobox in the sidebar: attached list (0) or attached tree (1)
2652{
2653 if(dt_gui_widgets_suppressed()) return;
2654 dt_conf_set_bool("plugins/lighttable/tagging/attached_treeview", dt_bauhaus_combobox_get(combo) == 1);
2655 _update_layout(self);
2656 _init_treeview(self, 0);
2657}
2658
2659// "show system tags" checkbox in the sidebar
2660static void _dttags_check_toggled(GtkToggleButton *source, dt_lib_module_t *self)
2661{
2662 if(dt_gui_widgets_suppressed()) return;
2664 d->dttags_flag = gtk_toggle_button_get_active(source);
2665 dt_conf_set_bool("plugins/lighttable/tagging/dttags", d->dttags_flag);
2666 _init_treeview(self, 0);
2667}
2668
2670{
2672 // clear entry boxes and query
2673 gtk_entry_set_text(d->entry, "");
2674 gtk_entry_set_text(d->dict_entry, "");
2675 _set_keyword(self);
2676 _init_treeview(self, 1);
2678}
2679
2681{
2682 return 3;
2683}
2684
2685// Single-column model feeding the tag entry autocompletion (full tag path).
2687
2688// (Re)populate the entry autocompletion store with every known tag.
2690{
2692 if(!d->completion_store) return;
2693 gtk_list_store_clear(d->completion_store);
2694 GList *tags = NULL;
2695 dt_tag_get_with_usage(&tags);
2696 for(GList *t = tags; t; t = g_list_next(t))
2697 {
2698 const dt_tag_t *tag = (const dt_tag_t *)t->data;
2699 if(IS_NULL_PTR(tag->tag)) continue;
2700 GtkTreeIter iter;
2701 gtk_list_store_append(d->completion_store, &iter);
2702 gtk_list_store_set(d->completion_store, &iter, DT_COMPL_COL_PATH, tag->tag, -1);
2703 }
2704 dt_tag_free_result(&tags);
2705}
2706
2707// (Re)build the set of tags used by the current collection. Used to seed the
2708// entry autocompletion when nothing has been typed yet.
2710{
2712 if(!d->collection_tags) return;
2713 g_hash_table_remove_all(d->collection_tags);
2714 GList *tags = NULL;
2716 for(GList *t = tags; t; t = g_list_next(t))
2717 {
2718 const dt_tag_t *tag = (const dt_tag_t *)t->data;
2719 if(IS_NULL_PTR(tag->tag)) continue;
2720 g_hash_table_add(d->collection_tags, g_strdup(tag->tag));
2721 }
2722 dt_tag_free_result(&tags);
2723}
2724
2725static gboolean _match_selected_func(GtkEntryCompletion *completion, GtkTreeModel *model, GtkTreeIter *iter, gpointer user_data)
2726{
2727 const int column = gtk_entry_completion_get_text_column(completion);
2728 char *tag = NULL;
2729
2730 if(gtk_tree_model_get_column_type(model, column) != G_TYPE_STRING) return TRUE;
2731
2732 GtkEditable *e = (GtkEditable *)gtk_entry_completion_get_entry(completion);
2733 if(!GTK_IS_EDITABLE(e))
2734 {
2735 return FALSE;
2736 }
2737
2738 gtk_tree_model_get(model, iter, column, &tag, -1);
2739
2740 gint cut_off, cur_pos = gtk_editable_get_position(e);
2741
2742 gchar *currentText = gtk_editable_get_chars(e, 0, -1);
2743 const gchar *lastTag = g_strrstr(currentText, ",");
2744 if(IS_NULL_PTR(lastTag))
2745 {
2746 cut_off = 0;
2747 }
2748 else
2749 {
2750 cut_off = (int)(g_utf8_strlen(currentText, -1) - g_utf8_strlen(lastTag, -1))+1;
2751 }
2752 dt_free(currentText);
2753
2754 gtk_editable_delete_text(e, cut_off, cur_pos);
2755 cur_pos = cut_off;
2756 gtk_editable_insert_text(e, tag, -1, &cur_pos);
2757 gtk_editable_set_position(e, cur_pos);
2758 dt_free(tag); // release result of gtk_tree_model_get
2759 return TRUE;
2760}
2761
2762static gboolean _completion_match_func(GtkEntryCompletion *completion, const gchar *key, GtkTreeIter *iter,
2763 gpointer user_data)
2764{
2765 gboolean res = FALSE;
2766 dt_lib_module_t *self = (dt_lib_module_t *)user_data;
2767 dt_lib_tagging_t *d = self ? (dt_lib_tagging_t *)self->data : NULL;
2768
2769 GtkEditable *e = (GtkEditable *)gtk_entry_completion_get_entry(completion);
2770
2771 if(!GTK_IS_EDITABLE(e))
2772 {
2773 return FALSE;
2774 }
2775
2776 const gint cur_pos = gtk_editable_get_position(e);
2777 const gboolean onLastTag = (g_strstr_len(&key[cur_pos], -1, ",") == NULL);
2778 if(!onLastTag)
2779 {
2780 return FALSE;
2781 }
2782
2783 GtkTreeModel *model = gtk_entry_completion_get_model(completion);
2784 const int column = gtk_entry_completion_get_text_column(completion);
2785
2786 if(gtk_tree_model_get_column_type(model, column) != G_TYPE_STRING)
2787 {
2788 return FALSE;
2789 }
2790
2791 const gchar *lastTag = g_strrstr(key, ",");
2792 if(!IS_NULL_PTR(lastTag))
2793 {
2794 lastTag++;
2795 }
2796 else
2797 {
2798 lastTag = key;
2799 }
2800
2801 char *tag = NULL;
2802 gtk_tree_model_get(model, iter, column, &tag, -1);
2803
2804 // nothing typed yet on the active tag token: default the suggestions to the
2805 // tags used by the current collection opened in lighttable
2806 if(lastTag[0] == '\0')
2807 {
2808 res = (d && d->collection_tags && tag) ? g_hash_table_contains(d->collection_tags, tag) : FALSE;
2809 dt_free(tag);
2810 return res;
2811 }
2812
2813 if(tag)
2814 {
2815 char *normalized = g_utf8_normalize(tag, -1, G_NORMALIZE_ALL);
2816 if(normalized)
2817 {
2818 char *casefold = g_utf8_casefold(normalized, -1);
2819 if(casefold)
2820 {
2821 res = g_strstr_len(casefold, -1, lastTag) != NULL;
2822 }
2823 dt_free(casefold);
2824 }
2825 dt_free(normalized);
2826 dt_free(tag);
2827 }
2828
2829 return res;
2830}
2831
2832static void _tree_selection_changed(GtkTreeSelection *treeselection, gpointer data)
2833{
2835}
2836
2838{
2840 if(!d->drag.root) return;
2841 GtkTreeModel *model = GTK_TREE_MODEL(d->dictionary_treestore);
2842 GtkTreeIter iter;
2843 gtk_tree_model_get_iter_first(model, &iter);
2844 char *name;
2845 gtk_tree_model_get(model, &iter, DT_LIB_TAGGING_COL_PATH, &name, -1);
2846 if(name && name[0] == '\0')
2847 gtk_tree_store_remove(d->dictionary_treestore, &iter);
2848 dt_free(name);
2849 d->drag.root = FALSE;
2850}
2851
2852static void _event_dnd_begin(GtkWidget *widget, GdkDragContext *context, dt_lib_module_t *self)
2853{
2855 GtkTreeView *tree = GTK_TREE_VIEW(widget);
2856
2857 if(d->drag.path)
2858 {
2859 cairo_surface_t *row_pix = gtk_tree_view_create_row_drag_icon(tree, d->drag.path);
2860 // FIXME: try to put the anchor point on left bottom corner as for images
2861// cairo_surface_set_device_offset(row_pix,
2862// // the + 1 is for the black border in the icon
2863// - (double)(cairo_image_surface_get_width(row_pix) + 1),
2864// - (double)(cairo_image_surface_get_height(row_pix)+ 1));
2865 gtk_drag_set_icon_surface(context, row_pix);
2866 cairo_surface_destroy(row_pix);
2867 gtk_tree_path_free(d->drag.path);
2868 d->drag.path = NULL;
2869 d->drag.tag_source = TRUE;
2870 }
2871}
2872
2873static void _event_dnd_get(GtkWidget *widget, GdkDragContext *context, GtkSelectionData *selection_data,
2874 const guint target_type, const guint time, dt_lib_module_t *self)
2875{
2876 if(target_type == DND_TARGET_TAG)
2877 gtk_selection_data_set(selection_data, gtk_selection_data_get_target(selection_data),
2878 _DWORD, NULL, 0);
2879}
2880
2881static void _event_dnd_received(GtkWidget *widget, GdkDragContext *context, gint x, gint y,
2882 GtkSelectionData *selection_data, guint target_type, guint time,
2883 dt_lib_module_t *self)
2884{
2886 GtkTreeView *tree = GTK_TREE_VIEW(widget);
2887 // disable the default handler
2888 g_signal_stop_emission_by_name(tree, "drag-data-received");
2889 gboolean success = FALSE;
2890
2891 if(target_type == DND_TARGET_TAG)
2892 {
2893 GtkTreePath *path = NULL;
2894 if(gtk_tree_view_get_path_at_pos(tree, x, y, &path, NULL, NULL, NULL))
2895 {
2896 char *name;
2897 GtkTreeModel *model = gtk_tree_view_get_model(tree);
2898 GtkTreeIter iter;
2899
2900 gtk_tree_model_get_iter(model, &iter, path);
2901 gtk_tree_model_get(model, &iter, DT_LIB_TAGGING_COL_PATH, &name, -1);
2902 _dnd_clear_root(self);
2903 const gboolean root = (name && name[0] == '\0');
2904 char *leave = g_strrstr(d->drag.tagname, "|");
2905 if(leave) leave++;
2906 name = dt_util_dstrcat(name, "%s%s", root ? "" : "|", leave ? leave : d->drag.tagname);
2907 _apply_rename_path(NULL, d->drag.tagname, name, self);
2908
2909 dt_free(name);
2910 dt_free(d->drag.tagname);
2911 gtk_tree_path_free(path); // release result of gtk_tree_view_get_path_at_pos above
2912 success = TRUE;
2913 }
2914 }
2915 else if((target_type == DND_TARGET_IMGID) && (!IS_NULL_PTR(selection_data)))
2916 {
2917 GtkTreePath *path = NULL;
2918 const int imgs_nb = gtk_selection_data_get_length(selection_data) / sizeof(uint32_t);
2919 if(imgs_nb && gtk_tree_view_get_path_at_pos(tree, x, y, &path, NULL, NULL, NULL))
2920 {
2921 const uint32_t *imgt = (uint32_t *)gtk_selection_data_get_data(selection_data);
2922 GList *imgs = NULL;
2923 for(int i = 0; i < imgs_nb; i++)
2924 {
2925 imgs = g_list_prepend(imgs, GINT_TO_POINTER(imgt[i]));
2926 }
2927 GtkTreeModel *model = gtk_tree_view_get_model(tree);
2928 GtkTreeIter iter;
2929 gtk_tree_model_get_iter(model, &iter, path);
2930 gint tagid;
2931 gtk_tree_model_get(model, &iter, DT_LIB_TAGGING_COL_ID, &tagid, -1);
2932 if(tagid)
2933 dt_tag_attach_images(tagid, imgs, TRUE);
2934 g_list_free(imgs);
2935 imgs = NULL;
2936 _update_attached_count(tagid, d->dictionary_view, d->tree_flag);
2937 _init_treeview(self, 0);
2940 gtk_tree_path_free(path); // release result of gtk_tree_view_get_path_at_pos above
2941 success = TRUE;
2942 }
2943 }
2944 gtk_drag_finish(context, success, FALSE, time);
2945}
2946
2948{
2950 if(d->drag.lastpath)
2951 {
2952 gtk_tree_view_expand_row(d->dictionary_view, d->drag.lastpath, FALSE);
2953 }
2954 return FALSE;
2955}
2956
2958{
2960 if(d->drag.scroll_timeout)
2961 {
2962 GdkRectangle visible;
2963 gtk_tree_view_get_visible_rect(d->dictionary_view, &visible);
2964 gint top_ty;
2965 gtk_tree_view_convert_bin_window_to_tree_coords(d->dictionary_view,
2966 0, 0, NULL, &top_ty);
2967
2968 // temporary drop root
2969 if(d->drag.tag_source && !d->drag.root && d->drag.last_y < 5 && top_ty < 1)
2970 {
2971 GtkTreeIter iter;
2972 gtk_tree_store_prepend(d->dictionary_treestore, &iter, NULL);
2973 gtk_tree_store_set(d->dictionary_treestore, &iter,
2974 DT_LIB_TAGGING_COL_TAG, _("drop to root"),
2981 -1);
2982 d->drag.root = TRUE;
2983 }
2984 else if(d->drag.root && d->drag.last_y >= 20)
2985 _dnd_clear_root(self);
2986
2987 if(d->drag.last_y < 5)
2988 {
2989 // scroll up
2990 gtk_tree_view_scroll_to_point(d->dictionary_view, 0, top_ty - 25 < 0 ? 0 : top_ty - 25);
2991 }
2992 else if(d->drag.last_y > visible.height - 5)
2993 // scroll down
2994 gtk_tree_view_scroll_to_point(d->dictionary_view, 0, top_ty + 25);
2995 return TRUE;
2996 }
2997 return FALSE;
2998}
2999
3000static gboolean _event_dnd_motion(GtkWidget *widget, GdkDragContext *context,
3001 gint x, gint y, guint time, dt_lib_module_t *self)
3002{
3004 GtkTreeView *tree = GTK_TREE_VIEW(widget);
3005 GtkTreePath *path = NULL;
3006
3007 if(gtk_tree_view_get_path_at_pos(tree, x, y, &path, NULL, NULL, NULL))
3008 {
3009 if(IS_NULL_PTR(d->drag.lastpath) || ((d->drag.lastpath) && gtk_tree_path_compare(d->drag.lastpath, path) != 0))
3010 {
3011 GtkTreeViewColumn *col = gtk_tree_view_get_column(d->dictionary_view, 0);
3012 const int sel_width = gtk_tree_view_column_get_width(col);
3013 if(x >= sel_width)
3014 {
3015 if(!gtk_tree_view_row_expanded(tree, path))
3016 d->drag.expand_timeout = g_timeout_add(200, (GSourceFunc)_dnd_expand_timeout, self);
3017 }
3018 }
3019
3020 GtkTreeSelection *selection = gtk_tree_view_get_selection(d->dictionary_view);
3021 gtk_tree_selection_select_path(selection, path);
3022 d->drag.last_y = y;
3023 if(d->drag.scroll_timeout == 0)
3024 {
3025 d->drag.scroll_timeout = g_timeout_add(100, (GSourceFunc)_dnd_scroll_timeout, self);
3026 }
3027 }
3028
3029 // FIXME - no row highlihted... workartound with selection above
3030// gtk_tree_view_set_drag_dest_row(tree, path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
3031
3032 if(d->drag.lastpath)
3033 gtk_tree_path_free(d->drag.lastpath);
3034 d->drag.lastpath = path;
3035
3036 return TRUE;
3037}
3038
3039static void _event_dnd_end(GtkWidget *widget, GdkDragContext *context, dt_lib_module_t *self)
3040{
3042 // FIXME (see gtk_tree_view_set_drag_dest_row above)
3043// gtk_tree_view_set_drag_dest_row(d->dictionary_view, NULL, GTK_TREE_VIEW_DROP_BEFORE);
3044 GtkTreeSelection *selection = gtk_tree_view_get_selection(d->dictionary_view);
3045 gtk_tree_selection_unselect_all(selection);
3046 if(d->drag.scroll_timeout)
3047 g_source_remove(d->drag.scroll_timeout);
3048 d->drag.scroll_timeout = 0;
3049 d->drag.tag_source = FALSE;
3050 _dnd_clear_root(self);
3051}
3052
3053// open the tag management window (from the module's gear menu)
3054static void _show_manage_window(GtkMenuItem *menuitem, dt_lib_module_t *self)
3055{
3057 gtk_widget_show_all(d->manage_window);
3058 gtk_window_present(GTK_WINDOW(d->manage_window));
3059}
3060
3061// after a suggestion setting changed, rebuild the dictionary if it shows suggestions
3063{
3065 if(!d->tree_flag && d->suggestion_flag)
3066 _init_treeview(self, 1);
3067}
3068
3069static void _confidence_changed(GtkSpinButton *spin, dt_lib_module_t *self)
3070{
3071 if(dt_gui_widgets_suppressed()) return;
3072 dt_conf_set_int("plugins/lighttable/tagging/confidence", gtk_spin_button_get_value_as_int(spin));
3074}
3075
3076static void _recent_tags_changed(GtkSpinButton *spin, dt_lib_module_t *self)
3077{
3078 if(dt_gui_widgets_suppressed()) return;
3079 dt_conf_set_int("plugins/lighttable/tagging/nb_recent_tags", gtk_spin_button_get_value_as_int(spin));
3082}
3083
3084// clear the GtkEntry when its built-in secondary (clear) icon is pressed
3085static void _entry_clear_icon(GtkEntry *entry, GtkEntryIconPosition pos, GdkEvent *event, gpointer user_data)
3086{
3087 if(pos == GTK_ENTRY_ICON_SECONDARY) gtk_entry_set_text(entry, "");
3088}
3089
3090// validate button next to the entry: attach the typed/picked tag (same as pressing Enter)
3091static void _validate_button_clicked(GtkButton *button, dt_lib_module_t *self)
3092{
3094 _create_tag_from_entry(self, d->entry);
3095}
3096
3098{
3099 dt_lib_tagging_t *d = (dt_lib_tagging_t *)calloc(sizeof(dt_lib_tagging_t),1);
3100 self->data = (void *)d;
3101 d->last_tag = NULL;
3102 self->timeout_handle = 0;
3103
3104 self->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
3105
3106 GtkBox *box, *hbox;
3107 GtkWidget *button;
3108 GtkWidget *w;
3109 GtkTreeView *view;
3110 GtkTreeModel *model;
3111 GtkListStore *liststore;
3112 GtkTreeStore *treestore;
3113 GtkTreeViewColumn *col;
3114 GtkCellRenderer *renderer;
3115
3116 // ── line 1: "view" (list/tree) and "sort" (name/count) comboboxes ──
3117 hbox = GTK_BOX(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING));
3118
3119 // "view" combobox: render the attached list above as a flat list, or as a hierarchical tree
3120 d->attached_view_combo = dt_bauhaus_combobox_new(darktable.bauhaus, DT_GUI_MODULE(NULL));
3121 dt_bauhaus_widget_set_label(d->attached_view_combo, _("view"));
3122 dt_bauhaus_combobox_add(d->attached_view_combo, _("list"));
3123 dt_bauhaus_combobox_add(d->attached_view_combo, _("tree"));
3125 gtk_widget_set_valign(d->attached_view_combo, GTK_ALIGN_CENTER);
3126 gtk_widget_set_tooltip_text(d->attached_view_combo,
3127 _("show the attached tags as a flat list or as a hierarchical tree"));
3128 g_signal_connect(G_OBJECT(d->attached_view_combo), "value-changed", G_CALLBACK(_attached_view_combo_changed), self);
3129 gtk_box_pack_start(hbox, d->attached_view_combo, TRUE, TRUE, 0);
3130
3131 // "sort" combobox: by name or by image count
3133 dt_bauhaus_widget_set_label(d->sort_combo, _("Sort by"));
3134 dt_bauhaus_combobox_add(d->sort_combo, _("name"));
3135 dt_bauhaus_combobox_add(d->sort_combo, _("count"));
3137 gtk_widget_set_valign(d->sort_combo, GTK_ALIGN_CENTER);
3138 gtk_widget_set_tooltip_text(d->sort_combo, _("sort the tags by name or by image count"));
3139 g_signal_connect(G_OBJECT(d->sort_combo), "value-changed", G_CALLBACK(_sort_combo_changed), self);
3140 gtk_box_pack_start(hbox, d->sort_combo, TRUE, TRUE, 0);
3141
3142 gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(hbox), FALSE, TRUE, 0);
3143
3144 // ── line 3: scrolled list / tree of attached tags ──
3145 // (packed here in code, but reordered below to sit under the entry — see the reorder call)
3146 box = GTK_BOX(gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING));
3147
3148 gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(box), TRUE, TRUE, 0);
3149 view = GTK_TREE_VIEW(gtk_tree_view_new());
3150 // Static: the attached-tags list tracks the hovered/selected images, so keep a fixed height.
3151 w = dt_ui_scroll_wrap(GTK_WIDGET(view), 200, "plugins/lighttable/tagging/heightattachedwindow",
3153 gtk_box_pack_start(box, w, TRUE, TRUE, 0);
3154 d->attached_view = view;
3155 gtk_tree_view_set_enable_search(view, FALSE);
3156 gtk_tree_view_set_headers_visible(view, FALSE);
3157 liststore = gtk_list_store_new(DT_LIB_TAGGING_NUM_COLS, G_TYPE_STRING, G_TYPE_UINT, G_TYPE_STRING,
3158 G_TYPE_STRING, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_BOOLEAN);
3159 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(liststore), DT_TAG_SORT_PATH_ID,
3160 (GtkTreeIterCompareFunc)_sort_tree_path_func, self, NULL);
3161 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(liststore), DT_TAG_SORT_NAME_ID,
3162 (GtkTreeIterCompareFunc)_sort_tree_tag_func, self, NULL);
3163 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(liststore), DT_TAG_SORT_COUNT_ID,
3164 (GtkTreeIterCompareFunc)_sort_tree_count_func, self, NULL);
3165 d->attached_liststore = liststore; // kept alive by d's ref (the view alternates between the two stores)
3166 // tree variant of the attached view, used when the "view" combobox is set to "tree"
3167 treestore = gtk_tree_store_new(DT_LIB_TAGGING_NUM_COLS, G_TYPE_STRING, G_TYPE_UINT, G_TYPE_STRING,
3168 G_TYPE_STRING, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_BOOLEAN);
3169 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(treestore), DT_TAG_SORT_PATH_ID,
3170 (GtkTreeIterCompareFunc)_sort_tree_path_func, self, NULL);
3171 d->attached_treestore = treestore;
3172 g_object_set(G_OBJECT(view), "has-tooltip", TRUE, NULL);
3173 g_signal_connect(G_OBJECT(view), "query-tooltip", G_CALLBACK(_row_tooltip_setup), (gpointer)self);
3174
3175 col = gtk_tree_view_column_new();
3176 gtk_tree_view_append_column(view, col);
3177 renderer = gtk_cell_renderer_text_new();
3178 g_object_set(renderer, "ellipsize", PANGO_ELLIPSIZE_MIDDLE, (gchar *)0);
3179 gtk_tree_view_column_pack_start(col, renderer, TRUE);
3180 gtk_tree_view_column_set_cell_data_func(col, renderer, _tree_tagname_show_attached, (gpointer)self, NULL);
3181 gtk_tree_view_column_set_expand(col, TRUE);
3182 gtk_tree_view_set_expander_column(view, col);
3183
3184 // per-row "delete" icon to detach a single tag in one click
3185 col = gtk_tree_view_column_new();
3186 gtk_tree_view_append_column(view, col);
3187 renderer = dtgtk_cell_renderer_button_new();
3188 g_object_set(renderer, "mode", GTK_CELL_RENDERER_MODE_ACTIVATABLE, NULL);
3189 gtk_tree_view_column_pack_end(col, renderer, FALSE);
3190 gtk_tree_view_column_set_cell_data_func(col, renderer, _attached_delete_icon_show, NULL, NULL);
3191 g_signal_connect(renderer, "activate", G_CALLBACK(_attached_delete_icon_activated), (gpointer)self);
3192
3193 gtk_tree_selection_set_mode(gtk_tree_view_get_selection(view), GTK_SELECTION_MULTIPLE);
3194 gtk_tree_view_set_model(view, GTK_TREE_MODEL(liststore));
3195 // NB: liststore and treestore are not unref'd here — d keeps a ref on each so they
3196 // both survive while the view switches between list and tree mode (freed in gui_cleanup)
3197 gtk_widget_set_tooltip_text(GTK_WIDGET(view), _("attached tags,"
3198 "\nclick the trash icon, press Delete or double-click to detach"
3199 "\nselect several tags and right-click to detach them in bulk,"
3200 "\npress Tab to give the focus to entry,"
3201 "\nctrl-wheel scroll to resize the window"));
3202 g_signal_connect(G_OBJECT(view), "button-press-event", G_CALLBACK(_click_on_view_attached), (gpointer)self);
3203 g_signal_connect(G_OBJECT(view), "key-press-event", G_CALLBACK(_attached_key_pressed), (gpointer)self);
3204 g_signal_connect(gtk_tree_view_get_selection(view), "changed", G_CALLBACK(_tree_selection_changed), self);
3205
3206#define NEW_TOGGLE_BUTTON(paint, callback, tooltip, action) \
3207 button = dtgtk_togglebutton_new(paint, 0, NULL); \
3208 gtk_widget_set_tooltip_text(button, tooltip); \
3209 gtk_box_pack_end(GTK_BOX(hbox), button, FALSE, TRUE, 0); \
3210 g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(callback), self); \
3211
3212 // ── line 2: tag input entry (with autocompletion) ──
3213 box = GTK_BOX(gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING));
3214 gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(box), FALSE, TRUE, 0);
3215 // the attached-tags list is built before this entry, so move the entry up to index 1
3216 // (just under the combos at index 0): final order is combos, entry, attached list, checkbox
3217 gtk_box_reorder_child(GTK_BOX(self->widget), GTK_WIDGET(box), 1);
3218
3219 hbox = GTK_BOX(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING));
3220
3221 w = gtk_entry_new();
3223 gtk_entry_set_text(GTK_ENTRY(w), "");
3224 gtk_entry_set_width_chars(GTK_ENTRY(w), 0);
3225 // no widget tooltip here: it would pop up over the autocompletion list and hide it
3226 gtk_entry_set_placeholder_text(GTK_ENTRY(w), _("enter or pick a tag, Enter to attach"));
3227 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(w), GTK_ENTRY_ICON_SECONDARY, "edit-clear-symbolic");
3228 gtk_entry_set_icon_tooltip_text(GTK_ENTRY(w), GTK_ENTRY_ICON_SECONDARY, _("clear entry"));
3229 g_signal_connect(G_OBJECT(w), "icon-press", G_CALLBACK(_entry_clear_icon), NULL);
3230 gtk_box_pack_start(hbox, w, TRUE, TRUE, 0);
3231 gtk_widget_add_events(GTK_WIDGET(w), GDK_KEY_RELEASE_MASK);
3232 d->entry = GTK_ENTRY(w);
3233
3234 // validate button: attach the typed/picked tag without having to hit Enter
3235 d->validate_button = dtgtk_button_new(dtgtk_cairo_paint_check_mark, 0, NULL);
3236 gtk_widget_set_tooltip_text(d->validate_button, _("attach this tag to the selected images"));
3237 gtk_box_pack_end(GTK_BOX(hbox), d->validate_button, FALSE, TRUE, 0);
3238 g_signal_connect(G_OBJECT(d->validate_button), "clicked", G_CALLBACK(_validate_button_clicked), (gpointer)self);
3239 gtk_box_pack_start(box, GTK_WIDGET(hbox), FALSE, TRUE, 0);
3240
3241 // autocompletion: every known tag feeds the model, suggestions default to the
3242 // current collection's tags when the entry holds a single token (see _completion_match_func).
3243 // minimum key length 1: an empty entry shows no popup, so the list does not hover over the
3244 // attached-tags view (e.g. right after a tag was attached and the entry cleared).
3245 d->completion_store = gtk_list_store_new(DT_COMPL_NUM_COLS, G_TYPE_STRING);
3246 {
3247 GtkEntryCompletion *completion = gtk_entry_completion_new();
3248 gtk_entry_completion_set_model(completion, GTK_TREE_MODEL(d->completion_store));
3249 gtk_entry_completion_set_text_column(completion, DT_COMPL_COL_PATH);
3250 gtk_entry_completion_set_inline_completion(completion, TRUE);
3251 gtk_entry_completion_set_popup_set_width(completion, FALSE);
3252 gtk_entry_completion_set_minimum_key_length(completion, 1);
3253 g_signal_connect(G_OBJECT(completion), "match-selected", G_CALLBACK(_match_selected_func), self);
3254 gtk_entry_completion_set_match_func(completion, _completion_match_func, self, NULL);
3255 gtk_entry_set_completion(d->entry, completion);
3256 g_object_unref(completion);
3257 }
3258 g_object_unref(d->completion_store); // kept alive by the completion above
3259 // connected after gtk_entry_set_completion() so GtkEntryCompletion's own Return-key
3260 // handling (accepting a keyboard-highlighted popup match into the entry text) runs
3261 // before ours: otherwise a tag gets created from the raw typed prefix instead of the
3262 // selected suggestion (#905)
3263 g_signal_connect(G_OBJECT(d->entry), "key-press-event", G_CALLBACK(_enter_key_pressed), (gpointer)self);
3264
3265 // ── tag management popup window (persistent, hidden until requested) ────
3266 d->manage_window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
3267#ifdef GDK_WINDOWING_QUARTZ
3268 dt_osx_disallow_fullscreen(d->manage_window);
3269#endif
3270 gtk_window_set_title(GTK_WINDOW(d->manage_window), _("manage tags"));
3271 gtk_window_set_transient_for(GTK_WINDOW(d->manage_window), GTK_WINDOW(dt_ui_main_window(darktable.gui->ui)));
3272 gtk_window_set_default_size(GTK_WINDOW(d->manage_window), DT_PIXEL_APPLY_DPI(400), DT_PIXEL_APPLY_DPI(600));
3273 // hide instead of destroy so the dictionary widgets/state persist across openings
3274 g_signal_connect(G_OBJECT(d->manage_window), "delete-event", G_CALLBACK(gtk_widget_hide_on_delete), NULL);
3275
3276 box = GTK_BOX(gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING));
3277 gtk_container_set_border_width(GTK_CONTAINER(box), DT_PIXEL_APPLY_DPI(8));
3278 gtk_container_add(GTK_CONTAINER(d->manage_window), GTK_WIDGET(box));
3279
3280 // search entry to filter the dictionary
3281 w = gtk_entry_new();
3283 gtk_entry_set_text(GTK_ENTRY(w), "");
3284 gtk_entry_set_width_chars(GTK_ENTRY(w), 0);
3285 gtk_entry_set_placeholder_text(GTK_ENTRY(w), _("type to filter the tag dictionary below"));
3286 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(w), GTK_ENTRY_ICON_SECONDARY, "edit-clear-symbolic");
3287 gtk_entry_set_icon_tooltip_text(GTK_ENTRY(w), GTK_ENTRY_ICON_SECONDARY, _("clear entry"));
3288 g_signal_connect(G_OBJECT(w), "icon-press", G_CALLBACK(_entry_clear_icon), NULL);
3289 gtk_widget_add_events(GTK_WIDGET(w), GDK_KEY_RELEASE_MASK);
3290 g_signal_connect(G_OBJECT(w), "changed", G_CALLBACK(_tag_name_changed), (gpointer)self);
3291 d->dict_entry = GTK_ENTRY(w);
3292 gtk_box_pack_start(box, w, FALSE, TRUE, 0);
3293
3294 // dictionary_view tree view
3295 view = GTK_TREE_VIEW(gtk_tree_view_new());
3296 // Static: keep a fixed height for stability alongside the attached-tags list above.
3297 w = dt_ui_scroll_wrap(GTK_WIDGET(view), 200, "plugins/lighttable/tagging/heightdictionarywindow",
3299 gtk_box_pack_start(box, w, TRUE, TRUE, 0);
3300 d->dictionary_view = view;
3301 gtk_tree_view_set_enable_search(view, FALSE);
3302 gtk_tree_view_set_headers_visible(view, FALSE);
3303 liststore = gtk_list_store_new(DT_LIB_TAGGING_NUM_COLS, G_TYPE_STRING, G_TYPE_UINT, G_TYPE_STRING,
3304 G_TYPE_STRING, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_BOOLEAN);
3305 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(liststore), DT_TAG_SORT_PATH_ID,
3306 (GtkTreeIterCompareFunc)_sort_tree_path_func, self, NULL);
3307 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(liststore), DT_TAG_SORT_NAME_ID,
3308 (GtkTreeIterCompareFunc)_sort_tree_tag_func, self, NULL);
3309 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(liststore), DT_TAG_SORT_COUNT_ID,
3310 (GtkTreeIterCompareFunc)_sort_tree_count_func, self, NULL);
3311 d->dictionary_liststore = liststore;
3312 model = gtk_tree_model_filter_new(GTK_TREE_MODEL(liststore), NULL);
3313 gtk_tree_model_filter_set_visible_column(GTK_TREE_MODEL_FILTER(model), DT_LIB_TAGGING_COL_VISIBLE);
3314 d->dictionary_listfilter = GTK_TREE_MODEL_FILTER(model);
3315 treestore = gtk_tree_store_new(DT_LIB_TAGGING_NUM_COLS, G_TYPE_STRING, G_TYPE_UINT, G_TYPE_STRING,
3316 G_TYPE_STRING, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_BOOLEAN);
3317 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(treestore), DT_TAG_SORT_PATH_ID,
3318 (GtkTreeIterCompareFunc)_sort_tree_path_func, self, NULL);
3319 d->dictionary_treestore = treestore;
3320 model = gtk_tree_model_filter_new(GTK_TREE_MODEL(treestore), NULL);
3321 gtk_tree_model_filter_set_visible_column(GTK_TREE_MODEL_FILTER(model), DT_LIB_TAGGING_COL_VISIBLE);
3322 d->dictionary_treefilter = GTK_TREE_MODEL_FILTER(model);
3323
3324 col = gtk_tree_view_column_new();
3325 gtk_tree_view_append_column(view, col);
3326 renderer = gtk_cell_renderer_text_new();
3327 g_object_set(renderer, "ellipsize", PANGO_ELLIPSIZE_MIDDLE, (gchar *)0);
3328 gtk_tree_view_column_pack_start(col, renderer, TRUE);
3329 gtk_tree_view_column_set_cell_data_func(col, renderer, _tree_tagname_show_dictionary, (gpointer)self, NULL);
3330 gtk_tree_view_set_expander_column(view, col);
3331
3332 gtk_tree_selection_set_mode(gtk_tree_view_get_selection(view), GTK_SELECTION_MULTIPLE);
3333 gtk_widget_set_tooltip_text(GTK_WIDGET(view), _("tag dictionary,"
3334 "\nright-click to create, edit, delete tags,"
3335 "\nselect several tags and right-click to delete them in bulk,"
3336 "\nshift+click to fully expand the selected tag,"
3337 "\nctrl-scroll to resize the window"));
3338
3339 g_signal_connect(G_OBJECT(view), "button-press-event", G_CALLBACK(_click_on_view_dictionary), (gpointer)self);
3340 g_signal_connect(G_OBJECT(view), "key-press-event", G_CALLBACK(_dictionary_key_pressed), (gpointer)self);
3341 gtk_tree_view_set_model(view, GTK_TREE_MODEL(d->dictionary_listfilter));
3342 g_object_unref(d->dictionary_listfilter);
3343 g_object_set(G_OBJECT(view), "has-tooltip", TRUE, NULL);
3344 g_signal_connect(G_OBJECT(view), "query-tooltip", G_CALLBACK(_row_tooltip_setup), (gpointer)self);
3345 g_signal_connect(gtk_tree_view_get_selection(view), "changed", G_CALLBACK(_tree_selection_changed), self);
3346
3347 // drag & drop
3348 {
3349 d->drag.path = NULL;
3350 d->drag.tagname = NULL;
3351 d->drag.scroll_timeout = 0;
3352 d->drag.expand_timeout = 0;
3353 d->drag.root = FALSE;
3354 d->drag.tag_source = FALSE;
3355 gtk_drag_dest_set(GTK_WIDGET(d->dictionary_view), GTK_DEST_DEFAULT_ALL,
3356 target_list_tags_dest, n_targets_tags_dest, GDK_ACTION_MOVE);
3357 g_signal_connect(d->dictionary_view, "drag-data-get", G_CALLBACK(_event_dnd_get), self);
3358 g_signal_connect(d->dictionary_view, "drag-data-received", G_CALLBACK(_event_dnd_received), self);
3359 g_signal_connect_after(d->dictionary_view, "drag-begin", G_CALLBACK(_event_dnd_begin), self);
3360 g_signal_connect_after(d->dictionary_view, "drag-end", G_CALLBACK(_event_dnd_end), self);
3361 g_signal_connect(d->dictionary_view, "drag-motion", G_CALLBACK(_event_dnd_motion), self);
3362 }
3363
3364 // buttons
3365 hbox = GTK_BOX(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING));
3366
3367 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);
3368 gtk_box_pack_start(hbox, d->new_button, TRUE, TRUE, 0);
3369
3370 d->import_button = dt_action_button_new(self, N_("import..."), _import_button_clicked, self, _("import tags from a Lightroom keyword file"), 0, 0);
3371 gtk_box_pack_start(hbox, d->import_button, TRUE, TRUE, 0);
3372
3373 d->export_button = dt_action_button_new(self, N_("export..."), _export_button_clicked, self, _("export all tags to a Lightroom keyword file"), 0, 0);
3374 gtk_box_pack_start(hbox, d->export_button, TRUE, TRUE, 0);
3375
3377 _("toggle list / tree view"), N_("tree"));
3379 _("toggle list with / without suggestion"), N_("suggestion"));
3380
3381 gtk_box_pack_start(box, GTK_WIDGET(hbox), FALSE, TRUE, 0);
3382
3383 // ── tag-suggestion settings (formerly the module "preferences" dialog), applied live ──
3384 gtk_box_pack_start(box, gtk_separator_new(GTK_ORIENTATION_HORIZONTAL), FALSE, FALSE, DT_PIXEL_APPLY_DPI(4));
3385 {
3386 GtkWidget *grid = gtk_grid_new();
3387 gtk_grid_set_row_spacing(GTK_GRID(grid), DT_GUI_BOX_SPACING);
3388 gtk_grid_set_column_spacing(GTK_GRID(grid), DT_GUI_BOX_SPACING);
3389
3390 GtkWidget *lbl = gtk_label_new(_("suggested tags level of confidence"));
3391 gtk_widget_set_halign(lbl, GTK_ALIGN_START);
3392 gtk_widget_set_hexpand(lbl, TRUE);
3393 GtkWidget *spin = gtk_spin_button_new_with_range(0, 100, 1);
3394 gtk_spin_button_set_value(GTK_SPIN_BUTTON(spin), dt_conf_get_int("plugins/lighttable/tagging/confidence"));
3395 gtk_widget_set_tooltip_text(spin, _("level of confidence to include a tag in the suggestions list:"
3396 "\n0: all associated tags, 99: 99% matching tags,"
3397 "\n100: only the recently-used tags (faster)"));
3398 g_signal_connect(G_OBJECT(spin), "value-changed", G_CALLBACK(_confidence_changed), self);
3399 gtk_grid_attach(GTK_GRID(grid), lbl, 0, 0, 1, 1);
3400 gtk_grid_attach(GTK_GRID(grid), spin, 1, 0, 1, 1);
3401
3402 GtkWidget *lbl2 = gtk_label_new(_("number of recently attached tags"));
3403 gtk_widget_set_halign(lbl2, GTK_ALIGN_START);
3404 gtk_widget_set_hexpand(lbl2, TRUE);
3405 GtkWidget *spin2 = gtk_spin_button_new_with_range(-1, 1000, 1);
3406 gtk_spin_button_set_value(GTK_SPIN_BUTTON(spin2), dt_conf_get_int("plugins/lighttable/tagging/nb_recent_tags"));
3407 gtk_widget_set_tooltip_text(spin2, _("number of recently attached tags shown in the suggestions list"
3408 "\n(-1 disables the recent-tags list)"));
3409 g_signal_connect(G_OBJECT(spin2), "value-changed", G_CALLBACK(_recent_tags_changed), self);
3410 gtk_grid_attach(GTK_GRID(grid), lbl2, 0, 1, 1, 1);
3411 gtk_grid_attach(GTK_GRID(grid), spin2, 1, 1, 1, 1);
3412
3413 gtk_box_pack_start(box, grid, FALSE, FALSE, 0);
3414 }
3415
3416 // "show system tags" checkbox: reveal the tags Ansel manages automatically (Ansel|…)
3417 d->dttags_check = gtk_check_button_new_with_label(_("Show system tags"));
3418 gtk_widget_set_tooltip_text(d->dttags_check, _("show the tags automatically attached by Ansel (names starting with “Ansel|”)"));
3419 d->dttags_flag = FALSE;
3420 g_signal_connect(G_OBJECT(d->dttags_check), "toggled", G_CALLBACK(_dttags_check_toggled), self);
3421 gtk_box_pack_start(GTK_BOX(self->widget), d->dttags_check, FALSE, TRUE, 0);
3422
3423#undef NEW_TOGGLE_BUTTON
3424
3425 /* connect to mouse over id */
3427 G_CALLBACK(_lib_tagging_redraw_callback), self);
3429 G_CALLBACK(_lib_tagging_tags_changed_callback), self);
3431 G_CALLBACK(_lib_selection_changed_callback), self);
3433 G_CALLBACK(_collection_updated_callback), self);
3434
3435 d->collection = g_malloc(4096);
3436 d->collection_tags = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
3437 _update_layout(self);
3438 _init_treeview(self, 0);
3439 _set_keyword(self);
3440 _init_treeview(self, 1);
3444
3446 darktable.gui->accels->lighttable_accels, N_("Lighttable/Tags"),
3447 N_("Tag"), GDK_KEY_t, GDK_CONTROL_MASK, FALSE,
3448 _("Opens the quick tagging entry"));
3450 darktable.gui->accels->lighttable_accels, N_("Lighttable/Tags"),
3451 N_("Redo last tag"), GDK_KEY_t, GDK_MOD1_MASK, FALSE,
3452 _("Re-applies the last used tag"));
3454 darktable.gui->accels->map_accels, N_("Map/Tags"),
3455 N_("Tag"), GDK_KEY_t, GDK_CONTROL_MASK, FALSE,
3456 _("Opens the quick tagging entry"));
3458 darktable.gui->accels->map_accels, N_("Map/Tags"),
3459 N_("Redo last tag"), GDK_KEY_t, GDK_MOD1_MASK, FALSE,
3460 _("Re-applies the last used tag"));
3461}
3462
3464{
3465 if(IS_NULL_PTR(self->data)) return;
3468
3469 gchar *path = dt_accels_build_path(N_("Lighttable/Tags"), N_("Tag"));
3471 dt_free(path);
3472 path = dt_accels_build_path(N_("Lighttable/Tags"), N_("Redo last tag"));
3474 dt_free(path);
3475 path = dt_accels_build_path(N_("Map/Tags"), N_("Tag"));
3477 dt_free(path);
3478 path = dt_accels_build_path(N_("Map/Tags"), N_("Redo last tag"));
3480 dt_free(path);
3481
3486 if(d->manage_window) gtk_widget_destroy(d->manage_window);
3487 if(d->collection_tags) g_hash_table_destroy(d->collection_tags);
3488 // d kept its own ref on both attached stores so the view could switch between them
3489 if(d->attached_liststore) g_object_unref(d->attached_liststore);
3490 if(d->attached_treestore) g_object_unref(d->attached_treestore);
3491 dt_free(d->collection);
3492 if(d->drag.tagname)
3493 {
3494 dt_free(d->drag.tagname);
3495 }
3496 if(d->drag.path) gtk_tree_path_free(d->drag.path);
3497 dt_free(self->data);
3498}
3499
3500// http://stackoverflow.com/questions/4631388/transparent-floating-gtkentry
3501static gboolean _lib_tagging_tag_key_press(GtkWidget *entry, GdkEventKey *event, dt_lib_module_t *self)
3502{
3504 guint key = dt_keys_mainpad_alternatives(event->keyval);
3505
3506 switch(key)
3507 {
3508 case GDK_KEY_Escape:
3509 g_list_free(d->floating_tag_imgs);
3510 d->floating_tag_imgs = NULL;
3511 gtk_widget_destroy(d->floating_tag_window);
3512 gtk_window_present(GTK_WINDOW(dt_ui_main_window(darktable.gui->ui)));
3513 return TRUE;
3514 case GDK_KEY_Tab:
3515 return TRUE;
3516 case GDK_KEY_Return:
3517 {
3518 const gchar *tag = gtk_entry_get_text(GTK_ENTRY(entry));
3519 const gboolean res = dt_tag_attach_string_list(tag, d->floating_tag_imgs, TRUE);
3520 if(res) dt_image_synch_xmps(d->floating_tag_imgs);
3521 g_list_free(d->floating_tag_imgs);
3522 d->floating_tag_imgs = NULL;
3523
3525 _save_last_tag_used(tag, d);
3526
3527 _init_treeview(self, 0);
3528 _init_treeview(self, 1);
3529 gtk_widget_destroy(d->floating_tag_window);
3530 gtk_window_present(GTK_WINDOW(dt_ui_main_window(darktable.gui->ui)));
3531 if(res) _raise_signal_tag_changed(self);
3532
3533 return TRUE;
3534 }
3535 }
3536 return FALSE; /* event not handled */
3537}
3538
3539static gboolean _lib_tagging_tag_destroy(GtkWidget *widget, GdkEvent *event, gpointer user_data)
3540{
3541 gtk_widget_destroy(GTK_WIDGET(user_data));
3542 return FALSE;
3543}
3544
3545
3549static gboolean _lib_tagging_tag_redo_accel(GtkAccelGroup *accel_group, GObject *accelerable, guint keyval,
3550 GdkModifierType mods, gpointer user_data)
3551{
3552 dt_lib_module_t *self = (dt_lib_module_t *)user_data;
3554
3555 if(d->last_tag)
3556 {
3557 GList *imgs = dt_act_on_get_images();
3558 const gboolean res = dt_tag_attach_string_list(d->last_tag, imgs, TRUE);
3559 if(res) dt_image_synch_xmps(imgs);
3560 g_list_free(imgs);
3561 imgs = NULL;
3562 _init_treeview(self, 0);
3563 _init_treeview(self, 1);
3564 if(res) _raise_signal_tag_changed(self);
3565 }
3566
3567 return TRUE;
3568}
3569
3574static gboolean _lib_tagging_tag_show_accel(GtkAccelGroup *accel_group, GObject *accelerable, guint keyval,
3575 GdkModifierType mods, gpointer user_data)
3576{
3577 dt_lib_module_t *self = (dt_lib_module_t *)user_data;
3579 if(d->tree_flag)
3580 {
3581 dt_control_log(_("tag shortcut is not active with tag tree view. please switch to list view"));
3582 return TRUE; // doesn't work properly with tree treeview
3583 }
3584
3585 d->floating_tag_imgs = dt_act_on_get_images();
3586 gint x, y;
3587 gint px, py, w, h;
3590 gdk_window_get_origin(gtk_widget_get_window(center), &px, &py);
3591
3592 w = gdk_window_get_width(gtk_widget_get_window(center));
3593 h = gdk_window_get_height(gtk_widget_get_window(center));
3594
3595 x = px + 0.5 * (w - FLOATING_ENTRY_WIDTH);
3596 y = py + h - 50;
3597
3598 /* put the floating box at the mouse pointer */
3599 // gint pointerx, pointery;
3600 // GdkDevice *device =
3601 // gdk_device_manager_get_client_pointer(gdk_display_get_device_manager(gtk_widget_get_display(widget)));
3602 // gdk_window_get_device_position (gtk_widget_get_window (widget), device, &pointerx, &pointery, NULL);
3603 // x = px + pointerx + 1;
3604 // y = py + pointery + 1;
3605
3606 d->floating_tag_window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
3607#ifdef GDK_WINDOWING_QUARTZ
3608 dt_osx_disallow_fullscreen(d->floating_tag_window);
3609#endif
3610 /* stackoverflow.com/questions/1925568/how-to-give-keyboard-focus-to-a-pop-up-gtk-window */
3611 gtk_widget_set_can_focus(d->floating_tag_window, TRUE);
3612 gtk_window_set_decorated(GTK_WINDOW(d->floating_tag_window), FALSE);
3613 gtk_window_set_type_hint(GTK_WINDOW(d->floating_tag_window), GDK_WINDOW_TYPE_HINT_POPUP_MENU);
3614 gtk_window_set_transient_for(GTK_WINDOW(d->floating_tag_window), GTK_WINDOW(window));
3615 gtk_widget_set_opacity(d->floating_tag_window, 0.8);
3616 gtk_window_move(GTK_WINDOW(d->floating_tag_window), x, y);
3617
3618 GtkWidget *entry = gtk_entry_new();
3620 gtk_widget_set_size_request(entry, FLOATING_ENTRY_WIDTH, -1);
3621 gtk_widget_add_events(entry, GDK_FOCUS_CHANGE_MASK);
3622
3623 GtkEntryCompletion *completion = gtk_entry_completion_new();
3624 gtk_entry_completion_set_model(completion, GTK_TREE_MODEL(d->completion_store));
3625 gtk_entry_completion_set_text_column(completion, DT_COMPL_COL_PATH);
3626 gtk_entry_completion_set_inline_completion(completion, TRUE);
3627 gtk_entry_completion_set_popup_set_width(completion, FALSE);
3628 gtk_entry_completion_set_minimum_key_length(completion, 0);
3629 g_signal_connect(G_OBJECT(completion), "match-selected", G_CALLBACK(_match_selected_func), self);
3630 gtk_entry_completion_set_match_func(completion, _completion_match_func, self, NULL);
3631 gtk_entry_set_completion(GTK_ENTRY(entry), completion);
3632
3633 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
3634 gtk_container_add(GTK_CONTAINER(d->floating_tag_window), entry);
3635 g_signal_connect(entry, "focus-out-event", G_CALLBACK(_lib_tagging_tag_destroy), d->floating_tag_window);
3636 g_signal_connect(entry, "key-press-event", G_CALLBACK(_lib_tagging_tag_key_press), self);
3637
3638 gtk_widget_show_all(d->floating_tag_window);
3639 gtk_widget_grab_focus(entry);
3640 gtk_window_present(GTK_WINDOW(d->floating_tag_window));
3641
3642 return TRUE;
3643}
3644
3646{
3647 const int length = dt_conf_get_int("plugins/lighttable/tagging/nb_recent_tags");
3648 if(length == -1) return length;
3649 else if(length >= 10/2) return length * 2;
3650 else return 10;
3651}
3652
3654{
3655 const char *list = dt_conf_get_string_const("plugins/lighttable/tagging/recent_tags");
3656 if(!list[0])
3657 return;
3658 const int length = _get_recent_tags_list_length();
3659 if(length == -1)
3660 {
3661 dt_conf_set_string("plugins/lighttable/tagging/recent_tags", "");
3662 return;
3663 }
3664
3665 char *p = (char *)list;
3666 int nb = 1;
3667 for(;*p != '\0'; p++)
3668 {
3669 if(*p == ',') nb++;
3670 }
3671
3672 if(nb > length)
3673 {
3674 nb -= length;
3675 char *list2 = g_strdup(list);
3676 for(; nb > 0; nb--)
3677 {
3678 p = g_strrstr(list2, "','");
3679 if(p) *p = '\0';
3680 }
3681 dt_conf_set_string("plugins/lighttable/tagging/recent_tags", list2);
3682 dt_free(list2);
3683 }
3684}
3685
3686// the module's gear menu opens the tag management window, which now also hosts the
3687// tag-suggestion settings that used to live in a separate "preferences" dialog
3688void set_preferences(void *menu, dt_lib_module_t *self)
3689{
3690 GtkWidget *mi = gtk_menu_item_new_with_label(_("manage tags..."));
3691 g_signal_connect(G_OBJECT(mi), "activate", G_CALLBACK(_show_manage_window), self);
3692 gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi);
3693}
3694
3695static void _save_last_tag_used(const char *tagnames, dt_lib_tagging_t *d)
3696{
3697 dt_free(d->last_tag);
3698 d->last_tag = g_strdup(tagnames);
3699
3700 const int nb_recent = _get_recent_tags_list_length();
3701
3702 if(nb_recent != -1)
3703 {
3704 GList *ntags = dt_util_str_to_glist(",", tagnames);
3705 if(ntags)
3706 {
3707 const char *sl = dt_conf_get_string_const("plugins/lighttable/tagging/recent_tags");
3708 // use "','" instead of "," to use the list as is in the query (dt_tag_get_suggestions)
3709 GList *tags = dt_util_str_to_glist("','", sl);
3710 for(GList *tag = ntags; tag; tag = g_list_next(tag))
3711 {
3712 char *escaped = sqlite3_mprintf("%q", (char *)tag->data);
3713 GList *found = g_list_find_custom(tags, escaped, (GCompareFunc)g_strcmp0);
3714 if(found)
3715 {
3716 tags = g_list_remove_link(tags, found);
3717 dt_free(found->data);
3718 g_list_free(found);
3719 found = NULL;
3720 }
3721 tags = g_list_prepend(tags, g_strdup(escaped));
3722 sqlite3_free(escaped);
3723 }
3724 g_list_free_full(ntags, dt_free_gpointer);
3725 ntags = NULL;
3726
3727 char *nsl = dt_util_glist_to_str("','", tags);
3728 dt_conf_set_string("plugins/lighttable/tagging/recent_tags", nsl);
3729 dt_free(nsl);
3730 if(g_list_length(tags) > nb_recent)
3732 g_list_free_full(tags, dt_free_gpointer);
3733 tags = NULL;
3734 }
3735 }
3736}
3737
3738// clang-format off
3739// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
3740// vim: shiftwidth=2 expandtab tabstop=2 cindent
3741// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
3742// 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:2095
int dt_bauhaus_combobox_get(GtkWidget *widget)
Definition bauhaus.c:2350
void dt_bauhaus_combobox_set(GtkWidget *widget, const int pos)
Definition bauhaus.c:2304
void dt_bauhaus_widget_set_label(GtkWidget *widget, const char *label)
Definition bauhaus.c:1656
GtkWidget * dt_bauhaus_combobox_new(dt_bauhaus_t *bh, dt_gui_module_t *self)
Definition bauhaus.c:1845
void dt_bauhaus_combobox_add(GtkWidget *widget, const char *text)
Definition bauhaus.c:2019
@ 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:939
void dt_control_log(const char *msg,...)
Definition control.c:777
void leave(dt_view_t *self)
Definition darkroom.c:1873
uint32_t view(const dt_view_t *self)
Definition darkroom.c:228
darktable_t darktable
Definition darktable.c:183
#define dt_gui_freeze_begin()
Definition darktable.h:900
#define dt_gui_freeze_end()
Definition darktable.h:901
#define DT_MODULE(MODVER)
Definition darktable.h:140
static void dt_free_gpointer(gpointer ptr)
Definition darktable.h:485
#define dt_free(ptr)
Definition darktable.h:478
void void void gboolean dt_gui_widgets_suppressed(void)
Definition gtk.c:194
static gboolean dt_modifier_is(const GdkModifierType state, const GdkModifierType desired_modifier_mask)
Definition darktable.h:955
#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:293
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
static const GtkTargetEntry target_list_tags_dest[]
@ DND_TARGET_IMGID
@ DND_TARGET_TAG
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:2919
void dt_gui_refocus_parent(GtkWindow *parent)
Definition gtk.c:1936
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:2945
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:3473
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:270
#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:502
void dt_control_signal_block_by_func(const struct dt_control_signal_t *ctlsig, GCallback cb, gpointer user_data)
Definition signal.c:497
#define DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(ctlsig, cb, user_data)
Definition signal.h:387
#define DT_DEBUG_CONTROL_SIGNAL_RAISE(ctlsig, signal,...)
Definition signal.h:366
@ 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:376
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:803
struct dt_selection_t * selection
Definition darktable.h:810
struct dt_control_signal_t * signals
Definition darktable.h:802
struct dt_bauhaus_t * bauhaus
Definition darktable.h:806
GtkAccelGroup * map_accels
GtkAccelGroup * lighttable_accels
dt_accels_t * accels
Definition gtk.h:199
dt_ui_t * ui
Definition gtk.h:165
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
GtkWidget * window
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:2598
static gint _sort_tree_count_func(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, dt_lib_module_t *self)
Definition tagging.c:2589
static void _export_button_clicked(GtkButton *button, dt_lib_module_t *self)
Definition tagging.c:2474
void gui_reset(dt_lib_module_t *self)
Definition tagging.c:2669
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:2957
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:3688
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:2612
static void _toggle_tree_button_callback(GtkToggleButton *source, dt_lib_module_t *self)
Definition tagging.c:2580
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:2641
static void _validate_button_clicked(GtkButton *button, dt_lib_module_t *self)
Definition tagging.c:3091
static gboolean _row_tooltip_setup(GtkWidget *treeview, gint x, gint y, gboolean kb_mode, GtkTooltip *tooltip, dt_lib_module_t *self)
Definition tagging.c:2404
static gboolean _dictionary_key_pressed(GtkWidget *view, GdkEventKey *event, dt_lib_module_t *self)
Definition tagging.c:2352
static void _refresh_collection_tags(dt_lib_module_t *self)
Definition tagging.c:2709
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:2873
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:2837
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:3549
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:2443
static void _pop_menu_dictionary_change_path(GtkWidget *menuitem, dt_lib_module_t *self)
Definition tagging.c:1950
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:3653
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:2509
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:2149
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:3463
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:1652
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:3054
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:3085
static void _pop_menu_dictionary(GtkWidget *treeview, GdkEventButton *event, dt_lib_module_t *self)
Definition tagging.c:2180
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:2660
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:2852
static void _recent_tags_changed(GtkSpinButton *spin, dt_lib_module_t *self)
Definition tagging.c:3076
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:1893
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
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:2062
static gboolean _completion_match_func(GtkEntryCompletion *completion, const gchar *key, GtkTreeIter *iter, gpointer user_data)
Definition tagging.c:2762
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:2157
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:1526
static void _attached_view_combo_changed(GtkWidget *combo, dt_lib_module_t *self)
Definition tagging.c:2651
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:3069
static void _refresh_completion_store(dt_lib_module_t *self)
Definition tagging.c:2689
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:2091
static int _get_recent_tags_list_length()
Definition tagging.c:3645
static void _refresh_suggestions(dt_lib_module_t *self)
Definition tagging.c:3062
static void _toggle_suggestion_button_callback(GtkToggleButton *source, dt_lib_module_t *self)
Definition tagging.c:2571
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:2881
static void _tree_selection_changed(GtkTreeSelection *treeselection, gpointer data)
Definition tagging.c:2832
static void _event_dnd_end(GtkWidget *widget, GdkDragContext *context, dt_lib_module_t *self)
Definition tagging.c:3039
static void _pop_menu_dictionary_goto_tag_collection(GtkWidget *menuitem, dt_lib_module_t *self)
Definition tagging.c:2038
void gui_init(dt_lib_module_t *self)
Definition tagging.c:3097
int position()
Definition tagging.c:2680
static gboolean _lib_tagging_tag_key_press(GtkWidget *entry, GdkEventKey *event, dt_lib_module_t *self)
Definition tagging.c:3501
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:3574
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:3539
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
@ DT_COMPL_NUM_COLS
Definition tagging.c:2686
@ DT_COMPL_COL_PATH
Definition tagging.c:2686
static gboolean _click_on_view_dictionary(GtkWidget *view, GdkEventButton *event, dt_lib_module_t *self)
Definition tagging.c:2293
static gboolean _dnd_expand_timeout(dt_lib_module_t *self)
Definition tagging.c:2947
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:3695
static gboolean _event_dnd_motion(GtkWidget *widget, GdkDragContext *context, gint x, gint y, guint time, dt_lib_module_t *self)
Definition tagging.c:3000
static gboolean _match_selected_func(GtkEntryCompletion *completion, GtkTreeModel *model, GtkTreeIter *iter, gpointer user_data)
Definition tagging.c:2725
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:2074
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