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