1 /*
2 This file is part of darktable,
3 Copyright (C) 2010-2021 darktable developers.
4
5 darktable is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 darktable is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with darktable. If not, see <http://www.gnu.org/licenses/>.
17 */
18 #include "common/collection.h"
19 #include "common/darktable.h"
20 #include "common/debug.h"
21 #include "common/tags.h"
22 #include "control/conf.h"
23 #include "control/control.h"
24 #include "dtgtk/button.h"
25 #include "gui/accelerators.h"
26 #include "gui/gtk.h"
27 #include "gui/drag_and_drop.h"
28 #include "libs/lib.h"
29 #include "libs/lib_api.h"
30 #include "views/view.h"
31 #ifdef GDK_WINDOWING_QUARTZ
32 #include "osx/osx.h"
33 #endif
34 #include <gdk/gdkkeysyms.h>
35 #include <math.h>
36
37 #define FLOATING_ENTRY_WIDTH DT_PIXEL_APPLY_DPI(150)
38
39 DT_MODULE(1)
40
41 static gboolean _lib_tagging_tag_redo(GtkAccelGroup *accel_group, GObject *acceleratable, guint keyval,
42 GdkModifierType modifier, dt_lib_module_t *self);
43 static gboolean _lib_tagging_tag_show(GtkAccelGroup *accel_group, GObject *acceleratable, guint keyval,
44 GdkModifierType modifier, dt_lib_module_t *self);
45
46 typedef struct dt_lib_tagging_t
47 {
48 char keyword[1024];
49 GtkEntry *entry;
50 GtkTreeView *attached_view, *dictionary_view;
51 GtkWidget *attach_button, *detach_button, *new_button, *import_button, *export_button;
52 GtkWidget *toggle_tree_button, *toggle_suggestion_button, *toggle_sort_button, *toggle_hide_button, *toggle_dttags_button;
53 gulong tree_button_handler, suggestion_button_handler, sort_button_handler, hide_button_handler;
54 gulong dttags_button_handler;
55 GtkListStore *attached_liststore, *dictionary_liststore;
56 GtkTreeStore *dictionary_treestore;
57 GtkTreeModelFilter *dictionary_listfilter, *dictionary_treefilter;
58 GtkWidget *floating_tag_window;
59 GList *floating_tag_imgs;
60 gboolean tree_flag, suggestion_flag, sort_count_flag, hide_path_flag, dttags_flag;
61 char *collection;
62 GtkEntryCompletion *completion;
63 char *last_tag;
64 struct
65 {
66 gchar *tagname;
67 GtkTreePath *path, *lastpath;
68 int expand_timeout, scroll_timeout, last_y;
69 gboolean root, tag_source;
70 } drag;
71 } dt_lib_tagging_t;
72
73 typedef struct dt_tag_op_t
74 {
75 gint tagid;
76 char *newtagname;
77 char *oldtagname;
78 gboolean tree_flag;
79 } dt_tag_op_t;
80
81 typedef enum dt_lib_tagging_cols_t
82 {
83 DT_LIB_TAGGING_COL_TAG = 0,
84 DT_LIB_TAGGING_COL_ID,
85 DT_LIB_TAGGING_COL_PATH,
86 DT_LIB_TAGGING_COL_SYNONYM,
87 DT_LIB_TAGGING_COL_COUNT,
88 DT_LIB_TAGGING_COL_SEL,
89 DT_LIB_TAGGING_COL_FLAGS,
90 DT_LIB_TAGGING_COL_VISIBLE,
91 DT_LIB_TAGGING_NUM_COLS
92 } dt_lib_tagging_cols_t;
93
94 typedef enum dt_tag_sort_id
95 {
96 DT_TAG_SORT_PATH_ID,
97 DT_TAG_SORT_NAME_ID,
98 DT_TAG_SORT_COUNT_ID
99 } dt_tag_sort_id;
100
name(dt_lib_module_t * self)101 const char *name(dt_lib_module_t *self)
102 {
103 return _("tagging");
104 }
105
views(dt_lib_module_t * self)106 const char **views(dt_lib_module_t *self)
107 {
108 static const char *v1[] = {"lighttable", "darkroom", "map", "tethering", NULL};
109 static const char *v2[] = {"lighttable", "map", "tethering", NULL};
110
111 if(dt_conf_get_bool("plugins/darkroom/tagging/visible"))
112 return v1;
113 else
114 return v2;
115 }
116
container(dt_lib_module_t * self)117 uint32_t container(dt_lib_module_t *self)
118 {
119 const dt_view_t *cv = dt_view_manager_get_current_view(darktable.view_manager);
120 if(cv->view((dt_view_t *)cv) == DT_VIEW_DARKROOM)
121 return DT_UI_CONTAINER_PANEL_LEFT_CENTER;
122 else
123 return DT_UI_CONTAINER_PANEL_RIGHT_CENTER;
124 }
125
init_key_accels(dt_lib_module_t * self)126 void init_key_accels(dt_lib_module_t *self)
127 {
128 dt_accel_register_lib(self, NC_("accel", "attach"), 0, 0);
129 dt_accel_register_lib(self, NC_("accel", "detach"), 0, 0);
130 dt_accel_register_lib(self, NC_("accel", "new"), 0, 0);
131 dt_accel_register_lib(self, NC_("accel", "tag"), GDK_KEY_t, GDK_CONTROL_MASK);
132 dt_accel_register_lib(self, NC_("accel", "redo last tag"), GDK_KEY_t, GDK_MOD1_MASK);
133 }
134
connect_key_accels(dt_lib_module_t * self)135 void connect_key_accels(dt_lib_module_t *self)
136 {
137 dt_lib_tagging_t *d = (dt_lib_tagging_t *)self->data;
138
139 dt_accel_connect_button_lib(self, "attach", d->attach_button);
140 dt_accel_connect_button_lib(self, "detach", d->detach_button);
141 dt_accel_connect_button_lib(self, "new", d->new_button);
142 dt_accel_connect_lib(self, "tag", g_cclosure_new(G_CALLBACK(_lib_tagging_tag_show), self, NULL));
143 dt_accel_connect_lib(self, "redo last tag", g_cclosure_new(G_CALLBACK(_lib_tagging_tag_redo), self, NULL));
144 }
145
_update_atdetach_buttons(dt_lib_module_t * self)146 static void _update_atdetach_buttons(dt_lib_module_t *self)
147 {
148 dt_lib_cancel_postponed_update(self);
149 dt_lib_tagging_t *d = (dt_lib_tagging_t *)self->data;
150
151 const GList *imgs = dt_view_get_images_to_act_on(TRUE, FALSE, FALSE);
152 const gboolean has_act_on = imgs != NULL;
153
154 const gint dict_tags_sel_cnt =
155 gtk_tree_selection_count_selected_rows(gtk_tree_view_get_selection(GTK_TREE_VIEW(d->dictionary_view)));
156
157 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(d->attached_view));
158 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(d->attached_view));
159 GtkTreeIter iter;
160 gboolean attached_tags_sel = FALSE;
161 if(gtk_tree_selection_get_selected(selection, &model, &iter))
162 {
163 // check this is a darktable tag
164 char *path;
165 gtk_tree_model_get(model, &iter, DT_LIB_TAGGING_COL_PATH, &path, -1);
166 if(!g_str_has_prefix(path, "darktable|") || g_str_has_prefix(path, "darktable|style|"))
167 attached_tags_sel = TRUE;
168 g_free(path);
169 }
170
171 gtk_widget_set_sensitive(GTK_WIDGET(d->attach_button), has_act_on && dict_tags_sel_cnt > 0);
172 gtk_widget_set_sensitive(GTK_WIDGET(d->detach_button), has_act_on && attached_tags_sel);
173 }
174
_propagate_sel_to_parents(GtkTreeModel * model,GtkTreeIter * iter)175 static void _propagate_sel_to_parents(GtkTreeModel *model, GtkTreeIter *iter)
176 {
177 guint sel;
178 GtkTreeIter parent, child = *iter;
179 while(gtk_tree_model_iter_parent(model, &parent, &child))
180 {
181 gtk_tree_model_get(model, &parent, DT_LIB_TAGGING_COL_SEL, &sel, -1);
182 if (sel == DT_TS_NO_IMAGE)
183 gtk_tree_store_set(GTK_TREE_STORE(model), &parent,
184 DT_LIB_TAGGING_COL_SEL, DT_TS_SOME_IMAGES, -1);
185 child = parent;
186 }
187 }
188
_set_matching_tag_visibility(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,dt_lib_module_t * self)189 static gboolean _set_matching_tag_visibility(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, dt_lib_module_t *self)
190 {
191 dt_lib_tagging_t *d = (dt_lib_tagging_t *)self->data;
192 gboolean visible;
193 gchar *tagname = NULL;
194 gchar *synonyms = NULL;
195 gtk_tree_model_get(model, iter, DT_LIB_TAGGING_COL_PATH, &tagname, DT_LIB_TAGGING_COL_SYNONYM, &synonyms, -1);
196 if (!d->keyword[0])
197 visible = TRUE;
198 else
199 {
200 if (synonyms && synonyms[0]) tagname = dt_util_dstrcat(tagname, ", %s", synonyms);
201 gchar *haystack = g_utf8_strdown(tagname, -1);
202 gchar *needle = g_utf8_strdown(d->keyword, -1);
203 visible = (g_strrstr(haystack, needle) != NULL);
204 g_free(haystack);
205 g_free(needle);
206 }
207 if (d->tree_flag)
208 gtk_tree_store_set(GTK_TREE_STORE(model), iter, DT_LIB_TAGGING_COL_VISIBLE, visible, -1);
209 else
210 gtk_list_store_set(GTK_LIST_STORE(model), iter, DT_LIB_TAGGING_COL_VISIBLE, visible, -1);
211 g_free(tagname);
212 g_free(synonyms);
213 return FALSE;
214 }
215
_tree_reveal_func(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,gpointer data)216 static gboolean _tree_reveal_func(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
217 {
218 gboolean state;
219 GtkTreeIter parent, child = *iter;
220
221 gtk_tree_model_get(model, iter, DT_LIB_TAGGING_COL_VISIBLE, &state, -1);
222 if(!state) return FALSE;
223
224 while(gtk_tree_model_iter_parent(model, &parent, &child))
225 {
226 gtk_tree_model_get(model, &parent, DT_LIB_TAGGING_COL_VISIBLE, &state, -1);
227 gtk_tree_store_set(GTK_TREE_STORE(model), &parent, DT_LIB_TAGGING_COL_VISIBLE, TRUE, -1);
228 child = parent;
229 }
230 return FALSE;
231 }
232
_sort_attached_list(dt_lib_module_t * self,gboolean force)233 static void _sort_attached_list(dt_lib_module_t *self, gboolean force)
234 {
235 dt_lib_tagging_t *d = (dt_lib_tagging_t *)self->data;
236 if (force && d->sort_count_flag)
237 {
238 // ugly but when sorted by count _tree_tagname_show() is not triggered
239 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(d->attached_liststore), DT_TAG_SORT_NAME_ID, GTK_SORT_ASCENDING);
240 }
241 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;
242 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(d->attached_liststore), sort, GTK_SORT_ASCENDING);
243 }
244
_sort_dictionary_list(dt_lib_module_t * self,gboolean force)245 static void _sort_dictionary_list(dt_lib_module_t *self, gboolean force)
246 {
247 dt_lib_tagging_t *d = (dt_lib_tagging_t *)self->data;
248 if (!d->tree_flag)
249 {
250 if (force && d->sort_count_flag)
251 {
252 // ugly but when sorted by count _tree_tagname_show() is not triggered
253 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(d->dictionary_liststore), DT_TAG_SORT_NAME_ID, GTK_SORT_ASCENDING);
254 }
255 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;
256 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(d->dictionary_liststore), sort, GTK_SORT_ASCENDING);
257 }
258 else
259 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(d->dictionary_treestore), DT_TAG_SORT_PATH_ID, GTK_SORT_ASCENDING);
260 }
261
262 // find a tag on the tree
_find_tag_iter_tagname(GtkTreeModel * model,GtkTreeIter * iter,const char * tagname)263 static gboolean _find_tag_iter_tagname(GtkTreeModel *model, GtkTreeIter *iter,
264 const char *tagname)
265 {
266 gboolean found = FALSE;
267 if(!tagname) return found;
268 char *path;
269 do
270 {
271 gtk_tree_model_get(model, iter, DT_LIB_TAGGING_COL_PATH, &path, -1);
272 found = !g_strcmp0(tagname, path);
273 g_free(path);
274 if(found) return found;
275 GtkTreeIter child, parent = *iter;
276 if(gtk_tree_model_iter_children(model, &child, &parent))
277 {
278 found = _find_tag_iter_tagname(model, &child, tagname);
279 if(found)
280 {
281 *iter = child;
282 return found;
283 }
284 }
285 } while(gtk_tree_model_iter_next(model, iter));
286 return found;
287 }
288
289 // make the tag visible on view
_show_tag_on_view(GtkTreeView * view,const char * tagname)290 static void _show_tag_on_view(GtkTreeView *view, const char *tagname)
291 {
292 if(tagname)
293 {
294 char *lt = g_strdup(tagname);
295 char *t = g_strstrip(lt);
296 GtkTreeIter iter;
297 GtkTreeModel *model = gtk_tree_view_get_model(view);
298 if(gtk_tree_model_get_iter_first(model, &iter))
299 {
300 if(_find_tag_iter_tagname(model, &iter, t))
301 {
302 GtkTreePath *path = gtk_tree_model_get_path(model, &iter);
303 gtk_tree_view_expand_to_path(view, path);
304 gtk_tree_view_scroll_to_cell(view, path, NULL, TRUE, 0.5, 0.5);
305 gtk_tree_path_free(path);
306 GtkTreeSelection *selection = gtk_tree_view_get_selection(view);
307 gtk_tree_selection_select_iter(selection, &iter);
308 }
309 }
310 g_free(lt);
311 }
312 }
313
_init_treeview(dt_lib_module_t * self,const int which)314 static void _init_treeview(dt_lib_module_t *self, const int which)
315 {
316 dt_lib_tagging_t *d = (dt_lib_tagging_t *)self->data;
317 GList *tags = NULL;
318 uint32_t count;
319 GtkTreeIter iter;
320 GtkTreeView *view;
321 GtkTreeModel *store;
322 GtkTreeModel *model;
323 gboolean no_sel = FALSE;
324
325 if(which == 0) // tags of selected images
326 {
327 const int imgsel = dt_control_get_mouse_over_id();
328 no_sel = imgsel > 0 || dt_selected_images_count() == 1;
329 count = dt_tag_get_attached(imgsel, &tags, d->dttags_flag ? FALSE : TRUE);
330 view = d->attached_view;
331 model = gtk_tree_view_get_model(GTK_TREE_VIEW(view));
332 store = model;
333 }
334 else // dictionary_view tags of typed text
335 {
336 if (!d->tree_flag && d->suggestion_flag)
337 count = dt_tag_get_suggestions(&tags);
338 else
339 count = dt_tag_get_with_usage(&tags);
340 view = d->dictionary_view;
341 model = gtk_tree_view_get_model(GTK_TREE_VIEW(view));
342 if (d->tree_flag)
343 store = GTK_TREE_MODEL(d->dictionary_treestore);
344 else
345 store = GTK_TREE_MODEL(d->dictionary_liststore);
346 }
347 g_object_ref(model);
348 gtk_tree_view_set_model(GTK_TREE_VIEW(view), NULL);
349
350 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(store), GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, GTK_SORT_ASCENDING);
351 if (which && d->tree_flag)
352 {
353 gtk_tree_store_clear(GTK_TREE_STORE(store));
354 {
355 char **last_tokens = NULL;
356 int last_tokens_length = 0;
357 GtkTreeIter last_parent = { 0 };
358 GList *sorted_tags = dt_sort_tag(tags, 0); // ordered by full tag name
359 tags = sorted_tags;
360 for(GList *taglist = tags; taglist; taglist = g_list_next(taglist))
361 {
362 const gchar *tag = ((dt_tag_t *)taglist->data)->tag;
363 if(tag == NULL) continue;
364 char **tokens;
365 tokens = g_strsplit(tag, "|", -1);
366 if(tokens)
367 {
368 // find the number of common parts at the beginning of tokens and last_tokens
369 GtkTreeIter parent = last_parent;
370 const int tokens_length = g_strv_length(tokens);
371 int common_length = 0;
372 if(last_tokens)
373 {
374 while(tokens[common_length] && last_tokens[common_length] &&
375 !g_strcmp0(tokens[common_length], last_tokens[common_length]))
376 {
377 common_length++;
378 }
379
380 // point parent iter to where the entries should be added
381 for(int i = common_length; i < last_tokens_length; i++)
382 {
383 gtk_tree_model_iter_parent(GTK_TREE_MODEL(store), &parent, &last_parent);
384 last_parent = parent;
385 }
386 }
387
388 // insert everything from tokens past the common part
389 char *pth = NULL;
390 for(int i = 0; i < common_length; i++)
391 pth = dt_util_dstrcat(pth, "%s|", tokens[i]);
392
393 for(char **token = &tokens[common_length]; *token; token++)
394 {
395 pth = dt_util_dstrcat(pth, "%s|", *token);
396 gchar *pth2 = g_strdup(pth);
397 pth2[strlen(pth2) - 1] = '\0';
398 gtk_tree_store_insert(GTK_TREE_STORE(store), &iter, common_length > 0 ? &parent : NULL, -1);
399 gtk_tree_store_set(GTK_TREE_STORE(store), &iter,
400 DT_LIB_TAGGING_COL_TAG, *token,
401 DT_LIB_TAGGING_COL_ID, (token == &tokens[tokens_length-1]) ?
402 ((dt_tag_t *)taglist->data)->id : 0,
403 DT_LIB_TAGGING_COL_PATH, pth2,
404 DT_LIB_TAGGING_COL_COUNT, (token == &tokens[tokens_length-1]) ?
405 ((dt_tag_t *)taglist->data)->count : 0,
406 DT_LIB_TAGGING_COL_SEL, ((dt_tag_t *)taglist->data)->select,
407 DT_LIB_TAGGING_COL_FLAGS, ((dt_tag_t *)taglist->data)->flags,
408 DT_LIB_TAGGING_COL_SYNONYM, ((dt_tag_t *)taglist->data)->synonym,
409 DT_LIB_TAGGING_COL_VISIBLE, TRUE,
410 -1);
411 if (((dt_tag_t *)taglist->data)->select)
412 _propagate_sel_to_parents(GTK_TREE_MODEL(store), &iter);
413 common_length++;
414 parent = iter;
415 g_free(pth2);
416 }
417 g_free(pth);
418
419 // remember things for the next round
420 if(last_tokens) g_strfreev(last_tokens);
421 last_tokens = tokens;
422 last_parent = parent;
423 last_tokens_length = tokens_length;
424 }
425 }
426 g_strfreev(last_tokens);
427 }
428 if (d->keyword[0])
429 {
430 gtk_tree_model_foreach(store, (GtkTreeModelForeachFunc)_set_matching_tag_visibility, self);
431 gtk_tree_model_foreach(store, (GtkTreeModelForeachFunc)_tree_reveal_func, NULL);
432 gtk_tree_view_set_model(GTK_TREE_VIEW(view), model);
433 gtk_tree_view_expand_all(d->dictionary_view);
434 }
435 else gtk_tree_view_set_model(GTK_TREE_VIEW(view), model);
436 g_object_unref(model);
437 }
438 else
439 {
440 gtk_list_store_clear(GTK_LIST_STORE(store));
441 if(count > 0 && tags)
442 {
443 for (GList *tag = tags; tag; tag = g_list_next(tag))
444 {
445 const char *subtag = g_strrstr(((dt_tag_t *)tag->data)->tag, "|");
446 gtk_list_store_append(GTK_LIST_STORE(store), &iter);
447 gtk_list_store_set(GTK_LIST_STORE(store), &iter,
448 DT_LIB_TAGGING_COL_TAG, !subtag ? ((dt_tag_t *)tag->data)->tag : subtag + 1,
449 DT_LIB_TAGGING_COL_ID, ((dt_tag_t *)tag->data)->id,
450 DT_LIB_TAGGING_COL_PATH, ((dt_tag_t *)tag->data)->tag,
451 DT_LIB_TAGGING_COL_COUNT, ((dt_tag_t *)tag->data)->count,
452 DT_LIB_TAGGING_COL_SEL, no_sel ? DT_TS_NO_IMAGE :
453 ((dt_tag_t *)tag->data)->select,
454 DT_LIB_TAGGING_COL_FLAGS, ((dt_tag_t *)tag->data)->flags,
455 DT_LIB_TAGGING_COL_SYNONYM, ((dt_tag_t *)tag->data)->synonym,
456 DT_LIB_TAGGING_COL_VISIBLE, TRUE,
457 -1);
458 }
459 }
460 if (which && d->keyword[0])
461 {
462 gtk_tree_model_foreach(store, (GtkTreeModelForeachFunc)_set_matching_tag_visibility, self);
463 }
464 gtk_tree_view_set_model(GTK_TREE_VIEW(view), model);
465 g_object_unref(model);
466 }
467 if (which)
468 _sort_dictionary_list(self, FALSE);
469 else
470 _sort_attached_list(self, FALSE);
471 // Free result...
472 dt_tag_free_result(&tags);
473 }
474
_tree_tagname_show(GtkTreeViewColumn * col,GtkCellRenderer * renderer,GtkTreeModel * model,GtkTreeIter * iter,gpointer data,gboolean dictionary_view)475 static void _tree_tagname_show(GtkTreeViewColumn *col, GtkCellRenderer *renderer,
476 GtkTreeModel *model, GtkTreeIter *iter,
477 gpointer data, gboolean dictionary_view)
478 {
479 dt_lib_module_t *self = (dt_lib_module_t *)data;
480 dt_lib_tagging_t *d = (dt_lib_tagging_t *)self->data;
481 guint tagid;
482 gchar *name;
483 gchar *path;
484 guint count;
485 gchar *coltext;
486 gint flags;
487
488 gtk_tree_model_get(model, iter,
489 DT_LIB_TAGGING_COL_ID, &tagid,
490 DT_LIB_TAGGING_COL_TAG, &name,
491 DT_LIB_TAGGING_COL_COUNT, &count,
492 DT_LIB_TAGGING_COL_FLAGS, &flags,
493 DT_LIB_TAGGING_COL_PATH, &path, -1);
494 const gboolean hide = dictionary_view ? (d->tree_flag ? TRUE : d->hide_path_flag) : d->hide_path_flag;
495 const gboolean istag = !(flags & DT_TF_CATEGORY) && tagid;
496 if ((dictionary_view && !count) || (!dictionary_view && count <= 1))
497 {
498 coltext = g_markup_printf_escaped(istag ? "%s" : "<i>%s</i>", hide ? name : path);
499 }
500 else
501 {
502 coltext = g_markup_printf_escaped(istag ? "%s (%d)" : "<i>%s</i> (%d)", hide ? name : path, count);
503 }
504 g_object_set(renderer, "markup", coltext, NULL);
505 g_free(coltext);
506 g_free(name);
507 g_free(path);
508 }
509
_tree_tagname_show_attached(GtkTreeViewColumn * col,GtkCellRenderer * renderer,GtkTreeModel * model,GtkTreeIter * iter,gpointer data)510 static void _tree_tagname_show_attached(GtkTreeViewColumn *col, GtkCellRenderer *renderer,
511 GtkTreeModel *model, GtkTreeIter *iter,
512 gpointer data)
513 {
514 _tree_tagname_show(col, renderer, model, iter, data, 0);
515 }
516
_tree_tagname_show_dictionary(GtkTreeViewColumn * col,GtkCellRenderer * renderer,GtkTreeModel * model,GtkTreeIter * iter,gpointer data)517 static void _tree_tagname_show_dictionary(GtkTreeViewColumn *col, GtkCellRenderer *renderer,
518 GtkTreeModel *model, GtkTreeIter *iter,
519 gpointer data)
520 {
521 _tree_tagname_show(col, renderer, model, iter, data, 1);
522 }
523
_tree_select_show(GtkTreeViewColumn * col,GtkCellRenderer * renderer,GtkTreeModel * model,GtkTreeIter * iter,gpointer data)524 static void _tree_select_show(GtkTreeViewColumn *col, GtkCellRenderer *renderer,
525 GtkTreeModel *model, GtkTreeIter *iter,
526 gpointer data)
527 {
528 guint tagid;
529 guint select;
530 gboolean active = FALSE;
531 gboolean inconsistent = FALSE;
532
533 gtk_tree_model_get(model, iter, DT_LIB_TAGGING_COL_ID, &tagid,
534 DT_LIB_TAGGING_COL_SEL, &select, -1);
535 if (!tagid)
536 {
537 if (select != DT_TS_NO_IMAGE) inconsistent = TRUE;
538 }
539 else
540 {
541 if (select == DT_TS_ALL_IMAGES) active = TRUE;
542 else if (select == DT_TS_SOME_IMAGES) inconsistent = TRUE;
543 }
544 g_object_set(renderer, "active", active, "inconsistent", inconsistent, NULL);
545 }
546
_postponed_update(dt_lib_module_t * self)547 static void _postponed_update(dt_lib_module_t *self)
548 {
549 _init_treeview(self, 0);
550 _update_atdetach_buttons(self);
551 }
552
_lib_tagging_redraw_callback(gpointer instance,dt_lib_module_t * self)553 static void _lib_tagging_redraw_callback(gpointer instance, dt_lib_module_t *self)
554 {
555 dt_lib_queue_postponed_update(self, _postponed_update);
556 }
557
_lib_tagging_tags_changed_callback(gpointer instance,dt_lib_module_t * self)558 static void _lib_tagging_tags_changed_callback(gpointer instance, dt_lib_module_t *self)
559 {
560 _init_treeview(self, 0);
561 _init_treeview(self, 1);
562 }
563
_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)564 static void _collection_updated_callback(gpointer instance, dt_collection_change_t query_change,
565 dt_collection_properties_t changed_property, gpointer imgs, int next,
566 dt_lib_module_t *self)
567 {
568 dt_lib_tagging_t *d = (dt_lib_tagging_t *)self->data;
569 d->collection[0] = '\0';
570 _update_atdetach_buttons(self);
571 }
572
_raise_signal_tag_changed(dt_lib_module_t * self)573 static void _raise_signal_tag_changed(dt_lib_module_t *self)
574 {
575 dt_lib_tagging_t *d = (dt_lib_tagging_t *)self->data;
576 // when collection is on tag any attach & detach becomes very slow
577 // speeding up when jumping from tag collection to the other
578 // the cost is that tag collection doesn't reflect the tag changes real time
579 if (!d->collection[0])
580 {
581 // raises change only for other modules
582 dt_control_signal_block_by_func(darktable.signals, G_CALLBACK(_collection_updated_callback), self);
583 dt_control_signal_block_by_func(darktable.signals, G_CALLBACK(_lib_tagging_tags_changed_callback), self);
584 DT_DEBUG_CONTROL_SIGNAL_RAISE(darktable.signals, DT_SIGNAL_TAG_CHANGED);
585 dt_control_signal_unblock_by_func(darktable.signals, G_CALLBACK(_lib_tagging_tags_changed_callback), self);
586 dt_control_signal_unblock_by_func(darktable.signals, G_CALLBACK(_collection_updated_callback), self);
587 }
588 }
589
590 // find a tag on the tree
_find_tag_iter_tagid(GtkTreeModel * model,GtkTreeIter * iter,const gint tagid)591 static gboolean _find_tag_iter_tagid(GtkTreeModel *model, GtkTreeIter *iter, const gint tagid)
592 {
593 gint tag;
594 do
595 {
596 gtk_tree_model_get(model, iter, DT_LIB_TAGGING_COL_ID, &tag, -1);
597 if (tag == tagid)
598 {
599 return TRUE;
600 }
601 GtkTreeIter child, parent = *iter;
602 if (gtk_tree_model_iter_children(model, &child, &parent))
603 if (_find_tag_iter_tagid(model, &child, tagid))
604 {
605 *iter = child;
606 return TRUE;
607 }
608 } while (gtk_tree_model_iter_next(model, iter));
609 return FALSE;
610 }
611
612 // calculate the indeterminated state (1) where needed on the tree
_calculate_sel_on_path(GtkTreeModel * model,GtkTreeIter * iter,const gboolean root)613 static void _calculate_sel_on_path(GtkTreeModel *model, GtkTreeIter *iter, const gboolean root)
614 {
615 GtkTreeIter child, parent = *iter;
616 do
617 {
618 gint sel = DT_TS_NO_IMAGE;
619 gtk_tree_model_get(model, &parent, DT_LIB_TAGGING_COL_SEL, &sel, -1);
620 if (sel == DT_TS_ALL_IMAGES)
621 {
622 _propagate_sel_to_parents(model, &parent);
623 }
624 if (gtk_tree_model_iter_children(model, &child, &parent))
625 _calculate_sel_on_path(model, &child, FALSE);
626 } while (!root && gtk_tree_model_iter_next(model, &parent));
627 }
628
629 // reset the indeterminated selection (1) on the tree
_reset_sel_on_path(GtkTreeModel * model,GtkTreeIter * iter,const gboolean root)630 static void _reset_sel_on_path(GtkTreeModel *model, GtkTreeIter *iter, const gboolean root)
631 {
632 GtkTreeIter child, parent = *iter;
633 do
634 {
635 if (gtk_tree_model_iter_children(model, &child, &parent))
636 {
637 gint sel = DT_TS_NO_IMAGE;
638 gtk_tree_model_get(model, &parent, DT_LIB_TAGGING_COL_SEL, &sel, -1);
639 if (sel == DT_TS_SOME_IMAGES)
640 {
641 gtk_tree_store_set(GTK_TREE_STORE(model), &parent,
642 DT_LIB_TAGGING_COL_SEL, DT_TS_NO_IMAGE, -1);
643 }
644 _reset_sel_on_path(model, &child, FALSE);
645 }
646 } while (!root && gtk_tree_model_iter_next(model, &parent));
647 }
648
649 // reset all selection (1 & 2) on the tree
_reset_sel_on_path_full(GtkTreeModel * model,GtkTreeIter * iter,const gboolean root)650 static void _reset_sel_on_path_full(GtkTreeModel *model, GtkTreeIter *iter, const gboolean root)
651 {
652 GtkTreeIter child, parent = *iter;
653 do
654 {
655 if(GTK_IS_TREE_STORE(model))
656 {
657 gtk_tree_store_set(GTK_TREE_STORE(model), &parent,
658 DT_LIB_TAGGING_COL_SEL, DT_TS_NO_IMAGE, -1);
659 if (gtk_tree_model_iter_children(model, &child, &parent))
660 _reset_sel_on_path_full(model, &child, FALSE);
661 }
662 else
663 {
664 gtk_list_store_set(GTK_LIST_STORE(model), &parent,
665 DT_LIB_TAGGING_COL_SEL, DT_TS_NO_IMAGE, -1);
666 }
667 } while (!root && gtk_tree_model_iter_next(model, &parent));
668 }
669
670 // try to find a node fully attached (2) which is the root of the update loop. If not the full tree will be used
_find_root_iter_iter(GtkTreeModel * model,GtkTreeIter * iter,GtkTreeIter * parent)671 static void _find_root_iter_iter(GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *parent)
672 {
673 guint sel;
674 GtkTreeIter child = *iter;
675 while (gtk_tree_model_iter_parent(model, parent, &child))
676 {
677 gtk_tree_model_get(model, parent, DT_LIB_TAGGING_COL_SEL, &sel, -1);
678 if (sel == DT_TS_ALL_IMAGES)
679 {
680 char *path = NULL;
681 gtk_tree_model_get(model, parent, DT_LIB_TAGGING_COL_PATH, &path, -1);
682 g_free(path);
683 return; // no need to go further
684 }
685 child = *parent;
686 }
687 *parent = child; // last before root
688 char *path = NULL;
689 gtk_tree_model_get(model, parent, DT_LIB_TAGGING_COL_PATH, &path, -1);
690 g_free(path);
691 }
692
693 // with tag detach update the tree selection
_calculate_sel_on_tree(GtkTreeModel * model,GtkTreeIter * iter)694 static void _calculate_sel_on_tree(GtkTreeModel *model, GtkTreeIter *iter)
695 {
696 GtkTreeIter parent;
697 if (iter)
698 {
699 // only on sub-tree
700 _find_root_iter_iter(model, iter, &parent);
701 _reset_sel_on_path(model, &parent, TRUE);
702 _calculate_sel_on_path(model, &parent, TRUE);
703 }
704 else
705 {
706 // on full tree
707 if(gtk_tree_model_get_iter_first(model, &parent))
708 {
709 _reset_sel_on_path(model, &parent, FALSE);
710 _calculate_sel_on_path(model, &parent, FALSE);
711 }
712 }
713 }
714
715 // get the new selected images and update the tree selection
_update_sel_on_tree(GtkTreeModel * model)716 static void _update_sel_on_tree(GtkTreeModel *model)
717 {
718 GList *tags = NULL;
719 dt_tag_get_attached(-1, &tags, TRUE);
720 GtkTreeIter parent;
721 if(gtk_tree_model_get_iter_first(model, &parent))
722 {
723 _reset_sel_on_path_full(model, &parent, FALSE);
724 for (GList *tag = tags; tag; tag = g_list_next(tag))
725 {
726 GtkTreeIter iter = parent;
727 if (_find_tag_iter_tagid(model, &iter, ((dt_tag_t *)tag->data)->id))
728 {
729 if(GTK_IS_TREE_STORE(model))
730 {
731 gtk_tree_store_set(GTK_TREE_STORE(model), &iter,
732 DT_LIB_TAGGING_COL_SEL, ((dt_tag_t *)tag->data)->select, -1);
733 _propagate_sel_to_parents(model, &iter);
734 }
735 else
736 {
737 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
738 DT_LIB_TAGGING_COL_SEL, ((dt_tag_t *)tag->data)->select, -1);
739 }
740 }
741 }
742 }
743 if(tags)
744 dt_tag_free_result(&tags);
745 }
746
747 // delete a tag in the tree (tree or list)
_delete_tree_tag(GtkTreeModel * model,GtkTreeIter * iter,gboolean tree)748 static void _delete_tree_tag(GtkTreeModel *model, GtkTreeIter *iter, gboolean tree)
749 {
750 guint tagid = 0;
751 gtk_tree_model_get(model, iter, DT_LIB_TAGGING_COL_ID, &tagid, -1);
752 if (tree)
753 {
754 if (tagid)
755 {
756 gtk_tree_store_set(GTK_TREE_STORE(model), iter,
757 DT_LIB_TAGGING_COL_SEL, DT_TS_NO_IMAGE,
758 DT_LIB_TAGGING_COL_ID, 0,
759 DT_LIB_TAGGING_COL_COUNT, 0, -1);
760 _calculate_sel_on_tree(model, iter);
761 GtkTreeIter child, parent = *iter;
762 if (!gtk_tree_model_iter_children(model, &child, &parent))
763 gtk_tree_store_remove(GTK_TREE_STORE(model), iter);
764 }
765 }
766 else
767 {
768 gtk_list_store_remove(GTK_LIST_STORE(model), iter);
769 }
770 }
771
772 // delete a branch of the tag tree
_delete_tree_path(GtkTreeModel * model,GtkTreeIter * iter,gboolean root,gboolean tree)773 static void _delete_tree_path(GtkTreeModel *model, GtkTreeIter *iter, gboolean root, gboolean tree)
774 {
775 if (tree) // the treeview is tree. It handles the hierarchy itself (parent / child)
776 {
777 GtkTreeIter child, parent = *iter;
778 gboolean valid = TRUE;
779 do
780 {
781 if (gtk_tree_model_iter_children(model, &child, &parent))
782 _delete_tree_path(model, &child, FALSE, tree);
783 GtkTreeIter tobedel = parent;
784 valid = gtk_tree_model_iter_next(model, &parent);
785 if (root)
786 {
787 gtk_tree_store_set(GTK_TREE_STORE(model), &tobedel,
788 DT_LIB_TAGGING_COL_SEL, DT_TS_NO_IMAGE,
789 DT_LIB_TAGGING_COL_COUNT, 0, -1);
790
791 char *path2 = NULL;
792 gtk_tree_model_get(model, &tobedel, DT_LIB_TAGGING_COL_PATH, &path2, -1);
793 g_free(path2);
794
795 _calculate_sel_on_tree(model, &tobedel);
796 }
797 char *path = NULL;
798 gtk_tree_model_get(model, &tobedel, DT_LIB_TAGGING_COL_PATH, &path, -1);
799 g_free(path);
800 gtk_tree_store_remove(GTK_TREE_STORE(model), &tobedel);
801 } while (!root && valid);
802 }
803 else // treeview is a list. The hierarchy of tags is found with the root (left part) of tagname
804 {
805 GtkTreeIter child;
806 char *path = NULL;
807 gtk_tree_model_get(model, iter, DT_LIB_TAGGING_COL_PATH, &path, -1);
808 guint pathlen = strlen(path);
809 gboolean valid = gtk_tree_model_get_iter_first(model, &child);
810 while(valid)
811 {
812 char *path2 = NULL;
813 gtk_tree_model_get(model, &child, DT_LIB_TAGGING_COL_PATH, &path2, -1);
814 GtkTreeIter tobedel = child;
815 valid = gtk_tree_model_iter_next (model, &child);
816 if (strlen(path2) >= pathlen)
817 {
818 char letter = path2[pathlen];
819 path2[pathlen] = '\0';
820 if (g_strcmp0(path, path2) == 0)
821 {
822 path2[pathlen] = letter;
823 gtk_list_store_remove(GTK_LIST_STORE(model), &tobedel);
824 }
825 }
826 g_free(path2);
827 }
828 g_free(path);
829 }
830 }
831
_lib_selection_changed_callback(gpointer instance,dt_lib_module_t * self)832 static void _lib_selection_changed_callback(gpointer instance, dt_lib_module_t *self)
833 {
834 dt_lib_tagging_t *d = (dt_lib_tagging_t *)self->data;
835 _init_treeview(self, 0);
836 if (!d->tree_flag && d->suggestion_flag)
837 {
838 _init_treeview(self, 1);
839 }
840 else
841 _update_sel_on_tree(d->tree_flag ? GTK_TREE_MODEL(d->dictionary_treestore)
842 : GTK_TREE_MODEL(d->dictionary_liststore));
843
844 _update_atdetach_buttons(self);
845 }
846
_set_keyword(dt_lib_module_t * self)847 static void _set_keyword(dt_lib_module_t *self)
848 {
849 dt_lib_tagging_t *d = (dt_lib_tagging_t *)self->data;
850 const gchar *beg = g_strrstr(gtk_entry_get_text(d->entry), ",");
851
852 if(!beg)
853 beg = gtk_entry_get_text(d->entry);
854 else
855 {
856 if(*beg == ',') beg++;
857 if(*beg == ' ') beg++;
858 }
859 g_strlcpy(d->keyword, beg, sizeof(d->keyword));
860 }
861
_update_tag_name_per_name(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,dt_tag_op_t * to)862 static gboolean _update_tag_name_per_name(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, dt_tag_op_t *to)
863 {
864 char *tagname;
865 char *newtagname = to->newtagname;
866 char *oldtagname = to->oldtagname;
867 gtk_tree_model_get(model, iter, DT_LIB_TAGGING_COL_PATH, &tagname, -1);
868 if (g_str_has_prefix(tagname, oldtagname))
869 {
870 if (strlen(tagname) == strlen(oldtagname))
871 {
872 // rename the tag itself
873 if (to->tree_flag)
874 {
875 char *subtag = g_strrstr(to->newtagname, "|");
876 subtag = (!subtag) ? newtagname : subtag + 1;
877 gtk_tree_store_set(GTK_TREE_STORE(model), iter, DT_LIB_TAGGING_COL_PATH,
878 newtagname, DT_LIB_TAGGING_COL_TAG, subtag, -1);
879 }
880 else
881 {
882 gtk_list_store_set(GTK_LIST_STORE(model), iter, DT_LIB_TAGGING_COL_PATH,
883 newtagname, DT_LIB_TAGGING_COL_TAG, newtagname, -1);
884 }
885 }
886 else if (strlen(tagname) > strlen(oldtagname) && tagname[strlen(oldtagname)] == '|')
887 {
888 // rename similar path
889 char *newpath = g_strconcat(newtagname, &tagname[strlen(oldtagname)] , NULL);
890 if (to->tree_flag)
891 {
892 gtk_tree_store_set(GTK_TREE_STORE(model), iter, DT_LIB_TAGGING_COL_PATH,
893 newpath, -1);
894 }
895 else
896 {
897 gtk_list_store_set(GTK_LIST_STORE(model), iter, DT_LIB_TAGGING_COL_PATH,
898 newpath, DT_LIB_TAGGING_COL_TAG, newpath, -1);
899 }
900 g_free(newpath);
901 }
902 }
903 g_free(tagname);
904 return FALSE;
905 }
906
_update_attached_count(const int tagid,GtkTreeView * view,const gboolean tree_flag)907 static void _update_attached_count(const int tagid, GtkTreeView *view, const gboolean tree_flag)
908 {
909 const guint count = dt_tag_images_count(tagid);
910 GtkTreeModel *model = gtk_tree_view_get_model(view);
911 GtkTreeModel *store = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
912 GtkTreeIter iter;
913 if(gtk_tree_model_get_iter_first(store, &iter))
914 {
915 if (_find_tag_iter_tagid(store, &iter, tagid))
916 {
917 if (tree_flag)
918 {
919 gtk_tree_store_set(GTK_TREE_STORE(store), &iter,
920 DT_LIB_TAGGING_COL_COUNT, count,
921 DT_LIB_TAGGING_COL_SEL, DT_TS_ALL_IMAGES, -1);
922 _calculate_sel_on_tree(GTK_TREE_MODEL(store), &iter);
923 }
924 else
925 {
926 gtk_list_store_set(GTK_LIST_STORE(store), &iter,
927 DT_LIB_TAGGING_COL_COUNT, count,
928 DT_LIB_TAGGING_COL_SEL, DT_TS_ALL_IMAGES, -1);
929 }
930 }
931 }
932 }
933
init_presets(dt_lib_module_t * self)934 void init_presets(dt_lib_module_t *self)
935 {
936
937 }
938
get_params(dt_lib_module_t * self,int * size)939 void *get_params(dt_lib_module_t *self, int *size)
940 {
941 char *params = NULL;
942 *size = 0;
943 GList *tags = NULL;
944 const guint count = dt_tag_get_attached(-1, &tags, TRUE);
945
946 if(count)
947 {
948 for(GList *taglist = tags; taglist; taglist = g_list_next(taglist))
949 {
950 params = dt_util_dstrcat(params, "%d,", ((dt_tag_t *)taglist->data)->id);
951 }
952 dt_tag_free_result(&tags);
953 if(params == NULL)
954 return NULL;
955 *size = strlen(params);
956 params[*size-1]='\0';
957 }
958 return params;
959 }
960
set_params(dt_lib_module_t * self,const void * params,int size)961 int set_params(dt_lib_module_t *self, const void *params, int size)
962 {
963 if(!params || !size) return 1;
964 dt_lib_tagging_t *d = (dt_lib_tagging_t *)self->data;
965
966 const char *buf = (char *)params;
967 if (buf && buf[0])
968 {
969 gchar **tokens = g_strsplit(buf, ",", 0);
970 if(tokens)
971 {
972 GList *tags = NULL;
973 gchar **entry = tokens;
974 while(*entry)
975 {
976 const guint tagid = strtoul(*entry, NULL, 0);
977 tags = g_list_prepend(tags, GINT_TO_POINTER(tagid));
978 entry++;
979 }
980 g_strfreev(tokens);
981 GList *tags_r = dt_tag_get_tags(-1, TRUE);
982 const GList *imgs = dt_view_get_images_to_act_on(TRUE, FALSE, FALSE);
983 dt_tag_set_tags(tags, imgs, TRUE, TRUE, TRUE);
984 gboolean change = FALSE;
985 for(GList *tag = tags; tag; tag = g_list_next(tag))
986 {
987 _update_attached_count(GPOINTER_TO_INT(tag->data), d->dictionary_view, d->tree_flag);
988 change = TRUE;
989 }
990 for(GList *tag = tags_r; tag; tag = g_list_next(tag))
991 {
992 if(!g_list_find(tags, tag->data))
993 {
994 _update_attached_count(GPOINTER_TO_INT(tag->data), d->dictionary_view, d->tree_flag);
995 change = TRUE;
996 }
997 }
998
999 if(change)
1000 {
1001 _init_treeview(self, 0);
1002 _raise_signal_tag_changed(self);
1003 dt_image_synch_xmp(-1);
1004 }
1005 g_list_free(tags);
1006 g_list_free(tags_r);
1007 }
1008 }
1009 return 0;
1010 }
1011
_attach_selected_tag(dt_lib_module_t * self,dt_lib_tagging_t * d)1012 static void _attach_selected_tag(dt_lib_module_t *self, dt_lib_tagging_t *d)
1013 {
1014 GtkTreeIter iter;
1015 GtkTreeModel *model = NULL;
1016 GtkTreeSelection *selection = gtk_tree_view_get_selection(d->dictionary_view);
1017 if(!gtk_tree_selection_get_selected(selection, &model, &iter)
1018 && !gtk_tree_model_get_iter_first(model, &iter))
1019 return;
1020 guint tagid;
1021 gtk_tree_model_get(model, &iter, DT_LIB_TAGGING_COL_ID, &tagid, -1);
1022 if(tagid <= 0) return;
1023
1024 // attach tag on images to act on
1025 if(dt_tag_attach(tagid, -1, TRUE, TRUE))
1026 {
1027 /** record last tag used */
1028 g_free(d->last_tag);
1029 d->last_tag = g_strdup(dt_tag_get_name(tagid));
1030
1031 _init_treeview(self, 0);
1032 if (d->tree_flag || !d->suggestion_flag)
1033 {
1034 const uint32_t count = dt_tag_images_count(tagid);
1035 GtkTreeIter store_iter;
1036 GtkTreeModel *store = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
1037 gtk_tree_model_filter_convert_iter_to_child_iter(GTK_TREE_MODEL_FILTER(model),
1038 &store_iter, &iter);
1039 if (d->tree_flag)
1040 {
1041 gtk_tree_store_set(GTK_TREE_STORE(store), &store_iter,
1042 DT_LIB_TAGGING_COL_COUNT, count,
1043 DT_LIB_TAGGING_COL_SEL, DT_TS_ALL_IMAGES, -1);
1044 _propagate_sel_to_parents(GTK_TREE_MODEL(store), &store_iter);
1045 }
1046 else
1047 {
1048 gtk_list_store_set(GTK_LIST_STORE(store), &store_iter,
1049 DT_LIB_TAGGING_COL_COUNT, count,
1050 DT_LIB_TAGGING_COL_SEL, DT_TS_ALL_IMAGES, -1);
1051 }
1052 }
1053 else
1054 {
1055 _init_treeview(self, 1);
1056 }
1057 _raise_signal_tag_changed(self);
1058 dt_image_synch_xmp(-1);
1059 }
1060 }
1061
_detach_selected_tag(GtkTreeView * view,dt_lib_module_t * self,dt_lib_tagging_t * d)1062 static void _detach_selected_tag(GtkTreeView *view, dt_lib_module_t *self, dt_lib_tagging_t *d)
1063 {
1064 GtkTreeIter iter;
1065 GtkTreeModel *model = NULL;
1066 GtkTreeSelection *selection = gtk_tree_view_get_selection(view);
1067 if(!gtk_tree_selection_get_selected(selection, &model, &iter)) return;
1068 guint tagid;
1069 gtk_tree_model_get(model, &iter, DT_LIB_TAGGING_COL_ID, &tagid, -1);
1070 if(tagid <= 0) return;
1071
1072 const GList *imgs = dt_view_get_images_to_act_on(FALSE, TRUE, FALSE);
1073 if(!imgs) return;
1074
1075 GList *affected_images = dt_tag_get_images_from_list(imgs, tagid);
1076 if(affected_images)
1077 {
1078 const gboolean res = dt_tag_detach_images(tagid, affected_images, TRUE);
1079
1080 _init_treeview(self, 0);
1081 if (d->tree_flag || !d->suggestion_flag)
1082 {
1083 const guint count = dt_tag_images_count(tagid);
1084 model = gtk_tree_view_get_model(d->dictionary_view);
1085 GtkTreeModel *store = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
1086 if(gtk_tree_model_get_iter_first(store, &iter))
1087 {
1088 if (_find_tag_iter_tagid(store, &iter, tagid))
1089 {
1090 if (d->tree_flag)
1091 {
1092 gtk_tree_store_set(GTK_TREE_STORE(store), &iter,
1093 DT_LIB_TAGGING_COL_COUNT, count,
1094 DT_LIB_TAGGING_COL_SEL, DT_TS_NO_IMAGE, -1);
1095 _calculate_sel_on_tree(GTK_TREE_MODEL(store), &iter);
1096 }
1097 else
1098 {
1099 gtk_list_store_set(GTK_LIST_STORE(store), &iter,
1100 DT_LIB_TAGGING_COL_COUNT, count,
1101 DT_LIB_TAGGING_COL_SEL, DT_TS_NO_IMAGE, -1);
1102 }
1103 }
1104 }
1105 }
1106 else
1107 {
1108 _init_treeview(self, 1);
1109 }
1110 if(res)
1111 {
1112 _raise_signal_tag_changed(self);
1113 dt_image_synch_xmps(affected_images);
1114 }
1115 g_list_free(affected_images);
1116 }
1117 }
1118
_attach_button_clicked(GtkButton * button,dt_lib_module_t * self)1119 static void _attach_button_clicked(GtkButton *button, dt_lib_module_t *self)
1120 {
1121 dt_lib_tagging_t *d = (dt_lib_tagging_t *)self->data;
1122 _attach_selected_tag(self, d);
1123 }
1124
_detach_button_clicked(GtkButton * button,dt_lib_module_t * self)1125 static void _detach_button_clicked(GtkButton *button, dt_lib_module_t *self)
1126 {
1127 dt_lib_tagging_t *d = (dt_lib_tagging_t *)self->data;
1128 _detach_selected_tag(d->attached_view, self, d);
1129 }
1130
_pop_menu_attached_attach_to_all(GtkWidget * menuitem,dt_lib_module_t * self)1131 static void _pop_menu_attached_attach_to_all(GtkWidget *menuitem, dt_lib_module_t *self)
1132 {
1133 dt_lib_tagging_t *d = (dt_lib_tagging_t *)self->data;
1134 GtkTreeIter iter;
1135 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(d->attached_view));
1136 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(d->attached_view));
1137 if(!gtk_tree_selection_get_selected(selection, &model, &iter))
1138 return;
1139 guint tagid;
1140 gtk_tree_model_get(model, &iter, DT_LIB_TAGGING_COL_ID, &tagid, -1);
1141 if(tagid <= 0) return;
1142
1143 // attach tag on images to act on
1144 const gboolean res = dt_tag_attach(tagid, -1, TRUE, TRUE);
1145
1146 /** record last tag used */
1147 g_free(d->last_tag);
1148 d->last_tag = g_strdup(dt_tag_get_name(tagid));
1149
1150 _init_treeview(self, 0);
1151
1152 const uint32_t count = dt_tag_images_count(tagid);
1153 model = gtk_tree_view_get_model(GTK_TREE_VIEW(d->dictionary_view));
1154 if(gtk_tree_model_get_iter_first(model, &iter))
1155 {
1156 if(_find_tag_iter_tagid(model, &iter, tagid))
1157 {
1158 GtkTreeIter store_iter;
1159 GtkTreeModel *store = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
1160 gtk_tree_model_filter_convert_iter_to_child_iter(GTK_TREE_MODEL_FILTER(model),
1161 &store_iter, &iter);
1162 if (d->tree_flag)
1163 {
1164 gtk_tree_store_set(GTK_TREE_STORE(store), &store_iter, DT_LIB_TAGGING_COL_COUNT, count, -1);
1165 }
1166 else
1167 {
1168 gtk_list_store_set(GTK_LIST_STORE(store), &store_iter, DT_LIB_TAGGING_COL_COUNT, count, -1);
1169 }
1170 }
1171 }
1172
1173 if(res)
1174 {
1175 _raise_signal_tag_changed(self);
1176 dt_image_synch_xmp(-1);
1177 }
1178 }
1179
_pop_menu_attached_detach(GtkWidget * menuitem,dt_lib_module_t * self)1180 static void _pop_menu_attached_detach(GtkWidget *menuitem, dt_lib_module_t *self)
1181 {
1182 dt_lib_tagging_t *d = (dt_lib_tagging_t *)self->data;
1183 _detach_selected_tag(d->attached_view, self, d);
1184 }
1185
_pop_menu_attached(GtkWidget * treeview,GdkEventButton * event,dt_lib_module_t * self)1186 static void _pop_menu_attached(GtkWidget *treeview, GdkEventButton *event, dt_lib_module_t *self)
1187 {
1188 dt_lib_tagging_t *d = (dt_lib_tagging_t *)self->data;
1189 GtkWidget *menu, *menuitem;
1190 menu = gtk_menu_new();
1191
1192 GtkTreeIter iter;
1193 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(d->attached_view));
1194 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(d->attached_view));
1195 if(gtk_tree_selection_get_selected(selection, &model, &iter))
1196 {
1197 guint sel;
1198 gtk_tree_model_get(model, &iter, DT_LIB_TAGGING_COL_SEL, &sel, -1);
1199 if (sel == DT_TS_SOME_IMAGES)
1200 {
1201 menuitem = gtk_menu_item_new_with_label(_("attach tag to all"));
1202 g_signal_connect(menuitem, "activate", (GCallback)_pop_menu_attached_attach_to_all, self);
1203 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
1204 menuitem = gtk_separator_menu_item_new();
1205 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
1206 }
1207 }
1208
1209 menuitem = gtk_menu_item_new_with_label(_("detach tag"));
1210 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
1211 g_signal_connect(menuitem, "activate", (GCallback)_pop_menu_attached_detach, self);
1212
1213 gtk_widget_show_all(GTK_WIDGET(menu));
1214
1215 #if GTK_CHECK_VERSION(3, 22, 0)
1216 gtk_menu_popup_at_pointer(GTK_MENU(menu), (GdkEvent *)event);
1217 #else
1218 /* Note: event can be NULL here when called from view_onPopupMenu;
1219 * gdk_event_get_time() accepts a NULL argument */
1220 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, (event != NULL) ? event->button : 0,
1221 gdk_event_get_time((GdkEvent *)event));
1222 #endif
1223 }
1224
_click_on_view_attached(GtkWidget * view,GdkEventButton * event,dt_lib_module_t * self)1225 static gboolean _click_on_view_attached(GtkWidget *view, GdkEventButton *event, dt_lib_module_t *self)
1226 {
1227 dt_lib_tagging_t *d = (dt_lib_tagging_t *)self->data;
1228
1229 if((event->type == GDK_BUTTON_PRESS && event->button == 3)
1230 || (event->type == GDK_2BUTTON_PRESS && event->button == 1))
1231 {
1232 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
1233 GtkTreePath *path = NULL;
1234 // Get tree path for row that was clicked
1235 if(gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(view), (gint)event->x, (gint)event->y, &path, NULL, NULL, NULL))
1236 {
1237 gboolean valid_tag = FALSE;
1238 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(d->attached_view));
1239 GtkTreeIter iter;
1240 if(gtk_tree_model_get_iter(model, &iter, path))
1241 {
1242 // check this is a darktable tag
1243 char *tagpath;
1244 gtk_tree_model_get(model, &iter, DT_LIB_TAGGING_COL_PATH, &tagpath, -1);
1245 if(!g_str_has_prefix(tagpath, "darktable|"))
1246 valid_tag = TRUE;
1247 g_free(tagpath);
1248 }
1249 if(valid_tag)
1250 {
1251 gtk_tree_selection_select_path(selection, path);
1252 _update_atdetach_buttons(self);
1253 if(event->type == GDK_BUTTON_PRESS && event->button == 3)
1254 {
1255 _pop_menu_attached(view, event, self);
1256 gtk_tree_path_free(path);
1257 return TRUE;
1258 }
1259 else if(event->type == GDK_2BUTTON_PRESS && event->button == 1)
1260 {
1261 _detach_selected_tag(d->attached_view, self, d);
1262 gtk_tree_path_free(path);
1263 return TRUE;
1264 }
1265 }
1266 }
1267 gtk_tree_path_free(path);
1268 }
1269 return FALSE;
1270 }
1271
_new_button_clicked(GtkButton * button,dt_lib_module_t * self)1272 static void _new_button_clicked(GtkButton *button, dt_lib_module_t *self)
1273 {
1274 dt_lib_tagging_t *d = (dt_lib_tagging_t *)self->data;
1275 const gchar *tag = gtk_entry_get_text(d->entry);
1276 if(!tag || tag[0] == '\0') return;
1277
1278 const GList *imgs = dt_view_get_images_to_act_on(FALSE, TRUE, FALSE);
1279 const gboolean res = dt_tag_attach_string_list(tag, imgs, TRUE);
1280 if(res) dt_image_synch_xmps(imgs);
1281
1282 /** record last tag used */
1283 g_free(d->last_tag);
1284 d->last_tag = g_strdup(tag);
1285
1286 /** clear input box */
1287 gtk_entry_set_text(d->entry, "");
1288
1289 _init_treeview(self, 0);
1290 _init_treeview(self, 1);
1291 char *tagname = strrchr(d->last_tag, ',');
1292 if(res) _raise_signal_tag_changed(self);
1293 _show_tag_on_view(GTK_TREE_VIEW(d->dictionary_view),
1294 tagname ? tagname + 1 : d->last_tag);
1295 }
1296
_key_pressed(GtkWidget * entry,GdkEventKey * event,dt_lib_module_t * self)1297 static gboolean _key_pressed(GtkWidget *entry, GdkEventKey *event, dt_lib_module_t *self)
1298 {
1299 switch(event->keyval)
1300 {
1301 case GDK_KEY_Return:
1302 case GDK_KEY_KP_Enter:
1303 _new_button_clicked(NULL, self);
1304 break;
1305 case GDK_KEY_Escape:
1306 gtk_window_set_focus(GTK_WINDOW(dt_ui_main_window(darktable.gui->ui)), NULL);
1307 break;
1308 default:
1309 break;
1310 }
1311 return FALSE;
1312 }
1313
_clear_entry_button_callback(GtkButton * button,dt_lib_module_t * self)1314 static void _clear_entry_button_callback(GtkButton *button, dt_lib_module_t *self)
1315 {
1316 dt_lib_tagging_t *d = (dt_lib_tagging_t *)self->data;
1317 /** clear input box */
1318 gtk_entry_set_text(d->entry, "");
1319 }
1320
_tag_name_changed(GtkEntry * entry,dt_lib_module_t * self)1321 static void _tag_name_changed(GtkEntry *entry, dt_lib_module_t *self)
1322 {
1323 dt_lib_tagging_t *d = (dt_lib_tagging_t *)self->data;
1324 _set_keyword(self);
1325 GtkTreeModel *model = gtk_tree_view_get_model(d->dictionary_view);
1326 GtkTreeModel *store = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
1327 gtk_tree_model_foreach(store, (GtkTreeModelForeachFunc)_set_matching_tag_visibility, self);
1328 if (d->tree_flag && d->keyword[0])
1329 {
1330 gtk_tree_model_foreach(store, (GtkTreeModelForeachFunc)_tree_reveal_func, NULL);
1331 gtk_tree_view_expand_all(d->dictionary_view);
1332 }
1333 }
1334
_pop_menu_dictionary_delete_tag(GtkWidget * menuitem,dt_lib_module_t * self,gboolean branch)1335 static void _pop_menu_dictionary_delete_tag(GtkWidget *menuitem, dt_lib_module_t *self, gboolean branch)
1336 {
1337 dt_lib_tagging_t *d = (dt_lib_tagging_t *)self->data;
1338
1339 int res = GTK_RESPONSE_YES;
1340
1341 char *tagname;
1342 gint tagid;
1343 gchar *text;
1344 GtkWidget *label;
1345 GtkTreeIter iter;
1346 GtkTreeModel *model = NULL;
1347 GtkTreeView *view = d->dictionary_view;
1348 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
1349 if(!gtk_tree_selection_get_selected(selection, &model, &iter)) return;
1350
1351 gtk_tree_model_get(model, &iter, DT_LIB_TAGGING_COL_PATH, &tagname,
1352 DT_LIB_TAGGING_COL_ID, &tagid, -1);
1353 if (!tagid) return;
1354 const guint img_count = dt_tag_remove(tagid, FALSE);
1355
1356 if (img_count > 0 || dt_conf_get_bool("plugins/lighttable/tagging/ask_before_delete_tag"))
1357 {
1358 GtkWidget *win = dt_ui_main_window(darktable.gui->ui);
1359 GtkWidget *dialog = gtk_dialog_new_with_buttons(_("delete tag?"), GTK_WINDOW(win), GTK_DIALOG_DESTROY_WITH_PARENT,
1360 _("cancel"), GTK_RESPONSE_NONE, _("delete"), GTK_RESPONSE_YES, NULL);
1361 gtk_window_set_default_size(GTK_WINDOW(dialog), 300, -1);
1362 GtkWidget *area = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
1363 GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 8);
1364 gtk_container_set_border_width(GTK_CONTAINER(vbox), 8);
1365 gtk_container_add(GTK_CONTAINER(area), vbox);
1366 text = g_strdup_printf(_("selected: %s"), tagname);
1367 label = gtk_label_new(text);
1368 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, TRUE, 0);
1369 g_free(text);
1370
1371 GtkWidget *box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 5);
1372 gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, TRUE, 0);
1373 text = g_markup_printf_escaped(ngettext("do you really want to delete the tag `%s'?\n%d image is assigned this tag!",
1374 "do you really want to delete the tag `%s'?\n%d images are assigned this tag!", img_count), tagname, img_count);
1375 label = gtk_label_new(NULL);
1376 gtk_label_set_markup(GTK_LABEL(label), text);
1377 gtk_box_pack_start(GTK_BOX(box), label, FALSE, TRUE, 0);
1378 g_free(text);
1379
1380 #ifdef GDK_WINDOWING_QUARTZ
1381 dt_osx_disallow_fullscreen(dialog);
1382 #endif
1383 gtk_widget_show_all(dialog);
1384
1385 res = gtk_dialog_run(GTK_DIALOG(dialog));
1386 gtk_widget_destroy(dialog);
1387 }
1388 if(res != GTK_RESPONSE_YES)
1389 {
1390 g_free(tagname);
1391 return;
1392 }
1393
1394 GList *tagged_images = NULL;
1395 sqlite3_stmt *stmt;
1396 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), "SELECT imgid FROM main.tagged_images WHERE tagid=?1",
1397 -1, &stmt, NULL);
1398 DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, tagid);
1399 while(sqlite3_step(stmt) == SQLITE_ROW)
1400 {
1401 tagged_images = g_list_prepend(tagged_images, GINT_TO_POINTER(sqlite3_column_int(stmt, 0)));
1402 }
1403 sqlite3_finalize(stmt);
1404 tagged_images = g_list_reverse(tagged_images); // list was built in reverse order, so unreverse it
1405
1406 dt_tag_remove(tagid, TRUE);
1407 dt_control_log(_("tag %s removed"), tagname);
1408
1409 GtkTreeIter store_iter;
1410 GtkTreeModel *store = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
1411 gtk_tree_model_filter_convert_iter_to_child_iter(GTK_TREE_MODEL_FILTER(model),
1412 &store_iter, &iter);
1413 _delete_tree_tag(GTK_TREE_MODEL(store), &store_iter, d->tree_flag);
1414 _init_treeview(self, 0);
1415
1416 dt_image_synch_xmps(tagged_images);
1417 g_list_free(tagged_images);
1418 g_free(tagname);
1419 _raise_signal_tag_changed(self);
1420 }
1421
_pop_menu_dictionary_delete_node(GtkWidget * menuitem,dt_lib_module_t * self)1422 static void _pop_menu_dictionary_delete_node(GtkWidget *menuitem, dt_lib_module_t *self)
1423 {
1424 dt_lib_tagging_t *d = (dt_lib_tagging_t *)self->data;
1425
1426 int res = GTK_RESPONSE_YES;
1427
1428 char *tagname;
1429 gint tagid;
1430 gchar *text;
1431 GtkWidget *label;
1432 GtkTreeIter iter;
1433 GtkTreeModel *model = NULL;
1434 GtkTreeView *view = d->dictionary_view;
1435 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
1436 if(!gtk_tree_selection_get_selected(selection, &model, &iter)) return;
1437
1438 gtk_tree_model_get(model, &iter, DT_LIB_TAGGING_COL_PATH, &tagname,
1439 DT_LIB_TAGGING_COL_ID, &tagid, -1);
1440
1441 gint tag_count = 0;
1442 gint img_count = 0;
1443 dt_tag_count_tags_images(tagname, &tag_count, &img_count);
1444 if (tag_count == 0) return;
1445
1446 GtkWidget *win = dt_ui_main_window(darktable.gui->ui);
1447 GtkWidget *dialog = gtk_dialog_new_with_buttons( _("delete node?"), GTK_WINDOW(win), GTK_DIALOG_DESTROY_WITH_PARENT,
1448 _("cancel"), GTK_RESPONSE_NONE, _("delete"), GTK_RESPONSE_YES, NULL);
1449 gtk_window_set_default_size(GTK_WINDOW(dialog), 300, -1);
1450 GtkWidget *area = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
1451 GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 8);
1452 gtk_container_set_border_width(GTK_CONTAINER(vbox), 8);
1453 gtk_container_add(GTK_CONTAINER(area), vbox);
1454 text = g_strdup_printf(_("selected: %s"), tagname);
1455 label = gtk_label_new(text);
1456 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, TRUE, 0);
1457 g_free(text);
1458
1459 GtkWidget *box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 5);
1460 gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, TRUE, 0);
1461 text = g_strdup_printf(ngettext("<u>%d</u> tag will be deleted.", "<u>%d</u> tags will be deleted.", tag_count), tag_count);
1462 label = gtk_label_new(NULL);
1463 gtk_label_set_markup(GTK_LABEL(label), text);
1464 gtk_box_pack_start(GTK_BOX(box), label, FALSE, TRUE, 0);
1465 g_free(text);
1466 text = g_strdup_printf(ngettext("<u>%d</u> image will be updated", "<u>%d</u> images will be updated ", img_count), img_count);
1467 label = gtk_label_new(NULL);
1468 gtk_label_set_markup(GTK_LABEL(label), text);
1469 gtk_box_pack_start(GTK_BOX(box), label, FALSE, TRUE, 0);
1470 g_free(text);
1471
1472 #ifdef GDK_WINDOWING_QUARTZ
1473 dt_osx_disallow_fullscreen(dialog);
1474 #endif
1475 gtk_widget_show_all(dialog);
1476
1477 res = gtk_dialog_run(GTK_DIALOG(dialog));
1478 gtk_widget_destroy(dialog);
1479 if (res != GTK_RESPONSE_YES)
1480 {
1481 g_free(tagname);
1482 return;
1483 }
1484
1485 GList *tag_family = NULL;
1486 GList *tagged_images = NULL;
1487 dt_tag_get_tags_images(tagname, &tag_family, &tagged_images);
1488
1489 dt_control_signal_block_by_func(darktable.signals, G_CALLBACK(_lib_tagging_tags_changed_callback), self);
1490 tag_count = dt_tag_remove_list(tag_family);
1491 dt_control_signal_unblock_by_func(darktable.signals, G_CALLBACK(_lib_tagging_tags_changed_callback), self);
1492 dt_control_log(_("%d tags removed"), tag_count);
1493
1494 GtkTreeIter store_iter;
1495 GtkTreeModel *store = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
1496 gtk_tree_model_filter_convert_iter_to_child_iter(GTK_TREE_MODEL_FILTER(model),
1497 &store_iter, &iter);
1498 _delete_tree_path(GTK_TREE_MODEL(store), &store_iter, TRUE, d->tree_flag);
1499 _init_treeview(self, 0);
1500
1501 dt_tag_free_result(&tag_family);
1502 dt_image_synch_xmps(tagged_images);
1503 g_list_free(tagged_images);
1504 _raise_signal_tag_changed(self);
1505 g_free(tagname);
1506 }
1507
1508 // create tag allows the user to create a single tag, which can be an element of the hierarchy or not
_pop_menu_dictionary_create_tag(GtkWidget * menuitem,dt_lib_module_t * self)1509 static void _pop_menu_dictionary_create_tag(GtkWidget *menuitem, dt_lib_module_t *self)
1510 {
1511 dt_lib_tagging_t *d = (dt_lib_tagging_t *)self->data;
1512
1513 char *tagname;
1514 char *path;
1515 gint tagid;
1516 gchar *text;
1517 GtkWidget *label;
1518 GtkTreeIter iter;
1519 GtkTreeModel *model = NULL;
1520 GtkTreeView *view = d->dictionary_view;
1521 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
1522 if(!gtk_tree_selection_get_selected(selection, &model, &iter)) return;
1523
1524 gtk_tree_model_get(model, &iter, DT_LIB_TAGGING_COL_TAG, &tagname,
1525 DT_LIB_TAGGING_COL_PATH, &path, DT_LIB_TAGGING_COL_ID, &tagid, -1);
1526
1527 GtkWidget *win = dt_ui_main_window(darktable.gui->ui);
1528 GtkWidget *dialog = gtk_dialog_new_with_buttons(_("create tag"), GTK_WINDOW(win), GTK_DIALOG_DESTROY_WITH_PARENT,
1529 _("cancel"), GTK_RESPONSE_NONE, _("save"), GTK_RESPONSE_YES, NULL);
1530 gtk_window_set_default_size(GTK_WINDOW(dialog), 300, -1);
1531 GtkWidget *area = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
1532 GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 8);
1533 gtk_container_set_border_width(GTK_CONTAINER(vbox), 8);
1534 gtk_container_add(GTK_CONTAINER(area), vbox);
1535
1536 GtkWidget *box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
1537 gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, TRUE, 0);
1538 label = gtk_label_new(_("name: "));
1539 gtk_box_pack_start(GTK_BOX(box), label, FALSE, TRUE, 0);
1540 GtkWidget *entry = gtk_entry_new();
1541 gtk_box_pack_end(GTK_BOX(box), entry, TRUE, TRUE, 0);
1542
1543 GtkWidget *category;
1544 GtkWidget *private;
1545 GtkWidget *parent;
1546 GtkTextBuffer *buffer = NULL;
1547 GtkWidget *vbox2 = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
1548 gtk_box_pack_start(GTK_BOX(vbox), vbox2, FALSE, TRUE, 0);
1549
1550 text = g_strdup_printf(_("add to: \"%s\" "), path);
1551 parent = gtk_check_button_new_with_label(text);
1552 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(parent), TRUE);
1553 gtk_box_pack_end(GTK_BOX(vbox2), parent, FALSE, TRUE, 0);
1554 g_free(text);
1555
1556 category = gtk_check_button_new_with_label(_("category"));
1557 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(category), FALSE);
1558 gtk_box_pack_end(GTK_BOX(vbox2), category, FALSE, TRUE, 0);
1559 private = gtk_check_button_new_with_label(_("private"));
1560 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(private), FALSE);
1561 gtk_box_pack_end(GTK_BOX(vbox2), private, FALSE, TRUE, 0);
1562
1563 box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
1564 gtk_box_pack_end(GTK_BOX(vbox), box, TRUE, TRUE, 0);
1565 label = gtk_label_new(_("synonyms: "));
1566 gtk_box_pack_start(GTK_BOX(box), label, FALSE, TRUE, 0);
1567 GtkWidget *synonyms = gtk_text_view_new();
1568 gtk_box_pack_end(GTK_BOX(box), synonyms, TRUE, TRUE, 0);
1569 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(synonyms), GTK_WRAP_WORD);
1570 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(synonyms));
1571
1572 #ifdef GDK_WINDOWING_QUARTZ
1573 dt_osx_disallow_fullscreen(dialog);
1574 #endif
1575 gtk_widget_show_all(dialog);
1576
1577 if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_YES)
1578 {
1579 const char *newtag = gtk_entry_get_text(GTK_ENTRY(entry));
1580 char *message = NULL;
1581 if (!newtag[0])
1582 message = _("empty tag is not allowed, aborting");
1583 char *new_tagname = NULL;
1584 const gboolean root = !gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(parent));
1585 if (!root)
1586 {
1587 new_tagname = g_strdup(path);
1588 new_tagname = dt_util_dstrcat(new_tagname, "|%s", newtag);
1589 }
1590 else new_tagname = g_strdup(newtag);
1591
1592 if (dt_tag_exists(new_tagname, NULL))
1593 message = _("tag name already exists. aborting.");
1594 if (message)
1595 {
1596 GtkWidget *warning_dialog = gtk_message_dialog_new(GTK_WINDOW(dialog), GTK_DIALOG_MODAL,
1597 GTK_MESSAGE_INFO, GTK_BUTTONS_CLOSE, "%s", message);
1598 gtk_dialog_run(GTK_DIALOG(warning_dialog));
1599 gtk_widget_destroy(warning_dialog);
1600 gtk_widget_destroy(dialog);
1601 g_free(tagname);
1602 return;
1603 }
1604 guint new_tagid = 0;
1605 if (dt_tag_new(new_tagname, &new_tagid))
1606 {
1607 const gint new_flags = ((gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(category)) ? DT_TF_CATEGORY : 0) |
1608 (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(private)) ? DT_TF_PRIVATE : 0));
1609 if (new_tagid) dt_tag_set_flags(new_tagid, new_flags);
1610 GtkTextIter start, end;
1611 gtk_text_buffer_get_start_iter(buffer, &start);
1612 gtk_text_buffer_get_end_iter(buffer, &end);
1613 gchar *new_synonyms_list = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1614 if (new_tagid && new_synonyms_list && new_synonyms_list[0])
1615 dt_tag_set_synonyms(new_tagid, new_synonyms_list);
1616 g_free(new_synonyms_list);
1617 _init_treeview(self, 1);
1618 _show_tag_on_view(view, new_tagname);
1619 }
1620 g_free(new_tagname);
1621 }
1622 _init_treeview(self, 0);
1623 gtk_widget_destroy(dialog);
1624 g_free(tagname);
1625 }
1626
1627 // edit tag allows the user to rename a single tag, which can be an element of the hierarchy and change other parameters
_pop_menu_dictionary_edit_tag(GtkWidget * menuitem,dt_lib_module_t * self)1628 static void _pop_menu_dictionary_edit_tag(GtkWidget *menuitem, dt_lib_module_t *self)
1629 {
1630 dt_lib_tagging_t *d = (dt_lib_tagging_t *)self->data;
1631
1632 char *tagname;
1633 char *synonyms_list;
1634 gint tagid;
1635 gchar *text;
1636 GtkWidget *label;
1637 GtkTreeIter iter;
1638 GtkTreeModel *model = NULL;
1639 GtkTreeView *view = d->dictionary_view;
1640 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
1641 if(!gtk_tree_selection_get_selected(selection, &model, &iter)) return;
1642
1643 gtk_tree_model_get(model, &iter, DT_LIB_TAGGING_COL_PATH, &tagname,
1644 DT_LIB_TAGGING_COL_SYNONYM, &synonyms_list, DT_LIB_TAGGING_COL_ID, &tagid, -1);
1645 char *subtag = g_strrstr(tagname, "|");
1646 if(subtag) subtag = subtag + 1;
1647 gint tag_count;
1648 gint img_count;
1649 dt_tag_count_tags_images(tagname, &tag_count, &img_count);
1650 if (tag_count == 0) return;
1651
1652 GtkWidget *win = dt_ui_main_window(darktable.gui->ui);
1653 GtkWidget *dialog = gtk_dialog_new_with_buttons(_("edit"), GTK_WINDOW(win), GTK_DIALOG_DESTROY_WITH_PARENT,
1654 _("cancel"), GTK_RESPONSE_NONE, _("save"), GTK_RESPONSE_YES, NULL);
1655 gtk_window_set_default_size(GTK_WINDOW(dialog), 300, -1);
1656 GtkWidget *area = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
1657 GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 8);
1658 gtk_container_set_border_width(GTK_CONTAINER(vbox), 8);
1659 gtk_container_add(GTK_CONTAINER(area), vbox);
1660 text = g_strdup_printf(_("selected: %s"), tagname);
1661 label = gtk_label_new(text);
1662 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, TRUE, 0);
1663 g_free(text);
1664
1665 GtkWidget *box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 5);
1666 gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, TRUE, 0);
1667 text = g_strdup_printf(ngettext("<u>%d</u> tag will be updated.", "<u>%d</u> tags will be updated.", tag_count), tag_count);
1668 label = gtk_label_new(NULL);
1669 gtk_label_set_markup(GTK_LABEL(label), text);
1670 gtk_box_pack_start(GTK_BOX(box), label, FALSE, TRUE, 0);
1671 g_free(text);
1672 text = g_strdup_printf(ngettext("<u>%d</u> image will be updated", "<u>%d</u> images will be updated ", img_count), img_count);
1673 label = gtk_label_new(NULL);
1674 gtk_label_set_markup(GTK_LABEL(label), text);
1675 gtk_box_pack_start(GTK_BOX(box), label, FALSE, TRUE, 0);
1676 g_free(text);
1677
1678 box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
1679 gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, TRUE, 0);
1680 label = gtk_label_new(_("name: "));
1681 gtk_box_pack_start(GTK_BOX(box), label, FALSE, TRUE, 0);
1682 GtkWidget *entry = gtk_entry_new();
1683 gtk_entry_set_text(GTK_ENTRY(entry), subtag ? subtag : tagname);
1684 gtk_box_pack_end(GTK_BOX(box), entry, TRUE, TRUE, 0);
1685
1686 gint flags = 0;
1687 GtkWidget *category;
1688 GtkWidget *private;
1689 GtkTextBuffer *buffer = NULL;
1690 if (tagid)
1691 {
1692 GtkWidget *vbox2 = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
1693 gtk_box_pack_start(GTK_BOX(vbox), vbox2, FALSE, TRUE, 0);
1694 flags = dt_tag_get_flags(tagid);
1695 category = gtk_check_button_new_with_label(_("category"));
1696 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(category), flags & DT_TF_CATEGORY);
1697 gtk_box_pack_end(GTK_BOX(vbox2), category, FALSE, TRUE, 0);
1698 private = gtk_check_button_new_with_label(_("private"));
1699 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(private), flags & DT_TF_PRIVATE);
1700 gtk_box_pack_end(GTK_BOX(vbox2), private, FALSE, TRUE, 0);
1701
1702 box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
1703 gtk_box_pack_end(GTK_BOX(vbox), box, TRUE, TRUE, 0);
1704 label = gtk_label_new(_("synonyms: "));
1705 gtk_box_pack_start(GTK_BOX(box), label, FALSE, TRUE, 0);
1706 GtkWidget *synonyms = gtk_text_view_new();
1707 gtk_box_pack_end(GTK_BOX(box), synonyms, TRUE, TRUE, 0);
1708 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(synonyms), GTK_WRAP_WORD);
1709 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(synonyms));
1710 if (synonyms_list) gtk_text_buffer_set_text(buffer, synonyms_list, -1);
1711 }
1712
1713 #ifdef GDK_WINDOWING_QUARTZ
1714 dt_osx_disallow_fullscreen(dialog);
1715 #endif
1716 gtk_widget_show_all(dialog);
1717
1718 if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_YES)
1719 {
1720 const char *newtag = gtk_entry_get_text(GTK_ENTRY(entry));
1721 if (g_strcmp0(newtag, subtag ? subtag : tagname) != 0)
1722 {
1723 // tag name has changed
1724 char *message = NULL;
1725 if (!newtag[0])
1726 message = _("empty tag is not allowed, aborting");
1727 if(strchr(newtag, '|') != 0)
1728 message = _("'|' character is not allowed for renaming tag.\nto modify the hierarchy use rename path instead. Aborting.");
1729 if (message)
1730 {
1731 GtkWidget *warning_dialog = gtk_message_dialog_new(GTK_WINDOW(dialog), GTK_DIALOG_MODAL,
1732 GTK_MESSAGE_INFO, GTK_BUTTONS_CLOSE, "%s", message);
1733 gtk_dialog_run(GTK_DIALOG(warning_dialog));
1734 gtk_widget_destroy(warning_dialog);
1735 gtk_widget_destroy(dialog);
1736 g_free(tagname);
1737 return;
1738 }
1739
1740 GList *tag_family = NULL;
1741 GList *tagged_images = NULL;
1742 dt_tag_get_tags_images(tagname, &tag_family, &tagged_images);
1743
1744 const int tagname_len = strlen(tagname);
1745 char *new_prefix_tag;
1746 if (subtag)
1747 {
1748 const int subtag_len = strlen(subtag);
1749 const char letter = tagname[tagname_len - subtag_len];
1750 tagname[tagname_len - subtag_len] = '\0';
1751 new_prefix_tag = g_strconcat(tagname, newtag, NULL);
1752 tagname[tagname_len - subtag_len] = letter;
1753 }
1754 else
1755 new_prefix_tag = (char *)newtag;
1756
1757 // check if one of the new tagnames already exists.
1758 gboolean tagname_exists = FALSE;
1759 for (GList *taglist = tag_family; taglist && !tagname_exists; taglist = g_list_next(taglist))
1760 {
1761 char *new_tagname = g_strconcat(new_prefix_tag, &((dt_tag_t *)taglist->data)->tag[tagname_len], NULL);
1762 tagname_exists = dt_tag_exists(new_tagname, NULL);
1763 if (tagname_exists)
1764 {
1765 GtkWidget *warning_dialog = gtk_message_dialog_new(GTK_WINDOW(dialog), GTK_DIALOG_MODAL,
1766 GTK_MESSAGE_INFO, GTK_BUTTONS_CLOSE,
1767 _("at least one new tag name (%s) already exists, aborting"), new_tagname);
1768 gtk_dialog_run(GTK_DIALOG(warning_dialog));
1769 gtk_widget_destroy(warning_dialog);
1770 g_free(new_tagname);
1771 if (subtag) g_free(new_prefix_tag);
1772 gtk_widget_destroy(dialog);
1773 g_free(tagname);
1774 return;
1775 };
1776 g_free(new_tagname);
1777 }
1778
1779 // rename related tags
1780 for (GList *taglist = tag_family; taglist; taglist = g_list_next(taglist))
1781 {
1782 char *new_tagname = g_strconcat(new_prefix_tag, &((dt_tag_t *)taglist->data)->tag[tagname_len], NULL);
1783 dt_tag_rename(((dt_tag_t *)taglist->data)->id, new_tagname);
1784 g_free(new_tagname);
1785 }
1786
1787 // update the store
1788 GtkTreeModel *store = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
1789 dt_tag_op_t *to = g_malloc(sizeof(dt_tag_op_t));
1790 to->tree_flag = d->tree_flag;
1791 to->oldtagname = tagname;
1792 to->newtagname = new_prefix_tag;
1793 gint sort_column;
1794 GtkSortType sort_order;
1795 gtk_tree_sortable_get_sort_column_id(GTK_TREE_SORTABLE(store), &sort_column, &sort_order);
1796 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(store), GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, GTK_SORT_ASCENDING);
1797 gtk_tree_model_foreach(store, (GtkTreeModelForeachFunc)_update_tag_name_per_name, to);
1798 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(store), sort_column, sort_order);
1799 g_free(to);
1800 if (subtag) g_free(new_prefix_tag);
1801
1802 _raise_signal_tag_changed(self);
1803 dt_tag_free_result(&tag_family);
1804 dt_image_synch_xmps(tagged_images);
1805 g_list_free(tagged_images);
1806 }
1807
1808 if (tagid)
1809 {
1810 gint new_flags = ((gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(category)) ? DT_TF_CATEGORY : 0) |
1811 (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(private)) ? DT_TF_PRIVATE : 0));
1812 GtkTextIter start, end;
1813 gtk_text_buffer_get_start_iter(buffer, &start);
1814 gtk_text_buffer_get_end_iter(buffer, &end);
1815 gchar *new_synonyms_list = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1816 // refresh iter
1817 gtk_tree_selection_get_selected(selection, &model, &iter);
1818 GtkTreeIter store_iter;
1819 GtkTreeModel *store = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
1820 gtk_tree_model_filter_convert_iter_to_child_iter(GTK_TREE_MODEL_FILTER(model),
1821 &store_iter, &iter);
1822 if (new_flags != (flags & (DT_TF_CATEGORY | DT_TF_PRIVATE)))
1823 {
1824 new_flags = (flags & ~(DT_TF_CATEGORY | DT_TF_PRIVATE)) | new_flags;
1825 dt_tag_set_flags(tagid, new_flags);
1826 if (!d->tree_flag)
1827 gtk_list_store_set(GTK_LIST_STORE(store), &store_iter, DT_LIB_TAGGING_COL_FLAGS, new_flags, -1);
1828 else
1829 gtk_tree_store_set(GTK_TREE_STORE(store), &store_iter, DT_LIB_TAGGING_COL_FLAGS, new_flags, -1);
1830 }
1831 if (new_synonyms_list && g_strcmp0(synonyms_list, new_synonyms_list) != 0)
1832 {
1833 dt_tag_set_synonyms(tagid, new_synonyms_list);
1834 if (!d->tree_flag)
1835 gtk_list_store_set(GTK_LIST_STORE(store), &store_iter, DT_LIB_TAGGING_COL_SYNONYM, new_synonyms_list, -1);
1836 else
1837 gtk_tree_store_set(GTK_TREE_STORE(store), &store_iter, DT_LIB_TAGGING_COL_SYNONYM, new_synonyms_list, -1);
1838 }
1839 g_free(new_synonyms_list);
1840 }
1841 }
1842 _init_treeview(self, 0);
1843 gtk_widget_destroy(dialog);
1844 g_free(tagname);
1845 }
1846
_apply_rename_path(GtkWidget * dialog,const char * tagname,const char * newtag,dt_lib_module_t * self)1847 static gboolean _apply_rename_path(GtkWidget *dialog, const char *tagname,
1848 const char *newtag, dt_lib_module_t *self)
1849 {
1850 dt_lib_tagging_t *d = (dt_lib_tagging_t *)self->data;
1851
1852 gboolean success = FALSE;
1853 GList *tag_family = NULL;
1854 GList *tagged_images = NULL;
1855 dt_tag_get_tags_images(tagname, &tag_family, &tagged_images);
1856
1857 // check if one of the new tagnames already exists.
1858 const int tagname_len = strlen(tagname);
1859 gboolean tagname_exists = FALSE;
1860 for(GList *taglist = tag_family; taglist && !tagname_exists; taglist = g_list_next(taglist))
1861 {
1862 char *new_tagname = g_strconcat(newtag, &((dt_tag_t *)taglist->data)->tag[tagname_len], NULL);
1863 tagname_exists = dt_tag_exists(new_tagname, NULL);
1864 if(tagname_exists)
1865 {
1866 GtkWidget *win;
1867 if(!dialog)
1868 win = dt_ui_main_window(darktable.gui->ui);
1869 else
1870 win = dialog;
1871
1872 GtkWidget *warning_dialog = gtk_message_dialog_new(GTK_WINDOW(win), GTK_DIALOG_MODAL,
1873 GTK_MESSAGE_INFO, GTK_BUTTONS_CLOSE,
1874 _("at least one new tagname (%s) already exists, aborting."), new_tagname);
1875 gtk_dialog_run(GTK_DIALOG(warning_dialog));
1876 gtk_widget_destroy(warning_dialog);
1877 }
1878 g_free(new_tagname);
1879 }
1880
1881 if(!tagname_exists)
1882 {
1883 for (GList *taglist = tag_family; taglist; taglist = g_list_next(taglist))
1884 {
1885 char *new_tagname = g_strconcat(newtag, &((dt_tag_t *)taglist->data)->tag[tagname_len], NULL);
1886 dt_tag_rename(((dt_tag_t *)taglist->data)->id, new_tagname);
1887 g_free(new_tagname);
1888 }
1889 _init_treeview(self, 0);
1890 _init_treeview(self, 1);
1891 dt_image_synch_xmps(tagged_images);
1892 _raise_signal_tag_changed(self);
1893 _show_tag_on_view(d->dictionary_view, newtag);
1894 success = TRUE;
1895 }
1896 dt_tag_free_result(&tag_family);
1897 g_list_free(tagged_images);
1898
1899 return success;
1900 }
1901
1902
1903 // rename path allows the user to redefine a hierarchy
_pop_menu_dictionary_change_path(GtkWidget * menuitem,dt_lib_module_t * self)1904 static void _pop_menu_dictionary_change_path(GtkWidget *menuitem, dt_lib_module_t *self)
1905 {
1906 dt_lib_tagging_t *d = (dt_lib_tagging_t *)self->data;
1907
1908 char *tagname;
1909 gint tagid;
1910 gchar *text;
1911 GtkWidget *label;
1912 GtkTreeIter iter;
1913 GtkTreeModel *model = NULL;
1914 GtkTreeView *view = d->dictionary_view;
1915 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
1916 if(!gtk_tree_selection_get_selected(selection, &model, &iter)) return;
1917
1918 gtk_tree_model_get(model, &iter, DT_LIB_TAGGING_COL_PATH, &tagname,
1919 DT_LIB_TAGGING_COL_ID, &tagid, -1);
1920
1921 gint tag_count = 0;
1922 gint img_count = 0;
1923 dt_tag_count_tags_images(tagname, &tag_count, &img_count);
1924 if (tag_count == 0) return;
1925
1926 GtkWidget *win = dt_ui_main_window(darktable.gui->ui);
1927 GtkWidget *dialog = gtk_dialog_new_with_buttons(_("change path"), GTK_WINDOW(win), GTK_DIALOG_DESTROY_WITH_PARENT,
1928 _("cancel"), GTK_RESPONSE_NONE, _("save"), GTK_RESPONSE_YES, NULL);
1929 gtk_window_set_default_size(GTK_WINDOW(dialog), 300, -1);
1930 GtkWidget *area = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
1931 GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 8);
1932 gtk_container_set_border_width(GTK_CONTAINER(vbox), 8);
1933 gtk_container_add(GTK_CONTAINER(area), vbox);
1934 text = g_strdup_printf(_("selected: %s"), tagname);
1935 label = gtk_label_new(text);
1936 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, TRUE, 0);
1937 g_free(text);
1938
1939 GtkWidget *box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 5);
1940 gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, TRUE, 0);
1941 text = g_strdup_printf(ngettext("<u>%d</u> tag will be updated.", "<u>%d</u> tags will be updated.", tag_count), tag_count);
1942 label = gtk_label_new(NULL);
1943 gtk_label_set_markup(GTK_LABEL(label), text);
1944 gtk_box_pack_start(GTK_BOX(box), label, FALSE, TRUE, 0);
1945 g_free(text);
1946 text = g_strdup_printf(ngettext("<u>%d</u> image will be updated", "<u>%d</u> images will be updated ", img_count), img_count);
1947 label = gtk_label_new(NULL);
1948 gtk_label_set_markup(GTK_LABEL(label), text);
1949 gtk_box_pack_start(GTK_BOX(box), label, FALSE, TRUE, 0);
1950 g_free(text);
1951
1952 GtkWidget *entry = gtk_entry_new();
1953 gtk_entry_set_text(GTK_ENTRY(entry), tagname);
1954 gtk_box_pack_start(GTK_BOX(vbox), entry, FALSE, TRUE, 0);
1955
1956 #ifdef GDK_WINDOWING_QUARTZ
1957 dt_osx_disallow_fullscreen(dialog);
1958 #endif
1959 gtk_widget_show_all(dialog);
1960
1961 if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_YES)
1962 {
1963 const char *newtag = gtk_entry_get_text(GTK_ENTRY(entry));
1964 if (g_strcmp0(newtag, tagname) == 0)
1965 return; // no change
1966 char *message = NULL;
1967 if (!newtag[0])
1968 message = _("empty tag is not allowed, aborting");
1969 if (strchr(newtag, '|') == &newtag[0] || strchr(newtag, '|') == &newtag[strlen(newtag)-1] || strstr(newtag, "||"))
1970 message = _("'|' misplaced, empty tag is not allowed, aborting");
1971 if (message)
1972 {
1973 GtkWidget *warning_dialog = gtk_message_dialog_new(GTK_WINDOW(dialog), GTK_DIALOG_MODAL,
1974 GTK_MESSAGE_INFO, GTK_BUTTONS_CLOSE, "%s", message);
1975 gtk_dialog_run(GTK_DIALOG(warning_dialog));
1976 gtk_widget_destroy(warning_dialog);
1977 gtk_widget_destroy(dialog);
1978 g_free(tagname);
1979 return;
1980 }
1981 _apply_rename_path(dialog, tagname, newtag, self);
1982 }
1983 gtk_widget_destroy(dialog);
1984 g_free(tagname);
1985 }
1986
_pop_menu_dictionary_goto_tag_collection(GtkWidget * menuitem,dt_lib_module_t * self)1987 static void _pop_menu_dictionary_goto_tag_collection(GtkWidget *menuitem, dt_lib_module_t *self)
1988 {
1989 dt_lib_tagging_t *d = (dt_lib_tagging_t *)self->data;
1990 GtkTreeIter iter;
1991 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(d->dictionary_view));
1992 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(d->dictionary_view));
1993 if(gtk_tree_selection_get_selected(selection, &model, &iter))
1994 {
1995 char *path;
1996 guint count;
1997 gtk_tree_model_get(model, &iter, DT_LIB_TAGGING_COL_PATH, &path, DT_LIB_TAGGING_COL_COUNT, &count, -1);
1998 if (count)
1999 {
2000 if (!d->collection[0]) dt_collection_serialize(d->collection, 4096);
2001 char *tag_collection = NULL;
2002 tag_collection = dt_util_dstrcat(tag_collection, "1:0:%d:%s$", DT_COLLECTION_PROP_TAG, path);
2003 dt_control_signal_block_by_func(darktable.signals, G_CALLBACK(_collection_updated_callback), self);
2004 dt_collection_deserialize(tag_collection);
2005 dt_control_signal_unblock_by_func(darktable.signals, G_CALLBACK(_collection_updated_callback), self);
2006 g_free(tag_collection);
2007 }
2008 g_free(path);
2009 }
2010 }
2011
_pop_menu_dictionary_goto_collection_back(GtkWidget * menuitem,dt_lib_module_t * self)2012 static void _pop_menu_dictionary_goto_collection_back(GtkWidget *menuitem, dt_lib_module_t *self)
2013 {
2014 dt_lib_tagging_t *d = (dt_lib_tagging_t *)self->data;
2015 if (d->collection[0])
2016 {
2017 dt_control_signal_block_by_func(darktable.signals, G_CALLBACK(_collection_updated_callback), self);
2018 dt_collection_deserialize(d->collection);
2019 dt_control_signal_unblock_by_func(darktable.signals, G_CALLBACK(_collection_updated_callback), self);
2020 d->collection[0] = '\0';
2021 }
2022 }
2023
_pop_menu_dictionary_copy_tag(GtkWidget * menuitem,dt_lib_module_t * self)2024 static void _pop_menu_dictionary_copy_tag(GtkWidget *menuitem, dt_lib_module_t *self)
2025 {
2026 dt_lib_tagging_t *d = (dt_lib_tagging_t *)self->data;
2027 GtkTreeIter iter;
2028 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(d->dictionary_view));
2029 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(d->dictionary_view));
2030 if(gtk_tree_selection_get_selected(selection, &model, &iter))
2031 {
2032 char *tag;
2033 gtk_tree_model_get(model, &iter, DT_LIB_TAGGING_COL_PATH, &tag, -1);
2034 gtk_entry_set_text(d->entry, tag);
2035 g_free(tag);
2036 gtk_entry_grab_focus_without_selecting(d->entry);
2037 }
2038 }
2039
_pop_menu_dictionary_attach_tag(GtkWidget * menuitem,dt_lib_module_t * self)2040 static void _pop_menu_dictionary_attach_tag(GtkWidget *menuitem, dt_lib_module_t *self)
2041 {
2042 dt_lib_tagging_t *d = (dt_lib_tagging_t *)self->data;
2043 _attach_selected_tag(self, d);
2044 }
2045
_pop_menu_dictionary_detach_tag(GtkWidget * menuitem,dt_lib_module_t * self)2046 static void _pop_menu_dictionary_detach_tag(GtkWidget *menuitem, dt_lib_module_t *self)
2047 {
2048 dt_lib_tagging_t *d = (dt_lib_tagging_t *)self->data;
2049 _detach_selected_tag(d->dictionary_view, self, d);
2050 }
2051
_pop_menu_dictionary_set_as_tag(GtkWidget * menuitem,dt_lib_module_t * self)2052 static void _pop_menu_dictionary_set_as_tag(GtkWidget *menuitem, dt_lib_module_t *self)
2053 {
2054 dt_lib_tagging_t *d = (dt_lib_tagging_t *)self->data;
2055 char *tagname;
2056 guint new_tagid;
2057
2058 GtkTreeIter iter;
2059 GtkTreeModel *model = NULL;
2060 GtkTreeView *view = d->dictionary_view;
2061 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
2062 if(!gtk_tree_selection_get_selected(selection, &model, &iter)) return;
2063
2064 gtk_tree_model_get(model, &iter, DT_LIB_TAGGING_COL_PATH, &tagname, -1);
2065
2066 dt_tag_new(tagname, &new_tagid);
2067 dt_control_log(_("tag %s created"), tagname);
2068
2069 _init_treeview(self, 1);
2070 _show_tag_on_view(d->dictionary_view, tagname);
2071 g_free(tagname);
2072
2073 }
2074
_pop_menu_dictionary(GtkWidget * treeview,GdkEventButton * event,dt_lib_module_t * self)2075 static void _pop_menu_dictionary(GtkWidget *treeview, GdkEventButton *event, dt_lib_module_t *self)
2076 {
2077 dt_lib_tagging_t *d = (dt_lib_tagging_t *)self->data;
2078 GtkTreeIter iter, child;
2079 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(d->dictionary_view));
2080 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(d->dictionary_view));
2081 if(gtk_tree_selection_get_selected(selection, &model, &iter))
2082 {
2083 guint tagid, count;
2084 gtk_tree_model_get(model, &iter,
2085 DT_LIB_TAGGING_COL_ID, &tagid,
2086 DT_LIB_TAGGING_COL_COUNT, &count, -1);
2087
2088 GtkWidget *menu, *menuitem;
2089 menu = gtk_menu_new();
2090
2091 if (tagid)
2092 {
2093 menuitem = gtk_menu_item_new_with_label(_("attach tag"));
2094 g_signal_connect(menuitem, "activate", (GCallback)_pop_menu_dictionary_attach_tag, self);
2095 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
2096
2097 menuitem = gtk_menu_item_new_with_label(_("detach tag"));
2098 g_signal_connect(menuitem, "activate", (GCallback)_pop_menu_dictionary_detach_tag, self);
2099 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
2100
2101 menuitem = gtk_separator_menu_item_new();
2102 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
2103 }
2104 if (d->tree_flag || !d->suggestion_flag)
2105 {
2106 menuitem = gtk_menu_item_new_with_label(_("create tag..."));
2107 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
2108 g_signal_connect(menuitem, "activate", (GCallback)_pop_menu_dictionary_create_tag, self);
2109
2110 if (tagid)
2111 {
2112 menuitem = gtk_menu_item_new_with_label(_("delete tag"));
2113 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
2114 g_signal_connect(menuitem, "activate", (GCallback)_pop_menu_dictionary_delete_tag, self);
2115 }
2116
2117 if(gtk_tree_model_iter_children(model, &child, &iter))
2118 {
2119 menuitem = gtk_menu_item_new_with_label(_("delete node"));
2120 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
2121 g_signal_connect(menuitem, "activate", (GCallback)_pop_menu_dictionary_delete_node, self);
2122 }
2123
2124 menuitem = gtk_menu_item_new_with_label(_("edit..."));
2125 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
2126 g_signal_connect(menuitem, "activate", (GCallback)_pop_menu_dictionary_edit_tag, self);
2127
2128 }
2129
2130 if (d->tree_flag)
2131 {
2132 menuitem = gtk_menu_item_new_with_label(_("change path..."));
2133 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
2134 g_signal_connect(menuitem, "activate", (GCallback)_pop_menu_dictionary_change_path, self);
2135 }
2136
2137 if (d->tree_flag && !tagid)
2138 {
2139 menuitem = gtk_separator_menu_item_new();
2140 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
2141
2142 menuitem = gtk_menu_item_new_with_label(_("set as a tag"));
2143 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
2144 g_signal_connect(menuitem, "activate", (GCallback)_pop_menu_dictionary_set_as_tag, self);
2145 }
2146
2147 if (!d->suggestion_flag)
2148 {
2149 menuitem = gtk_separator_menu_item_new();
2150 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
2151 }
2152
2153 menuitem = gtk_menu_item_new_with_label(_("copy to entry"));
2154 g_signal_connect(menuitem, "activate", (GCallback)_pop_menu_dictionary_copy_tag, self);
2155 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
2156
2157 if (d->collection[0])
2158 {
2159 char *collection = g_malloc(4096);
2160 dt_collection_serialize(collection, 4096);
2161 if (g_strcmp0(d->collection, collection) == 0) d->collection[0] = '\0';
2162 g_free(collection);
2163 }
2164 if (count || d->collection[0])
2165 {
2166 menuitem = gtk_separator_menu_item_new();
2167 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
2168 if (count)
2169 {
2170 menuitem = gtk_menu_item_new_with_label(_("go to tag collection"));
2171 g_signal_connect(menuitem, "activate", (GCallback)_pop_menu_dictionary_goto_tag_collection, self);
2172 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
2173 }
2174 if (d->collection[0])
2175 {
2176 menuitem = gtk_menu_item_new_with_label(_("go back to work"));
2177 g_signal_connect(menuitem, "activate", (GCallback)_pop_menu_dictionary_goto_collection_back, self);
2178 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
2179 }
2180 }
2181 gtk_widget_show_all(GTK_WIDGET(menu));
2182
2183 #if GTK_CHECK_VERSION(3, 22, 0)
2184 gtk_menu_popup_at_pointer(GTK_MENU(menu), (GdkEvent *)event);
2185 #else
2186 /* Note: event can be NULL here when called from view_onPopupMenu;
2187 * gdk_event_get_time() accepts a NULL argument */
2188 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, (event != NULL) ? event->button : 0,
2189 gdk_event_get_time((GdkEvent *)event));
2190 #endif
2191 }
2192 }
2193
_click_on_view_dictionary(GtkWidget * view,GdkEventButton * event,dt_lib_module_t * self)2194 static gboolean _click_on_view_dictionary(GtkWidget *view, GdkEventButton *event, dt_lib_module_t *self)
2195 {
2196 dt_lib_tagging_t *d = (dt_lib_tagging_t *)self->data;
2197
2198 const int button_pressed = (event->type == GDK_BUTTON_PRESS) ? event->button : 0;
2199 const gboolean shift_pressed = dt_modifier_is(event->state, GDK_SHIFT_MASK);
2200 if((button_pressed == 3)
2201 || (d->tree_flag && button_pressed == 1 && shift_pressed)
2202 || (event->type == GDK_2BUTTON_PRESS && event->button == 1)
2203 || (button_pressed == 1))
2204 {
2205 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
2206 GtkTreePath *path = NULL;
2207 // Get tree path for row that was clicked
2208 if(gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(view), (gint)event->x, (gint)event->y, &path, NULL, NULL, NULL))
2209 {
2210 if(button_pressed == 1)
2211 {
2212 GtkTreeModel *model = gtk_tree_view_get_model(d->dictionary_view);
2213 GtkTreeIter iter;
2214 gtk_tree_model_get_iter(model, &iter, path);
2215 char *tagname;
2216 gtk_tree_model_get(model, &iter,
2217 DT_LIB_TAGGING_COL_PATH, &tagname, -1);
2218 if(d->drag.tagname) g_free(d->drag.tagname);
2219 d->drag.tagname = tagname;
2220 if(d->drag.path) gtk_tree_path_free(d->drag.path);
2221 d->drag.path = path;
2222 if(d->drag.lastpath) gtk_tree_path_free(d->drag.lastpath);
2223 d->drag.lastpath = NULL;
2224 return FALSE;
2225 }
2226 else
2227 {
2228 gtk_tree_selection_select_path(selection, path);
2229 _update_atdetach_buttons(self);
2230 if(button_pressed == 3)
2231 {
2232 _pop_menu_dictionary(view, event, self);
2233 gtk_tree_path_free(path);
2234 return TRUE;
2235 }
2236 else if(d->tree_flag && button_pressed == 1 && shift_pressed)
2237 {
2238 gtk_tree_view_expand_row(GTK_TREE_VIEW(view), path, TRUE);
2239 gtk_tree_path_free(path);
2240 return TRUE;
2241 }
2242 else if(event->type == GDK_2BUTTON_PRESS && event->button == 1)
2243 {
2244 _attach_selected_tag(self, d);
2245 gtk_tree_path_free(path);
2246 return TRUE;
2247 }
2248 }
2249 }
2250 gtk_tree_path_free(path);
2251 }
2252 return FALSE;
2253 }
2254
_row_tooltip_setup(GtkWidget * treeview,gint x,gint y,gboolean kb_mode,GtkTooltip * tooltip,dt_lib_module_t * self)2255 static gboolean _row_tooltip_setup(GtkWidget *treeview, gint x, gint y, gboolean kb_mode,
2256 GtkTooltip* tooltip, dt_lib_module_t *self)
2257 {
2258 gboolean res = FALSE;
2259 GtkTreePath *path = NULL;
2260 // Get tree path mouse position
2261 if(gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(treeview), x, y, &path, NULL, NULL, NULL))
2262 {
2263 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(treeview));
2264 GtkTreeIter iter;
2265 if (gtk_tree_model_get_iter(model, &iter, path))
2266 {
2267 char *tagname;
2268 guint tagid;
2269 guint flags;
2270 char *synonyms = NULL;
2271 gtk_tree_model_get(model, &iter, DT_LIB_TAGGING_COL_ID, &tagid, DT_LIB_TAGGING_COL_TAG, &tagname,
2272 DT_LIB_TAGGING_COL_FLAGS, &flags, DT_LIB_TAGGING_COL_SYNONYM, &synonyms, -1);
2273 if (tagid)
2274 {
2275 if ((flags & DT_TF_PRIVATE) || (synonyms && synonyms[0]))
2276 {
2277 char *text = dt_util_dstrcat(NULL, _("%s"), tagname);
2278 text = dt_util_dstrcat(text, " %s\n", (flags & DT_TF_PRIVATE) ? _("(private)") : "");
2279 text = dt_util_dstrcat(text, "synonyms: %s", (synonyms && synonyms[0]) ? synonyms : " - ");
2280 gtk_tooltip_set_text(tooltip, text);
2281 g_free(text);
2282 res = TRUE;
2283 }
2284 }
2285 g_free(synonyms);
2286 g_free(tagname);
2287 }
2288 }
2289 gtk_tree_path_free(path);
2290
2291 return res;
2292 }
2293
_import_button_clicked(GtkButton * button,dt_lib_module_t * self)2294 static void _import_button_clicked(GtkButton *button, dt_lib_module_t *self)
2295 {
2296 char *last_dirname = dt_conf_get_string("plugins/lighttable/tagging/last_import_export_location");
2297 if(!last_dirname || !*last_dirname)
2298 {
2299 g_free(last_dirname);
2300 last_dirname = g_strdup(g_get_home_dir());
2301 }
2302
2303 GtkWidget *win = dt_ui_main_window(darktable.gui->ui);
2304 GtkWidget *filechooser = gtk_file_chooser_dialog_new(_("Select a keyword file"), GTK_WINDOW(win),
2305 GTK_FILE_CHOOSER_ACTION_OPEN,
2306 _("_cancel"), GTK_RESPONSE_CANCEL,
2307 _("_import"), GTK_RESPONSE_ACCEPT, (char *)NULL);
2308 #ifdef GDK_WINDOWING_QUARTZ
2309 dt_osx_disallow_fullscreen(filechooser);
2310 #endif
2311 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(filechooser), last_dirname);
2312 gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(filechooser), FALSE);
2313
2314 if(gtk_dialog_run(GTK_DIALOG(filechooser)) == GTK_RESPONSE_ACCEPT)
2315 {
2316 char *filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(filechooser));
2317 char *dirname = g_path_get_dirname(filename);
2318 dt_conf_set_string("plugins/lighttable/tagging/last_import_export_location", dirname);
2319 ssize_t count = dt_tag_import(filename);
2320 if(count < 0)
2321 dt_control_log(_("error importing tags"));
2322 else
2323 dt_control_log(_("%zd tags imported"), count);
2324 g_free(filename);
2325 g_free(dirname);
2326 }
2327
2328 g_free(last_dirname);
2329 gtk_widget_destroy(filechooser);
2330 _init_treeview(self, 1);
2331 }
2332
_export_button_clicked(GtkButton * button,dt_lib_module_t * self)2333 static void _export_button_clicked(GtkButton *button, dt_lib_module_t *self)
2334 {
2335 GDateTime *now = g_date_time_new_now_local();
2336 char *export_filename = g_date_time_format(now, "darktable_tags_%F_%R.txt");
2337 char *last_dirname = dt_conf_get_string("plugins/lighttable/tagging/last_import_export_location");
2338 if(!last_dirname || !*last_dirname)
2339 {
2340 g_free(last_dirname);
2341 last_dirname = g_strdup(g_get_home_dir());
2342 }
2343
2344 GtkWidget *win = dt_ui_main_window(darktable.gui->ui);
2345 GtkWidget *filechooser = gtk_file_chooser_dialog_new(_("Select file to export to"), GTK_WINDOW(win),
2346 GTK_FILE_CHOOSER_ACTION_SAVE,
2347 _("_cancel"), GTK_RESPONSE_CANCEL,
2348 _("_export"), GTK_RESPONSE_ACCEPT, (char *)NULL);
2349 #ifdef GDK_WINDOWING_QUARTZ
2350 dt_osx_disallow_fullscreen(filechooser);
2351 #endif
2352 gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(filechooser), TRUE);
2353 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(filechooser), last_dirname);
2354 gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(filechooser), export_filename);
2355
2356 if(gtk_dialog_run(GTK_DIALOG(filechooser)) == GTK_RESPONSE_ACCEPT)
2357 {
2358 char *filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(filechooser));
2359 char *dirname = g_path_get_dirname(filename);
2360 dt_conf_set_string("plugins/lighttable/tagging/last_import_export_location", dirname);
2361 const ssize_t count = dt_tag_export(filename);
2362 if(count < 0)
2363 dt_control_log(_("error exporting tags"));
2364 else
2365 dt_control_log(_("%zd tags exported"), count);
2366 g_free(filename);
2367 g_free(dirname);
2368 }
2369
2370 g_date_time_unref(now);
2371 g_free(last_dirname);
2372 g_free(export_filename);
2373 gtk_widget_destroy(filechooser);
2374 }
2375
_update_layout(dt_lib_module_t * self)2376 static void _update_layout(dt_lib_module_t *self)
2377 {
2378 dt_lib_tagging_t *d = (dt_lib_tagging_t *)self->data;
2379 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(d->dictionary_view));
2380
2381 const gboolean active_s = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(d->toggle_suggestion_button));
2382 d->suggestion_flag = (dt_conf_key_exists("plugins/lighttable/tagging/nosuggestion")
2383 && !dt_conf_get_bool("plugins/lighttable/tagging/nosuggestion"));
2384 if (active_s != d->suggestion_flag)
2385 {
2386 g_signal_handler_block (d->toggle_suggestion_button, d->suggestion_button_handler);
2387 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->toggle_suggestion_button), d->suggestion_flag);
2388 g_signal_handler_unblock (d->toggle_suggestion_button, d->suggestion_button_handler);
2389 }
2390
2391 const gboolean active_t = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(d->toggle_tree_button));
2392 d->tree_flag = dt_conf_get_bool("plugins/lighttable/tagging/treeview");
2393 if (active_t != d->tree_flag)
2394 {
2395 g_signal_handler_block (d->toggle_tree_button, d->tree_button_handler);
2396 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->toggle_tree_button), d->tree_flag);
2397 g_signal_handler_unblock (d->toggle_tree_button, d->tree_button_handler);
2398 }
2399
2400 if (d->tree_flag)
2401 {
2402 if (model == GTK_TREE_MODEL(d->dictionary_listfilter))
2403 {
2404 g_object_ref(model);
2405 gtk_tree_view_set_model(GTK_TREE_VIEW(d->dictionary_view), NULL);
2406 GtkTreeModel *store = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
2407 gtk_list_store_clear(GTK_LIST_STORE(store));
2408 gtk_tree_view_set_model(GTK_TREE_VIEW(d->dictionary_view), GTK_TREE_MODEL(d->dictionary_treefilter));
2409 g_object_unref(d->dictionary_treefilter);
2410 if (d->completion) gtk_entry_set_completion(d->entry, NULL);
2411 }
2412 gtk_widget_set_sensitive(GTK_WIDGET(d->toggle_suggestion_button), FALSE);
2413 }
2414 else
2415 {
2416 if (model == GTK_TREE_MODEL(d->dictionary_treefilter))
2417 {
2418 g_object_ref(model);
2419 gtk_tree_view_set_model(GTK_TREE_VIEW(d->dictionary_view), NULL);
2420 GtkTreeModel *store = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
2421 gtk_tree_store_clear(GTK_TREE_STORE(store));
2422 gtk_tree_view_set_model(GTK_TREE_VIEW(d->dictionary_view), GTK_TREE_MODEL(d->dictionary_listfilter));
2423 g_object_unref(d->dictionary_listfilter);
2424 if (d->completion) gtk_entry_set_completion(d->entry, d->completion);
2425 }
2426 gtk_widget_set_sensitive(GTK_WIDGET(d->toggle_suggestion_button), TRUE);
2427 }
2428
2429 // drag & drop
2430 if(d->tree_flag)
2431 gtk_drag_source_set(GTK_WIDGET(d->dictionary_view), GDK_BUTTON1_MASK,
2432 target_list_tags, n_targets_tags, GDK_ACTION_MOVE);
2433 else
2434 gtk_drag_source_unset(GTK_WIDGET(d->dictionary_view));
2435
2436 const gboolean active_c = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(d->toggle_sort_button));
2437 d->sort_count_flag = dt_conf_get_bool("plugins/lighttable/tagging/listsortedbycount");
2438 if (active_c != d->sort_count_flag)
2439 {
2440 g_signal_handler_block (d->toggle_sort_button, d->sort_button_handler);
2441 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->toggle_sort_button), d->sort_count_flag);
2442 g_signal_handler_unblock (d->toggle_sort_button, d->sort_button_handler);
2443 }
2444
2445 const gboolean active_h = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(d->toggle_hide_button));
2446 d->hide_path_flag = dt_conf_get_bool("plugins/lighttable/tagging/hidehierarchy");
2447 if (active_h != d->hide_path_flag)
2448 {
2449 g_signal_handler_block (d->toggle_hide_button, d->hide_button_handler);
2450 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->toggle_hide_button), d->hide_path_flag);
2451 g_signal_handler_unblock (d->toggle_hide_button, d->hide_button_handler);
2452 }
2453
2454 const gboolean active_d = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(d->toggle_dttags_button));
2455 d->dttags_flag = dt_conf_get_bool("plugins/lighttable/tagging/dttags");
2456 if (active_d != d->dttags_flag)
2457 {
2458 g_signal_handler_block (d->toggle_dttags_button, d->dttags_button_handler);
2459 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->toggle_dttags_button), d->dttags_flag);
2460 g_signal_handler_unblock (d->toggle_dttags_button, d->dttags_button_handler);
2461 }
2462 }
2463
_toggle_suggestion_button_callback(GtkToggleButton * source,dt_lib_module_t * self)2464 static void _toggle_suggestion_button_callback(GtkToggleButton *source, dt_lib_module_t *self)
2465 {
2466 const gboolean new_state = !dt_conf_get_bool("plugins/lighttable/tagging/nosuggestion");
2467 dt_conf_set_bool("plugins/lighttable/tagging/nosuggestion", new_state);
2468 _update_layout(self);
2469 _init_treeview(self, 1);
2470 }
2471
_toggle_tree_button_callback(GtkToggleButton * source,dt_lib_module_t * self)2472 static void _toggle_tree_button_callback(GtkToggleButton *source, dt_lib_module_t *self)
2473 {
2474 const gboolean new_state = !dt_conf_get_bool("plugins/lighttable/tagging/treeview");
2475 dt_conf_set_bool("plugins/lighttable/tagging/treeview", new_state);
2476 _update_layout(self);
2477 _init_treeview(self, 1);
2478 }
2479
_sort_tree_count_func(GtkTreeModel * model,GtkTreeIter * a,GtkTreeIter * b,dt_lib_module_t * self)2480 static gint _sort_tree_count_func(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, dt_lib_module_t *self)
2481 {
2482 guint count_a = 0;
2483 guint count_b = 0;
2484 gtk_tree_model_get(model, a, DT_LIB_TAGGING_COL_COUNT, &count_a, -1);
2485 gtk_tree_model_get(model, b, DT_LIB_TAGGING_COL_COUNT, &count_b, -1);
2486 return (count_b - count_a);
2487 }
2488
_sort_tree_tag_func(GtkTreeModel * model,GtkTreeIter * a,GtkTreeIter * b,dt_lib_module_t * self)2489 static gint _sort_tree_tag_func(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, dt_lib_module_t *self)
2490 {
2491 char *tag_a = NULL;
2492 char *tag_b = NULL;
2493 gtk_tree_model_get(model, a, DT_LIB_TAGGING_COL_TAG, &tag_a, -1);
2494 gtk_tree_model_get(model, b, DT_LIB_TAGGING_COL_TAG, &tag_b, -1);
2495 if(tag_a == NULL) tag_a = g_strdup("");
2496 if(tag_b == NULL) tag_b = g_strdup("");
2497 const gboolean sort = g_strcmp0(tag_a, tag_b);
2498 g_free(tag_a);
2499 g_free(tag_b);
2500 return sort;
2501 }
2502
_sort_tree_path_func(GtkTreeModel * model,GtkTreeIter * a,GtkTreeIter * b,dt_lib_module_t * self)2503 static gint _sort_tree_path_func(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, dt_lib_module_t *self)
2504 {
2505 char *tag_a = NULL;
2506 char *tag_b = NULL;
2507 gtk_tree_model_get(model, a, DT_LIB_TAGGING_COL_PATH, &tag_a, -1);
2508 gtk_tree_model_get(model, b, DT_LIB_TAGGING_COL_PATH, &tag_b, -1);
2509 if(tag_a)
2510 {
2511 for(char *letter = tag_a; *letter; letter++)
2512 if(*letter == '|') *letter = '\1';
2513 }
2514 else
2515 tag_a = g_strdup("");
2516
2517 if(tag_b)
2518 {
2519 for(char *letter = tag_b; *letter; letter++)
2520 if(*letter == '|') *letter = '\1';
2521 }
2522 else
2523 tag_b = g_strdup("");
2524
2525 const gboolean sort = g_strcmp0(tag_a, tag_b);
2526 g_free(tag_a);
2527 g_free(tag_b);
2528 return sort;
2529 }
2530
_toggle_sort_button_callback(GtkToggleButton * source,dt_lib_module_t * self)2531 static void _toggle_sort_button_callback(GtkToggleButton *source, dt_lib_module_t *self)
2532 {
2533 const gboolean new_state = !dt_conf_get_bool("plugins/lighttable/tagging/listsortedbycount");
2534 dt_conf_set_bool("plugins/lighttable/tagging/listsortedbycount", new_state);
2535 _update_layout(self);
2536 _sort_attached_list(self, FALSE);
2537 _sort_dictionary_list(self, FALSE);
2538 }
2539
_toggle_hide_button_callback(GtkToggleButton * source,dt_lib_module_t * self)2540 static void _toggle_hide_button_callback(GtkToggleButton *source, dt_lib_module_t *self)
2541 {
2542 const gboolean new_state = !dt_conf_get_bool("plugins/lighttable/tagging/hidehierarchy");
2543 dt_conf_set_bool("plugins/lighttable/tagging/hidehierarchy", new_state);
2544 _update_layout(self);
2545 _sort_attached_list(self, TRUE);
2546 _sort_dictionary_list(self, TRUE);
2547 }
2548
_toggle_dttags_button_callback(GtkToggleButton * source,dt_lib_module_t * self)2549 static void _toggle_dttags_button_callback(GtkToggleButton *source, dt_lib_module_t *self)
2550 {
2551 const gboolean new_state = !dt_conf_get_bool("plugins/lighttable/tagging/dttags");
2552 dt_conf_set_bool("plugins/lighttable/tagging/dttags", new_state);
2553 dt_lib_tagging_t *d = (dt_lib_tagging_t *)self->data;
2554 d->dttags_flag = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(d->toggle_dttags_button));
2555 _init_treeview(self, 0);
2556 }
2557
gui_reset(dt_lib_module_t * self)2558 void gui_reset(dt_lib_module_t *self)
2559 {
2560 dt_lib_tagging_t *d = (dt_lib_tagging_t *)self->data;
2561 // clear entry box and query
2562 gtk_entry_set_text(d->entry, "");
2563 _set_keyword(self);
2564 _init_treeview(self, 1);
2565 _update_atdetach_buttons(self);
2566 }
2567
position()2568 int position()
2569 {
2570 return 500;
2571 }
2572
_match_selected_func(GtkEntryCompletion * completion,GtkTreeModel * model,GtkTreeIter * iter,gpointer user_data)2573 static gboolean _match_selected_func(GtkEntryCompletion *completion, GtkTreeModel *model, GtkTreeIter *iter, gpointer user_data)
2574 {
2575 const int column = gtk_entry_completion_get_text_column(completion);
2576 char *tag = NULL;
2577
2578 if(gtk_tree_model_get_column_type(model, column) != G_TYPE_STRING) return TRUE;
2579
2580 GtkEditable *e = (GtkEditable *)gtk_entry_completion_get_entry(completion);
2581 if(!GTK_IS_EDITABLE(e))
2582 {
2583 return FALSE;
2584 }
2585
2586 gtk_tree_model_get(model, iter, column, &tag, -1);
2587
2588 gint cut_off, cur_pos = gtk_editable_get_position(e);
2589
2590 gchar *currentText = gtk_editable_get_chars(e, 0, -1);
2591 const gchar *lastTag = g_strrstr(currentText, ",");
2592 if(lastTag == NULL)
2593 {
2594 cut_off = 0;
2595 }
2596 else
2597 {
2598 cut_off = (int)(g_utf8_strlen(currentText, -1) - g_utf8_strlen(lastTag, -1))+1;
2599 }
2600 free(currentText);
2601
2602 gtk_editable_delete_text(e, cut_off, cur_pos);
2603 cur_pos = cut_off;
2604 gtk_editable_insert_text(e, tag, -1, &cur_pos);
2605 gtk_editable_set_position(e, cur_pos);
2606 g_free(tag); // release result of gtk_tree_model_get
2607 return TRUE;
2608 }
2609
_completion_match_func(GtkEntryCompletion * completion,const gchar * key,GtkTreeIter * iter,gpointer user_data)2610 static gboolean _completion_match_func(GtkEntryCompletion *completion, const gchar *key, GtkTreeIter *iter,
2611 gpointer user_data)
2612 {
2613 gboolean res = FALSE;
2614
2615 GtkEditable *e = (GtkEditable *)gtk_entry_completion_get_entry(completion);
2616
2617 if(!GTK_IS_EDITABLE(e))
2618 {
2619 return FALSE;
2620 }
2621
2622 const gint cur_pos = gtk_editable_get_position(e);
2623 const gboolean onLastTag = (g_strstr_len(&key[cur_pos], -1, ",") == NULL);
2624 if(!onLastTag)
2625 {
2626 return FALSE;
2627 }
2628
2629 GtkTreeModel *model = gtk_entry_completion_get_model(completion);
2630 const int column = gtk_entry_completion_get_text_column(completion);
2631
2632 if(gtk_tree_model_get_column_type(model, column) != G_TYPE_STRING)
2633 {
2634 return FALSE;
2635 }
2636
2637 const gchar *lastTag = g_strrstr(key, ",");
2638 if(lastTag != NULL)
2639 {
2640 lastTag++;
2641 }
2642 else
2643 {
2644 lastTag = key;
2645 }
2646 if(lastTag[0] == '\0' || key[0] == '\0')
2647 {
2648 return FALSE;
2649 }
2650
2651 char *tag = NULL;
2652 gtk_tree_model_get(model, iter, column, &tag, -1);
2653
2654 if(tag)
2655 {
2656 char *normalized = g_utf8_normalize(tag, -1, G_NORMALIZE_ALL);
2657 if(normalized)
2658 {
2659 char *casefold = g_utf8_casefold(normalized, -1);
2660 if(casefold)
2661 {
2662 res = g_strstr_len(casefold, -1, lastTag) != NULL;
2663 }
2664 g_free(casefold);
2665 }
2666 g_free(normalized);
2667 g_free(tag);
2668 }
2669
2670 return res;
2671 }
2672
_tree_selection_changed(GtkTreeSelection * treeselection,gpointer data)2673 static void _tree_selection_changed(GtkTreeSelection *treeselection, gpointer data)
2674 {
2675 _update_atdetach_buttons((dt_lib_module_t *)data);
2676 }
2677
_dnd_clear_root(dt_lib_module_t * self)2678 static void _dnd_clear_root(dt_lib_module_t *self)
2679 {
2680 dt_lib_tagging_t *d = (dt_lib_tagging_t *)self->data;
2681 if(!d->drag.root) return;
2682 GtkTreeModel *model = GTK_TREE_MODEL(d->dictionary_treestore);
2683 GtkTreeIter iter;
2684 gtk_tree_model_get_iter_first(model, &iter);
2685 char *name;
2686 gtk_tree_model_get(model, &iter, DT_LIB_TAGGING_COL_PATH, &name, -1);
2687 if(name && name[0] == '\0')
2688 gtk_tree_store_remove(d->dictionary_treestore, &iter);
2689 g_free(name);
2690 d->drag.root = FALSE;
2691 }
2692
_event_dnd_begin(GtkWidget * widget,GdkDragContext * context,dt_lib_module_t * self)2693 static void _event_dnd_begin(GtkWidget *widget, GdkDragContext *context, dt_lib_module_t *self)
2694 {
2695 dt_lib_tagging_t *d = (dt_lib_tagging_t *)self->data;
2696 GtkTreeView *tree = GTK_TREE_VIEW(widget);
2697
2698 if(d->drag.path)
2699 {
2700 cairo_surface_t *row_pix = gtk_tree_view_create_row_drag_icon(tree, d->drag.path);
2701 // FIXME: try to put the anchor point on left bottom corner as for images
2702 // cairo_surface_set_device_offset(row_pix,
2703 // // the + 1 is for the black border in the icon
2704 // - (double)(cairo_image_surface_get_width(row_pix) + 1),
2705 // - (double)(cairo_image_surface_get_height(row_pix)+ 1));
2706 gtk_drag_set_icon_surface(context, row_pix);
2707 cairo_surface_destroy(row_pix);
2708 gtk_tree_path_free(d->drag.path);
2709 d->drag.path = NULL;
2710 d->drag.tag_source = TRUE;
2711 }
2712 }
2713
_event_dnd_get(GtkWidget * widget,GdkDragContext * context,GtkSelectionData * selection_data,const guint target_type,const guint time,dt_lib_module_t * self)2714 static void _event_dnd_get(GtkWidget *widget, GdkDragContext *context, GtkSelectionData *selection_data,
2715 const guint target_type, const guint time, dt_lib_module_t *self)
2716 {
2717 if(target_type == DND_TARGET_TAG)
2718 gtk_selection_data_set(selection_data, gtk_selection_data_get_target(selection_data),
2719 _DWORD, NULL, 0);
2720 }
2721
_event_dnd_received(GtkWidget * widget,GdkDragContext * context,gint x,gint y,GtkSelectionData * selection_data,guint target_type,guint time,dt_lib_module_t * self)2722 static void _event_dnd_received(GtkWidget *widget, GdkDragContext *context, gint x, gint y,
2723 GtkSelectionData *selection_data, guint target_type, guint time,
2724 dt_lib_module_t *self)
2725 {
2726 dt_lib_tagging_t *d = (dt_lib_tagging_t *)self->data;
2727 GtkTreeView *tree = GTK_TREE_VIEW(widget);
2728 // disable the deault handler
2729 g_signal_stop_emission_by_name(tree, "drag-data-received");
2730 gboolean success = FALSE;
2731
2732 if(target_type == DND_TARGET_TAG)
2733 {
2734 GtkTreePath *path = NULL;
2735 if (gtk_tree_view_get_path_at_pos(tree, x, y, &path, NULL, NULL, NULL))
2736 {
2737 char *name;
2738 GtkTreeModel *model = gtk_tree_view_get_model(tree);
2739 GtkTreeIter iter;
2740
2741 gtk_tree_model_get_iter(model, &iter, path);
2742 gtk_tree_model_get(model, &iter, DT_LIB_TAGGING_COL_PATH, &name, -1);
2743 _dnd_clear_root(self);
2744 const gboolean root = (name && name[0] == '\0');
2745 char *leave = g_strrstr(d->drag.tagname, "|");
2746 if(leave) leave++;
2747 name = dt_util_dstrcat(name, "%s%s", root ? "" : "|", leave ? leave : d->drag.tagname);
2748 _apply_rename_path(NULL, d->drag.tagname, name, self);
2749
2750 g_free(name);
2751 g_free(d->drag.tagname);
2752 d->drag.tagname = NULL;
2753 success = TRUE;
2754 }
2755 }
2756 else if((target_type == DND_TARGET_IMGID) && (selection_data != NULL))
2757 {
2758 GtkTreePath *path = NULL;
2759 const int imgs_nb = gtk_selection_data_get_length(selection_data) / sizeof(uint32_t);
2760 if(imgs_nb && gtk_tree_view_get_path_at_pos(tree, x, y, &path, NULL, NULL, NULL))
2761 {
2762 const uint32_t *imgt = (uint32_t *)gtk_selection_data_get_data(selection_data);
2763 GList *imgs = NULL;
2764 for(int i = 0; i < imgs_nb; i++)
2765 {
2766 imgs = g_list_prepend(imgs, GINT_TO_POINTER(imgt[i]));
2767 }
2768 GtkTreeModel *model = gtk_tree_view_get_model(tree);
2769 GtkTreeIter iter;
2770 gtk_tree_model_get_iter(model, &iter, path);
2771 gint tagid;
2772 gtk_tree_model_get(model, &iter, DT_LIB_TAGGING_COL_ID, &tagid, -1);
2773 if(tagid)
2774 dt_tag_attach_images(tagid, imgs, TRUE);
2775 g_list_free(imgs);
2776 _update_attached_count(tagid, d->dictionary_view, d->tree_flag);
2777 _init_treeview(self, 0);
2778 _raise_signal_tag_changed(self);
2779 dt_image_synch_xmp(-1);
2780 gtk_tree_path_free(path); // release result of gtk_tree_view_get_path_at_pos above
2781 success = TRUE;
2782 }
2783 }
2784 gtk_drag_finish(context, success, FALSE, time);
2785 }
2786
_dnd_expand_timeout(dt_lib_module_t * self)2787 static gboolean _dnd_expand_timeout(dt_lib_module_t *self)
2788 {
2789 dt_lib_tagging_t *d = (dt_lib_tagging_t *)self->data;
2790 if(d->drag.lastpath)
2791 {
2792 gtk_tree_view_expand_row(d->dictionary_view, d->drag.lastpath, FALSE);
2793 }
2794 return FALSE;
2795 }
2796
_dnd_scroll_timeout(dt_lib_module_t * self)2797 static gboolean _dnd_scroll_timeout(dt_lib_module_t *self)
2798 {
2799 dt_lib_tagging_t *d = (dt_lib_tagging_t *)self->data;
2800 if(d->drag.scroll_timeout)
2801 {
2802 GdkRectangle visible;
2803 gtk_tree_view_get_visible_rect(d->dictionary_view, &visible);
2804 gint top_ty;
2805 gtk_tree_view_convert_bin_window_to_tree_coords(d->dictionary_view,
2806 0, 0, NULL, &top_ty);
2807
2808 // temporary drop root
2809 if(d->drag.tag_source && !d->drag.root && d->drag.last_y < 5 && top_ty < 1)
2810 {
2811 GtkTreeIter iter;
2812 gtk_tree_store_prepend(d->dictionary_treestore, &iter, NULL);
2813 gtk_tree_store_set(d->dictionary_treestore, &iter,
2814 DT_LIB_TAGGING_COL_TAG, _("drop to root"),
2815 DT_LIB_TAGGING_COL_ID, 0,
2816 DT_LIB_TAGGING_COL_PATH, "",
2817 DT_LIB_TAGGING_COL_COUNT, 0,
2818 DT_LIB_TAGGING_COL_SEL, 0,
2819 DT_LIB_TAGGING_COL_FLAGS, 0,
2820 DT_LIB_TAGGING_COL_VISIBLE, TRUE,
2821 -1);
2822 d->drag.root = TRUE;
2823 }
2824 else if(d->drag.root && d->drag.last_y >= 20)
2825 _dnd_clear_root(self);
2826
2827 if(d->drag.last_y < 5)
2828 {
2829 // scroll up
2830 gtk_tree_view_scroll_to_point(d->dictionary_view, 0, top_ty - 25 < 0 ? 0 : top_ty - 25);
2831 }
2832 else if (d->drag.last_y > visible.height - 5)
2833 // scroll down
2834 gtk_tree_view_scroll_to_point(d->dictionary_view, 0, top_ty + 25);
2835 return TRUE;
2836 }
2837 return FALSE;
2838 }
2839
_event_dnd_motion(GtkWidget * widget,GdkDragContext * context,gint x,gint y,guint time,dt_lib_module_t * self)2840 static gboolean _event_dnd_motion(GtkWidget *widget, GdkDragContext *context,
2841 gint x, gint y, guint time, dt_lib_module_t *self)
2842 {
2843 dt_lib_tagging_t *d = (dt_lib_tagging_t *)self->data;
2844 GtkTreeView *tree = GTK_TREE_VIEW(widget);
2845 GtkTreePath *path = NULL;
2846
2847 if(gtk_tree_view_get_path_at_pos(tree, x, y, &path, NULL, NULL, NULL))
2848 {
2849 if(!d->drag.lastpath || ((d->drag.lastpath) && gtk_tree_path_compare(d->drag.lastpath, path) != 0))
2850 {
2851 if (!gtk_tree_view_row_expanded(tree, path))
2852 d->drag.expand_timeout = g_timeout_add(500, (GSourceFunc)_dnd_expand_timeout, self);
2853 }
2854
2855 GtkTreeSelection *selection = gtk_tree_view_get_selection(d->dictionary_view);
2856 gtk_tree_selection_select_path(selection, path);
2857 d->drag.last_y = y;
2858 if(d->drag.scroll_timeout == 0)
2859 {
2860 d->drag.scroll_timeout = g_timeout_add(100, (GSourceFunc)_dnd_scroll_timeout, self);
2861 }
2862 }
2863
2864 // FIXME - no row highlihted... workartound with selection above
2865 // gtk_tree_view_set_drag_dest_row(tree, path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
2866
2867 if (d->drag.lastpath)
2868 gtk_tree_path_free(d->drag.lastpath);
2869 d->drag.lastpath = path;
2870
2871 return TRUE;
2872 }
2873
_event_dnd_end(GtkWidget * widget,GdkDragContext * context,dt_lib_module_t * self)2874 static void _event_dnd_end(GtkWidget *widget, GdkDragContext *context, dt_lib_module_t *self)
2875 {
2876 dt_lib_tagging_t *d = (dt_lib_tagging_t *)self->data;
2877 // FIXME (see gtk_tree_view_set_drag_dest_row above)
2878 // gtk_tree_view_set_drag_dest_row(d->dictionary_view, NULL, GTK_TREE_VIEW_DROP_BEFORE);
2879 GtkTreeSelection *selection = gtk_tree_view_get_selection(d->dictionary_view);
2880 gtk_tree_selection_unselect_all(selection);
2881 if(d->drag.scroll_timeout)
2882 g_source_remove(d->drag.scroll_timeout);
2883 d->drag.scroll_timeout = 0;
2884 d->drag.tag_source = FALSE;
2885 _dnd_clear_root(self);
2886 }
2887
gui_init(dt_lib_module_t * self)2888 void gui_init(dt_lib_module_t *self)
2889 {
2890 dt_lib_tagging_t *d = (dt_lib_tagging_t *)calloc(sizeof(dt_lib_tagging_t),1);
2891 self->data = (void *)d;
2892 d->last_tag = NULL;
2893 self->timeout_handle = 0;
2894
2895 self->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
2896 dt_gui_add_help_link(self->widget, dt_get_help_url(self->plugin_name));
2897
2898 GtkBox *box, *hbox;
2899 GtkWidget *button;
2900 GtkWidget *w;
2901 GtkTreeView *view;
2902 GtkTreeModel *model;
2903 GtkListStore *liststore;
2904 GtkTreeStore *treestore;
2905 GtkTreeViewColumn *col;
2906 GtkCellRenderer *renderer;
2907
2908 // attached_view
2909 box = GTK_BOX(gtk_box_new(GTK_ORIENTATION_VERTICAL, 0));
2910
2911 gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(box), TRUE, TRUE, 0);
2912 view = GTK_TREE_VIEW(gtk_tree_view_new());
2913 w = dt_ui_scroll_wrap(GTK_WIDGET(view), 200, "plugins/lighttable/tagging/heightattachedwindow");
2914 gtk_box_pack_start(box, w, TRUE, TRUE, 0);
2915 d->attached_view = view;
2916 gtk_tree_view_set_headers_visible(view, FALSE);
2917 liststore = gtk_list_store_new(DT_LIB_TAGGING_NUM_COLS, G_TYPE_STRING, G_TYPE_UINT, G_TYPE_STRING,
2918 G_TYPE_STRING, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_BOOLEAN);
2919 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(liststore), DT_TAG_SORT_PATH_ID,
2920 (GtkTreeIterCompareFunc)_sort_tree_path_func, self, NULL);
2921 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(liststore), DT_TAG_SORT_NAME_ID,
2922 (GtkTreeIterCompareFunc)_sort_tree_tag_func, self, NULL);
2923 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(liststore), DT_TAG_SORT_COUNT_ID,
2924 (GtkTreeIterCompareFunc)_sort_tree_count_func, self, NULL);
2925 d->attached_liststore = liststore;
2926 g_object_set(G_OBJECT(view), "has-tooltip", TRUE, NULL);
2927 g_signal_connect(G_OBJECT(view), "query-tooltip", G_CALLBACK(_row_tooltip_setup), (gpointer)self);
2928
2929 col = gtk_tree_view_column_new();
2930 gtk_tree_view_append_column(view, col);
2931 renderer = gtk_cell_renderer_toggle_new();
2932 gtk_tree_view_column_pack_start(col, renderer, TRUE);
2933 gtk_tree_view_column_set_cell_data_func(col, renderer, _tree_select_show, NULL, NULL);
2934 g_object_set(renderer, "indicator-size", 8, NULL); // too big by default
2935
2936 col = gtk_tree_view_column_new();
2937 gtk_tree_view_append_column(view, col);
2938 renderer = gtk_cell_renderer_text_new();
2939 g_object_set(renderer, "ellipsize", PANGO_ELLIPSIZE_MIDDLE, (gchar *)0);
2940 gtk_tree_view_column_pack_start(col, renderer, TRUE);
2941 gtk_tree_view_column_set_cell_data_func(col, renderer, _tree_tagname_show_attached, (gpointer)self, NULL);
2942
2943 gtk_tree_selection_set_mode(gtk_tree_view_get_selection(view), GTK_SELECTION_SINGLE);
2944 gtk_tree_view_set_model(view, GTK_TREE_MODEL(liststore));
2945 g_object_unref(liststore);
2946 gtk_widget_set_tooltip_text(GTK_WIDGET(view), _("attached tags,\ndouble-click to detach"
2947 "\nright-click for other actions on attached tag,"
2948 "\nctrl-wheel scroll to resize the window"));
2949 dt_gui_add_help_link(GTK_WIDGET(view), dt_get_help_url("tagging"));
2950 g_signal_connect(G_OBJECT(view), "button-press-event", G_CALLBACK(_click_on_view_attached), (gpointer)self);
2951 g_signal_connect(gtk_tree_view_get_selection(view), "changed", G_CALLBACK(_tree_selection_changed), self);
2952
2953 // attach/detach buttons
2954 hbox = GTK_BOX(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0));
2955
2956 d->attach_button = dt_ui_button_new(_("attach"), _("attach tag to all selected images"), dt_get_help_url("tagging"));
2957 gtk_box_pack_start(hbox, d->attach_button, TRUE, TRUE, 0);
2958 g_signal_connect(G_OBJECT(d->attach_button), "clicked", G_CALLBACK(_attach_button_clicked), (gpointer)self);
2959
2960 d->detach_button = dt_ui_button_new(_("detach"), _("detach tag from all selected images"), dt_get_help_url("tagging"));
2961 g_signal_connect(G_OBJECT(d->detach_button), "clicked", G_CALLBACK(_detach_button_clicked), (gpointer)self);
2962 gtk_box_pack_start(hbox, d->detach_button, TRUE, TRUE, 0);
2963
2964 button = dtgtk_togglebutton_new(dtgtk_cairo_paint_minus_simple, CPF_STYLE_FLAT, NULL);
2965 d->toggle_hide_button = button;
2966 gtk_widget_set_tooltip_text(button, _("toggle list with / without hierarchy"));
2967 dt_gui_add_help_link(button, dt_get_help_url("tagging"));
2968 gtk_box_pack_end(hbox, button, FALSE, TRUE, 0);
2969 d->hide_button_handler = g_signal_connect(G_OBJECT(button), "clicked",
2970 G_CALLBACK(_toggle_hide_button_callback), (gpointer)self);
2971
2972 button = dtgtk_togglebutton_new(dtgtk_cairo_paint_sorting, CPF_STYLE_FLAT, NULL);
2973 d->toggle_sort_button = button;
2974 gtk_widget_set_tooltip_text(button, _("toggle sort by name or by count"));
2975 dt_gui_add_help_link(button, dt_get_help_url("tagging"));
2976 gtk_box_pack_end(hbox, button, FALSE, TRUE, 0);
2977 d->sort_button_handler = g_signal_connect(G_OBJECT(button), "clicked",
2978 G_CALLBACK(_toggle_sort_button_callback), (gpointer)self);
2979
2980 button = dtgtk_togglebutton_new(dtgtk_cairo_paint_check_mark, CPF_STYLE_FLAT, NULL);
2981 d->toggle_dttags_button = button;
2982 d->dttags_flag = FALSE;
2983 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->toggle_dttags_button), FALSE);
2984 gtk_widget_set_tooltip_text(button, _("toggle show or not darktable tags"));
2985 dt_gui_add_help_link(button, dt_get_help_url("tagging"));
2986 gtk_box_pack_end(hbox, button, FALSE, TRUE, 0);
2987 d->dttags_button_handler =
2988 g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(_toggle_dttags_button_callback), (gpointer)self);
2989
2990 gtk_box_pack_start(box, GTK_WIDGET(hbox), FALSE, TRUE, 0);
2991
2992 // dictionary_view
2993 box = GTK_BOX(gtk_box_new(GTK_ORIENTATION_VERTICAL, 0));
2994 gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(box), TRUE, TRUE, 0);
2995
2996 hbox = GTK_BOX(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0));
2997
2998 // text entry
2999 w = gtk_entry_new();
3000 gtk_entry_set_text(GTK_ENTRY(w), "");
3001 gtk_entry_set_width_chars(GTK_ENTRY(w), 0);
3002 gtk_widget_set_tooltip_text(w, _("enter tag name"));
3003 dt_gui_add_help_link(w, dt_get_help_url("tagging"));
3004 gtk_box_pack_start(hbox, w, TRUE, TRUE, 0);
3005 gtk_widget_add_events(GTK_WIDGET(w), GDK_KEY_RELEASE_MASK);
3006 g_signal_connect(G_OBJECT(w), "changed", G_CALLBACK(_tag_name_changed), (gpointer)self);
3007 d->entry = GTK_ENTRY(w);
3008 dt_gui_key_accel_block_on_focus_connect(GTK_WIDGET(d->entry));
3009
3010 button = dtgtk_button_new(dtgtk_cairo_paint_multiply_small, CPF_STYLE_FLAT, NULL);
3011 gtk_widget_set_tooltip_text(button, _("clear entry"));
3012 gtk_box_pack_end(hbox, button, FALSE, TRUE, 0);
3013 g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(_clear_entry_button_callback), (gpointer)self);
3014 gtk_box_pack_start(box, GTK_WIDGET(hbox), FALSE, TRUE, 0);
3015
3016 // dictionary_view tree view
3017 view = GTK_TREE_VIEW(gtk_tree_view_new());
3018 w = dt_ui_scroll_wrap(GTK_WIDGET(view), 200, "plugins/lighttable/tagging/heightdictionarywindow");
3019 gtk_box_pack_start(box, w, TRUE, TRUE, 0);
3020 d->dictionary_view = view;
3021 gtk_tree_view_set_headers_visible(view, FALSE);
3022 liststore = gtk_list_store_new(DT_LIB_TAGGING_NUM_COLS, G_TYPE_STRING, G_TYPE_UINT, G_TYPE_STRING,
3023 G_TYPE_STRING, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_BOOLEAN);
3024 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(liststore), DT_TAG_SORT_PATH_ID,
3025 (GtkTreeIterCompareFunc)_sort_tree_path_func, self, NULL);
3026 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(liststore), DT_TAG_SORT_NAME_ID,
3027 (GtkTreeIterCompareFunc)_sort_tree_tag_func, self, NULL);
3028 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(liststore), DT_TAG_SORT_COUNT_ID,
3029 (GtkTreeIterCompareFunc)_sort_tree_count_func, self, NULL);
3030 d->dictionary_liststore = liststore;
3031 model = gtk_tree_model_filter_new(GTK_TREE_MODEL(liststore), NULL);
3032 gtk_tree_model_filter_set_visible_column(GTK_TREE_MODEL_FILTER(model), DT_LIB_TAGGING_COL_VISIBLE);
3033 d->dictionary_listfilter = GTK_TREE_MODEL_FILTER(model);
3034 treestore = gtk_tree_store_new(DT_LIB_TAGGING_NUM_COLS, G_TYPE_STRING, G_TYPE_UINT, G_TYPE_STRING,
3035 G_TYPE_STRING, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_BOOLEAN);
3036 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(treestore), DT_TAG_SORT_PATH_ID,
3037 (GtkTreeIterCompareFunc)_sort_tree_path_func, self, NULL);
3038 d->dictionary_treestore = treestore;
3039 model = gtk_tree_model_filter_new(GTK_TREE_MODEL(treestore), NULL);
3040 gtk_tree_model_filter_set_visible_column(GTK_TREE_MODEL_FILTER(model), DT_LIB_TAGGING_COL_VISIBLE);
3041 d->dictionary_treefilter = GTK_TREE_MODEL_FILTER(model);
3042
3043 col = gtk_tree_view_column_new();
3044 gtk_tree_view_append_column(view, col);
3045 renderer = gtk_cell_renderer_toggle_new();
3046 gtk_tree_view_column_pack_start(col, renderer, TRUE);
3047 gtk_cell_renderer_toggle_set_activatable(GTK_CELL_RENDERER_TOGGLE(renderer), TRUE);
3048 gtk_tree_view_column_set_cell_data_func(col, renderer, _tree_select_show, NULL, NULL);
3049 g_object_set(renderer, "indicator-size", 8, NULL); // too big by default
3050
3051 col = gtk_tree_view_column_new();
3052 gtk_tree_view_append_column(view, col);
3053 renderer = gtk_cell_renderer_text_new();
3054 g_object_set(renderer, "ellipsize", PANGO_ELLIPSIZE_MIDDLE, (gchar *)0);
3055 gtk_tree_view_column_pack_start(col, renderer, TRUE);
3056 gtk_tree_view_column_set_cell_data_func(col, renderer, _tree_tagname_show_dictionary, (gpointer)self, NULL);
3057 gtk_tree_view_set_expander_column(view, col);
3058
3059 gtk_tree_selection_set_mode(gtk_tree_view_get_selection(view), GTK_SELECTION_SINGLE);
3060 gtk_widget_set_tooltip_text(GTK_WIDGET(view), _("tag dictionary,\ndouble-click to attach,"
3061 "\nright-click for other actions on selected tag,"
3062 "\nctrl-wheel scroll to resize the window"));
3063 dt_gui_add_help_link(GTK_WIDGET(view), dt_get_help_url("tagging"));
3064 g_signal_connect(G_OBJECT(view), "button-press-event", G_CALLBACK(_click_on_view_dictionary), (gpointer)self);
3065 gtk_tree_view_set_model(view, GTK_TREE_MODEL(d->dictionary_listfilter));
3066 g_object_unref(d->dictionary_listfilter);
3067 g_object_set(G_OBJECT(view), "has-tooltip", TRUE, NULL);
3068 g_signal_connect(G_OBJECT(view), "query-tooltip", G_CALLBACK(_row_tooltip_setup), (gpointer)self);
3069 g_signal_connect(gtk_tree_view_get_selection(view), "changed", G_CALLBACK(_tree_selection_changed), self);
3070
3071 // drag & drop
3072 {
3073 d->drag.path = NULL;
3074 d->drag.tagname = NULL;
3075 d->drag.scroll_timeout = 0;
3076 d->drag.expand_timeout = 0;
3077 d->drag.root = FALSE;
3078 d->drag.tag_source = FALSE;
3079 gtk_drag_dest_set(GTK_WIDGET(d->dictionary_view), GTK_DEST_DEFAULT_ALL,
3080 target_list_tags_dest, n_targets_tags_dest, GDK_ACTION_MOVE);
3081 g_signal_connect(d->dictionary_view, "drag-data-get", G_CALLBACK(_event_dnd_get), self);
3082 g_signal_connect(d->dictionary_view, "drag-data-received", G_CALLBACK(_event_dnd_received), self);
3083 g_signal_connect_after(d->dictionary_view, "drag-begin", G_CALLBACK(_event_dnd_begin), self);
3084 g_signal_connect_after(d->dictionary_view, "drag-end", G_CALLBACK(_event_dnd_end), self);
3085 g_signal_connect(d->dictionary_view, "drag-motion", G_CALLBACK(_event_dnd_motion), self);
3086 }
3087
3088 // buttons
3089 hbox = GTK_BOX(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0));
3090
3091 d->new_button = dt_ui_button_new(_("new"), _("create a new tag with the\nname you entered"), dt_get_help_url("tagging"));
3092 gtk_box_pack_start(hbox, d->new_button, TRUE, TRUE, 0);
3093 g_signal_connect(G_OBJECT(d->new_button), "clicked", G_CALLBACK(_new_button_clicked), (gpointer)self);
3094
3095 d->import_button = dt_ui_button_new(C_("verb", "import..."), _("import tags from a Lightroom keyword file"), dt_get_help_url("tagging"));
3096 gtk_box_pack_start(hbox, d->import_button, TRUE, TRUE, 0);
3097 g_signal_connect(G_OBJECT(d->import_button), "clicked", G_CALLBACK(_import_button_clicked), (gpointer)self);
3098
3099 d->export_button = dt_ui_button_new(C_("verb", "export..."), _("export all tags to a Lightroom keyword file"), dt_get_help_url("tagging"));
3100 gtk_box_pack_start(hbox, d->export_button, TRUE, TRUE, 0);
3101 g_signal_connect(G_OBJECT(d->export_button), "clicked", G_CALLBACK(_export_button_clicked), (gpointer)self);
3102
3103 button = dtgtk_togglebutton_new(dtgtk_cairo_paint_treelist, CPF_STYLE_FLAT, NULL);
3104 d->toggle_tree_button = button;
3105 gtk_widget_set_tooltip_text(button, _("toggle list / tree view"));
3106 dt_gui_add_help_link(button, dt_get_help_url("tagging"));
3107 gtk_box_pack_end(hbox, button, FALSE, TRUE, 0);
3108 d->tree_button_handler = g_signal_connect(G_OBJECT(button), "clicked",
3109 G_CALLBACK(_toggle_tree_button_callback), (gpointer)self);
3110
3111 button = dtgtk_togglebutton_new(dtgtk_cairo_paint_plus_simple, CPF_STYLE_FLAT, NULL);
3112 d->toggle_suggestion_button = button;
3113 gtk_widget_set_tooltip_text(button, _("toggle list with / without suggestion"));
3114 dt_gui_add_help_link(button, dt_get_help_url("tagging"));
3115 gtk_box_pack_end(hbox, button, FALSE, TRUE, 0);
3116 d->suggestion_button_handler = g_signal_connect(G_OBJECT(button), "clicked",
3117 G_CALLBACK(_toggle_suggestion_button_callback), (gpointer)self);
3118
3119 gtk_box_pack_start(box, GTK_WIDGET(hbox), FALSE, TRUE, 0);
3120
3121 if (!dt_conf_get_bool("plugins/lighttable/tagging/no_entry_completion"))
3122 {
3123 // add entry completion
3124 GtkEntryCompletion *completion = gtk_entry_completion_new();
3125 gtk_entry_completion_set_model(completion, gtk_tree_view_get_model(GTK_TREE_VIEW(d->dictionary_view)));
3126 gtk_entry_completion_set_text_column(completion, DT_LIB_TAGGING_COL_PATH);
3127 gtk_entry_completion_set_inline_completion(completion, TRUE);
3128 gtk_entry_completion_set_match_func(completion, _completion_match_func, NULL, NULL);
3129 gtk_entry_set_completion(d->entry, completion);
3130 d->completion = completion;
3131 }
3132 else d->completion = NULL;
3133
3134 // completion works better if this happens after completion connection
3135 g_signal_connect(G_OBJECT(d->entry), "key-press-event", G_CALLBACK(_key_pressed), (gpointer)self);
3136
3137 /* connect to mouse over id */
3138 DT_DEBUG_CONTROL_SIGNAL_CONNECT(darktable.signals, DT_SIGNAL_MOUSE_OVER_IMAGE_CHANGE,
3139 G_CALLBACK(_lib_tagging_redraw_callback), self);
3140 DT_DEBUG_CONTROL_SIGNAL_CONNECT(darktable.signals, DT_SIGNAL_TAG_CHANGED,
3141 G_CALLBACK(_lib_tagging_tags_changed_callback), self);
3142 DT_DEBUG_CONTROL_SIGNAL_CONNECT(darktable.signals, DT_SIGNAL_SELECTION_CHANGED,
3143 G_CALLBACK(_lib_selection_changed_callback), self);
3144 DT_DEBUG_CONTROL_SIGNAL_CONNECT(darktable.signals, DT_SIGNAL_COLLECTION_CHANGED,
3145 G_CALLBACK(_collection_updated_callback), self);
3146
3147 d->collection = g_malloc(4096);
3148 _update_layout(self);
3149 _init_treeview(self, 0);
3150 _set_keyword(self);
3151 _init_treeview(self, 1);
3152 _update_atdetach_buttons(self);
3153 }
3154
gui_cleanup(dt_lib_module_t * self)3155 void gui_cleanup(dt_lib_module_t *self)
3156 {
3157 dt_lib_cancel_postponed_update(self);
3158 dt_lib_tagging_t *d = (dt_lib_tagging_t *)self->data;
3159
3160 dt_gui_key_accel_block_on_focus_disconnect(GTK_WIDGET(d->entry));
3161 DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(darktable.signals, G_CALLBACK(_lib_tagging_redraw_callback), self);
3162 DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(darktable.signals, G_CALLBACK(_lib_tagging_tags_changed_callback), self);
3163 DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(darktable.signals, G_CALLBACK(_lib_selection_changed_callback), self);
3164 DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(darktable.signals, G_CALLBACK(_collection_updated_callback), self);
3165 g_free(d->collection);
3166 if(d->drag.tagname) g_free(d->drag.tagname);
3167 if(d->drag.path) gtk_tree_path_free(d->drag.path);
3168 free(self->data);
3169 self->data = NULL;
3170 }
3171
3172 // http://stackoverflow.com/questions/4631388/transparent-floating-gtkentry
_lib_tagging_tag_key_press(GtkWidget * entry,GdkEventKey * event,dt_lib_module_t * self)3173 static gboolean _lib_tagging_tag_key_press(GtkWidget *entry, GdkEventKey *event, dt_lib_module_t *self)
3174 {
3175 dt_lib_tagging_t *d = (dt_lib_tagging_t *)self->data;
3176 switch(event->keyval)
3177 {
3178 case GDK_KEY_Escape:
3179 g_list_free(d->floating_tag_imgs);
3180 gtk_widget_destroy(d->floating_tag_window);
3181 gtk_window_present(GTK_WINDOW(dt_ui_main_window(darktable.gui->ui)));
3182 return TRUE;
3183 case GDK_KEY_Tab:
3184 return TRUE;
3185 case GDK_KEY_Return:
3186 case GDK_KEY_KP_Enter:
3187 {
3188 const gchar *tag = gtk_entry_get_text(GTK_ENTRY(entry));
3189 const gboolean res = dt_tag_attach_string_list(tag, d->floating_tag_imgs, TRUE);
3190 if(res) dt_image_synch_xmps(d->floating_tag_imgs);
3191 g_list_free(d->floating_tag_imgs);
3192
3193 /** record last tag used */
3194 g_free(d->last_tag);
3195 d->last_tag = g_strdup(tag);
3196
3197 _init_treeview(self, 0);
3198 _init_treeview(self, 1);
3199 gtk_widget_destroy(d->floating_tag_window);
3200 gtk_window_present(GTK_WINDOW(dt_ui_main_window(darktable.gui->ui)));
3201 if(res) _raise_signal_tag_changed(self);
3202
3203 return TRUE;
3204 }
3205 }
3206 return FALSE; /* event not handled */
3207 }
3208
_lib_tagging_tag_destroy(GtkWidget * widget,GdkEvent * event,gpointer user_data)3209 static gboolean _lib_tagging_tag_destroy(GtkWidget *widget, GdkEvent *event, gpointer user_data)
3210 {
3211 gtk_widget_destroy(GTK_WIDGET(user_data));
3212 return FALSE;
3213 }
3214
_lib_tagging_tag_redo(GtkAccelGroup * accel_group,GObject * acceleratable,guint keyval,GdkModifierType modifier,dt_lib_module_t * self)3215 static gboolean _lib_tagging_tag_redo(GtkAccelGroup *accel_group, GObject *acceleratable, guint keyval,
3216 GdkModifierType modifier, dt_lib_module_t *self)
3217 {
3218 dt_lib_tagging_t *d = (dt_lib_tagging_t *)self->data;
3219
3220 if(d->last_tag)
3221 {
3222 const GList *imgs = dt_view_get_images_to_act_on(TRUE, TRUE, FALSE);
3223 const gboolean res = dt_tag_attach_string_list(d->last_tag, imgs, TRUE);
3224 if(res) dt_image_synch_xmps(imgs);
3225 _init_treeview(self, 0);
3226 _init_treeview(self, 1);
3227 if(res) _raise_signal_tag_changed(self);
3228 }
3229 return TRUE;
3230 }
3231
_lib_tagging_tag_show(GtkAccelGroup * accel_group,GObject * acceleratable,guint keyval,GdkModifierType modifier,dt_lib_module_t * self)3232 static gboolean _lib_tagging_tag_show(GtkAccelGroup *accel_group, GObject *acceleratable, guint keyval,
3233 GdkModifierType modifier, dt_lib_module_t *self)
3234 {
3235 dt_lib_tagging_t *d = (dt_lib_tagging_t *)self->data;
3236 if (d->tree_flag)
3237 {
3238 dt_control_log(_("tag shortcut is not active with tag tree view. please switch to list view"));
3239 return TRUE; // doesn't work properly with tree treeview
3240 }
3241
3242 d->floating_tag_imgs = g_list_copy((GList *)dt_view_get_images_to_act_on(FALSE, TRUE, FALSE));
3243 gint x, y;
3244 gint px, py, w, h;
3245 GtkWidget *window = dt_ui_main_window(darktable.gui->ui);
3246 GtkWidget *center = dt_ui_center(darktable.gui->ui);
3247 gdk_window_get_origin(gtk_widget_get_window(center), &px, &py);
3248
3249 w = gdk_window_get_width(gtk_widget_get_window(center));
3250 h = gdk_window_get_height(gtk_widget_get_window(center));
3251
3252 x = px + 0.5 * (w - FLOATING_ENTRY_WIDTH);
3253 y = py + h - 50;
3254
3255 /* put the floating box at the mouse pointer */
3256 // gint pointerx, pointery;
3257 // GdkDevice *device =
3258 // gdk_device_manager_get_client_pointer(gdk_display_get_device_manager(gtk_widget_get_display(widget)));
3259 // gdk_window_get_device_position (gtk_widget_get_window (widget), device, &pointerx, &pointery, NULL);
3260 // x = px + pointerx + 1;
3261 // y = py + pointery + 1;
3262
3263 d->floating_tag_window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
3264 #ifdef GDK_WINDOWING_QUARTZ
3265 dt_osx_disallow_fullscreen(d->floating_tag_window);
3266 #endif
3267 /* stackoverflow.com/questions/1925568/how-to-give-keyboard-focus-to-a-pop-up-gtk-window */
3268 gtk_widget_set_can_focus(d->floating_tag_window, TRUE);
3269 gtk_window_set_decorated(GTK_WINDOW(d->floating_tag_window), FALSE);
3270 gtk_window_set_type_hint(GTK_WINDOW(d->floating_tag_window), GDK_WINDOW_TYPE_HINT_POPUP_MENU);
3271 gtk_window_set_transient_for(GTK_WINDOW(d->floating_tag_window), GTK_WINDOW(window));
3272 gtk_widget_set_opacity(d->floating_tag_window, 0.8);
3273 gtk_window_move(GTK_WINDOW(d->floating_tag_window), x, y);
3274
3275 GtkWidget *entry = gtk_entry_new();
3276 gtk_widget_set_size_request(entry, FLOATING_ENTRY_WIDTH, -1);
3277 gtk_widget_add_events(entry, GDK_FOCUS_CHANGE_MASK);
3278
3279 GtkEntryCompletion *completion = gtk_entry_completion_new();
3280 gtk_entry_completion_set_model(completion, gtk_tree_view_get_model(GTK_TREE_VIEW(d->dictionary_view)));
3281 gtk_entry_completion_set_text_column(completion, DT_LIB_TAGGING_COL_PATH);
3282 gtk_entry_completion_set_inline_completion(completion, TRUE);
3283 gtk_entry_completion_set_popup_set_width(completion, FALSE);
3284 g_signal_connect(G_OBJECT(completion), "match-selected", G_CALLBACK(_match_selected_func), self);
3285 gtk_entry_completion_set_match_func(completion, _completion_match_func, NULL, NULL);
3286 gtk_entry_set_completion(GTK_ENTRY(entry), completion);
3287
3288 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
3289 gtk_container_add(GTK_CONTAINER(d->floating_tag_window), entry);
3290 g_signal_connect(entry, "focus-out-event", G_CALLBACK(_lib_tagging_tag_destroy), d->floating_tag_window);
3291 g_signal_connect(entry, "key-press-event", G_CALLBACK(_lib_tagging_tag_key_press), self);
3292
3293 gtk_widget_show_all(d->floating_tag_window);
3294 gtk_widget_grab_focus(entry);
3295 gtk_window_present(GTK_WINDOW(d->floating_tag_window));
3296
3297 return TRUE;
3298 }
3299
3300 // modelines: These editor modelines have been set for all relevant files by tools/update_modelines.sh
3301 // vim: shiftwidth=2 expandtab tabstop=2 cindent
3302 // kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
3303