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