1 /*
2     This file is part of darktable,
3     Copyright (C) 2019-2020 darktable developers.
4 
5 
6     darktable is free software: you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation, either version 3 of the License, or
9     (at your option) any later version.
10 
11     darktable is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15 
16     You should have received a copy of the GNU General Public License
17     along with darktable.  If not, see <http://www.gnu.org/licenses/>.
18 */
19 #include "bauhaus/bauhaus.h"
20 #include "common/darktable.h"
21 #include "common/debug.h"
22 #include "common/imageio_module.h"
23 #include "control/conf.h"
24 #include "control/control.h"
25 #include "control/signal.h"
26 #include "dtgtk/button.h"
27 #include "gui/accelerators.h"
28 #include "gui/gtk.h"
29 #include "gui/gtkentry.h"
30 #include "libs/lib.h"
31 #include "libs/lib_api.h"
32 #ifdef GDK_WINDOWING_QUARTZ
33 #include "osx/osx.h"
34 #endif
35 #include <gdk/gdkkeysyms.h>
36 #include <gtk/gtk.h>
37 #include <stdlib.h>
38 
39 typedef enum dt_lib_tagging_cols_t
40 {
41   DT_LIB_EXPORT_METADATA_COL_XMP = 0,
42   DT_LIB_EXPORT_METADATA_COL_TYPE,
43   DT_LIB_EXPORT_METADATA_COL_FORMULA,
44   DT_LIB_EXPORT_METADATA_COL_VISIBLE,
45   DT_LIB_EXPORT_METADATA_NUM_COLS
46 } dt_lib_tagging_cols_t;
47 
48 typedef struct dt_lib_export_metadata_t
49 {
50   GtkTreeView *view;
51   GtkListStore *liststore;
52   GtkWidget *dialog;
53   GtkTreeView *sel_view;
54   GtkWidget *sel_entry;
55   const gchar *sel_entry_text;
56   GList *taglist;
57 } dt_lib_export_metadata_t;
58 
59 const GList *dt_exif_get_exiv2_taglist();
60 
61 // find a string on the list
find_metadata_iter_per_text(GtkTreeModel * model,GtkTreeIter * iter,gint col,const char * text)62 static gboolean find_metadata_iter_per_text(GtkTreeModel *model, GtkTreeIter *iter, gint col, const char *text)
63 {
64   if(!text) return FALSE;
65   GtkTreeIter it;
66   gboolean valid = gtk_tree_model_get_iter_first(model, &it);
67   char *name;
68   while (valid)
69   {
70     gtk_tree_model_get(model, &it, col, &name, -1);
71     const gboolean found = g_strcmp0(text, name) == 0;
72     g_free(name);
73     if(found)
74     {
75       if (iter) *iter = it;
76       return TRUE;
77     }
78     valid = gtk_tree_model_iter_next(model, &it);
79   }
80   return FALSE;
81 }
82 
83 // add selected metadata tag to formula list
add_selected_metadata(GtkTreeView * view,dt_lib_export_metadata_t * d)84 static void add_selected_metadata(GtkTreeView *view, dt_lib_export_metadata_t *d)
85 {
86   GtkTreeIter iter;
87   GtkTreeModel *model = gtk_tree_view_get_model(view);
88   GtkTreeSelection *selection = gtk_tree_view_get_selection(view);
89   if(gtk_tree_selection_get_selected(selection, &model, &iter))
90   {
91     char *tagname;
92     gtk_tree_model_get(model, &iter, DT_LIB_EXPORT_METADATA_COL_XMP, &tagname, -1);
93     if (!find_metadata_iter_per_text(GTK_TREE_MODEL(d->liststore), NULL, DT_LIB_EXPORT_METADATA_COL_XMP, tagname))
94     {
95       gtk_list_store_append(d->liststore, &iter);
96       gtk_list_store_set(d->liststore, &iter, DT_LIB_EXPORT_METADATA_COL_XMP, tagname,
97                             DT_LIB_EXPORT_METADATA_COL_FORMULA, "", -1);
98       selection = gtk_tree_view_get_selection(d->view);
99       gtk_tree_selection_select_iter(selection, &iter);
100     }
101     g_free(tagname);
102   }
103 }
104 
105 // choice of a metadata tag
click_on_metadata_list(GtkWidget * view,GdkEventButton * event,dt_lib_export_metadata_t * d)106 static gboolean click_on_metadata_list(GtkWidget *view, GdkEventButton *event, dt_lib_export_metadata_t *d)
107 {
108   if(event->type == GDK_2BUTTON_PRESS && event->button == 1)
109   {
110 
111     GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
112     GtkTreePath *path = NULL;
113     // Get tree path for row that was clicked
114     if(gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(view), (gint)event->x, (gint)event->y, &path, NULL, NULL, NULL))
115     {
116       gtk_tree_selection_select_path(selection, path);
117       if(event->type == GDK_2BUTTON_PRESS && event->button == 1)
118       {
119         add_selected_metadata(GTK_TREE_VIEW(view), d);
120         gtk_tree_path_free(path);
121         return TRUE;
122       }
123     }
124     gtk_tree_path_free(path);
125   }
126   return FALSE;
127 }
128 
129 // routine to set individual visibility flag
set_matching_tag_visibility(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,dt_lib_export_metadata_t * d)130 static gboolean set_matching_tag_visibility(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, dt_lib_export_metadata_t *d)
131 {
132   gboolean visible;
133   gchar *tagname = NULL;
134   gtk_tree_model_get(model, iter, DT_LIB_EXPORT_METADATA_COL_XMP, &tagname, -1);
135   if (!d->sel_entry_text[0])
136     visible = TRUE;
137   else
138   {
139     gchar *haystack = g_utf8_strdown(tagname, -1);
140     gchar *needle = g_utf8_strdown(d->sel_entry_text, -1);
141     visible = (g_strrstr(haystack, needle) != NULL);
142     g_free(haystack);
143     g_free(needle);
144   }
145   gtk_list_store_set(GTK_LIST_STORE(model), iter, DT_LIB_EXPORT_METADATA_COL_VISIBLE, visible, -1);
146   g_free(tagname);
147   return FALSE;
148 }
149 
150 // set the metadata tag visibility aligned with filter
_tag_name_changed(GtkEntry * entry,dt_lib_export_metadata_t * d)151 static void _tag_name_changed(GtkEntry *entry, dt_lib_export_metadata_t *d)
152 {
153   d->sel_entry_text = gtk_entry_get_text(GTK_ENTRY(d->sel_entry));
154   GtkTreeModel *model = gtk_tree_view_get_model(d->sel_view);
155   GtkTreeModel *store = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
156   gtk_tree_model_foreach(store, (GtkTreeModelForeachFunc)set_matching_tag_visibility, d);
157 }
158 
159 // dialog to add metadata tag into the formula list
add_tag_button_clicked(GtkButton * button,dt_lib_export_metadata_t * d)160 static void add_tag_button_clicked(GtkButton *button, dt_lib_export_metadata_t *d)
161 {
162   GtkWidget *dialog = gtk_dialog_new_with_buttons(_("select tag"), GTK_WINDOW(d->dialog), GTK_DIALOG_DESTROY_WITH_PARENT,
163                                        _("add"), GTK_RESPONSE_YES, _("done"), GTK_RESPONSE_NONE, NULL);
164   gtk_window_set_default_size(GTK_WINDOW(dialog), 300, -1);
165   GtkWidget *area = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
166   GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 8);
167   gtk_container_set_border_width(GTK_CONTAINER(vbox), 8);
168   gtk_container_add(GTK_CONTAINER(area), vbox);
169 
170   GtkWidget *entry = gtk_entry_new();
171   d->sel_entry = entry;
172   gtk_entry_set_text(GTK_ENTRY(entry), "");
173   gtk_widget_set_tooltip_text(entry, _("list filter"));
174   gtk_box_pack_start(GTK_BOX(vbox), entry, TRUE, TRUE, 0);
175   g_signal_connect(G_OBJECT(entry), "changed", G_CALLBACK(_tag_name_changed), d);
176 
177   GtkWidget *w = gtk_scrolled_window_new(NULL, NULL);
178   gtk_widget_set_size_request(w, DT_PIXEL_APPLY_DPI(500), DT_PIXEL_APPLY_DPI(300));
179   gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
180   gtk_box_pack_start(GTK_BOX(vbox), w, TRUE, TRUE, 0);
181   GtkTreeView *view = GTK_TREE_VIEW(gtk_tree_view_new());
182   d->sel_view = view;
183   gtk_container_add(GTK_CONTAINER(w), GTK_WIDGET(view));
184   gtk_widget_set_tooltip_text(GTK_WIDGET(view), _("list of available tags. click 'add' button or double-click on tag to add the selected one"));
185   gtk_tree_selection_set_mode(gtk_tree_view_get_selection(view), GTK_SELECTION_SINGLE);
186   GtkCellRenderer *renderer = gtk_cell_renderer_text_new();
187   GtkTreeViewColumn *col = gtk_tree_view_column_new_with_attributes(_("tag"), renderer, "text", 0, NULL);
188   gtk_tree_view_append_column(view, col);
189   renderer = gtk_cell_renderer_text_new();
190   col = gtk_tree_view_column_new_with_attributes(_("type"), renderer, "text", 1, NULL);
191   gtk_tree_view_append_column(view, col);
192   GtkListStore *liststore = gtk_list_store_new(4, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN);
193   GtkTreeModel *model = gtk_tree_model_filter_new(GTK_TREE_MODEL(liststore), NULL);
194   gtk_tree_model_filter_set_visible_column(GTK_TREE_MODEL_FILTER(model), DT_LIB_EXPORT_METADATA_COL_VISIBLE);
195 
196   // populate the metadata tag list with exiv2 information
197   for(GList *tag = d->taglist; tag; tag = g_list_next(tag))
198   {
199     GtkTreeIter iter;
200     gtk_list_store_append(liststore, &iter);
201     const char *tagname = tag->data;
202     char *type = g_strstr_len(tagname, -1, ",");
203     if(type)
204     {
205       type[0] = '\0';
206       type++;
207     }
208     gtk_list_store_set(liststore, &iter, DT_LIB_EXPORT_METADATA_COL_XMP, tagname, DT_LIB_EXPORT_METADATA_COL_TYPE, type,
209         DT_LIB_EXPORT_METADATA_COL_VISIBLE, TRUE, -1);
210     if(type)
211     {
212       type--;
213       type[0] = ',';
214     }
215   }
216 
217   gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(liststore), DT_LIB_EXPORT_METADATA_COL_XMP, GTK_SORT_ASCENDING);
218   gtk_tree_view_set_model(view, model);
219   g_object_unref(model);
220   g_signal_connect(G_OBJECT(view), "button-press-event", G_CALLBACK(click_on_metadata_list), (gpointer)d);
221 
222   #ifdef GDK_WINDOWING_QUARTZ
223     dt_osx_disallow_fullscreen(dialog);
224   #endif
225     gtk_widget_show_all(dialog);
226   while (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_YES)
227   {
228     add_selected_metadata(view, d);
229   }
230   gtk_widget_destroy(dialog);
231 }
232 
remove_tag_from_list(dt_lib_export_metadata_t * d)233 static void remove_tag_from_list(dt_lib_export_metadata_t *d)
234 {
235   GtkTreeIter iter;
236   GtkTreeModel *model = GTK_TREE_MODEL(d->liststore);
237   GtkTreeSelection *selection = gtk_tree_view_get_selection(d->view);
238   if(gtk_tree_selection_get_selected(selection, &model, &iter))
239   {
240     gtk_list_store_remove(d->liststore, &iter);
241   }
242 }
243 
delete_tag_button_clicked(GtkButton * button,dt_lib_export_metadata_t * d)244 static void delete_tag_button_clicked(GtkButton *button, dt_lib_export_metadata_t *d)
245 {
246   remove_tag_from_list(d);
247 }
248 
key_press_on_list(GtkWidget * widget,GdkEventKey * event,dt_lib_export_metadata_t * d)249 static gboolean key_press_on_list(GtkWidget *widget, GdkEventKey *event, dt_lib_export_metadata_t *d)
250 {
251   if(event->type == GDK_KEY_PRESS && event->keyval == GDK_KEY_Delete && !event->state)
252   {
253     remove_tag_from_list(d);
254     return TRUE;
255   }
256   return FALSE;
257 }
258 
formula_edited(GtkCellRenderer * renderer,gchar * path,gchar * new_text,dt_lib_export_metadata_t * d)259 static void formula_edited(GtkCellRenderer *renderer, gchar *path, gchar *new_text, dt_lib_export_metadata_t *d)
260 {
261   GtkTreeIter iter;
262   if (gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(d->liststore), &iter, path))
263     gtk_list_store_set(d->liststore, &iter, DT_LIB_EXPORT_METADATA_COL_FORMULA, new_text, -1);
264 }
265 
dt_lib_export_metadata_configuration_dialog(char * metadata_presets,const gboolean ondisk)266 char *dt_lib_export_metadata_configuration_dialog(char *metadata_presets, const gboolean ondisk)
267 {
268   dt_lib_export_metadata_t *d = calloc(1, sizeof(dt_lib_export_metadata_t));
269 
270   GtkWidget *win = dt_ui_main_window(darktable.gui->ui);
271   GtkWidget *dialog = gtk_dialog_new_with_buttons(_("edit metadata exportation"), GTK_WINDOW(win), GTK_DIALOG_DESTROY_WITH_PARENT,
272                                        _("cancel"), GTK_RESPONSE_NONE, _("save"), GTK_RESPONSE_YES, NULL);
273   d->dialog = dialog;
274   gtk_window_set_default_size(GTK_WINDOW(dialog), 300, -1);
275   GtkWidget *area = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
276 
277   GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
278   gtk_container_add(GTK_CONTAINER(area), hbox);
279 
280   // general info
281   GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 8);
282   gtk_container_set_border_width(GTK_CONTAINER(vbox), 8);
283   gtk_container_add(GTK_CONTAINER(hbox), vbox);
284   GtkWidget *label = gtk_label_new(_("general settings"));
285   gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, TRUE, 0);
286   GtkWidget *vbox2 = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
287   gtk_box_pack_start(GTK_BOX(vbox), vbox2, FALSE, TRUE, 0);
288 
289   GtkWidget *exiftag = gtk_check_button_new_with_label(_("exif data"));
290   gtk_widget_set_tooltip_text(exiftag, _("export exif metadata"));
291   gtk_box_pack_start(GTK_BOX(vbox2), exiftag, FALSE, TRUE, 0);
292   GtkWidget *dtmetadata = gtk_check_button_new_with_label(_("metadata"));
293   gtk_widget_set_tooltip_text(dtmetadata, _("export dt xmp metadata (from metadata editor module)"));
294   gtk_box_pack_start(GTK_BOX(vbox2), dtmetadata, FALSE, TRUE, 0);
295 
296   GtkWidget *calculated;
297   if (!ondisk)
298   {
299     GtkWidget *box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
300     gtk_box_pack_start(GTK_BOX(vbox2), box, FALSE, TRUE, 0);
301     GtkWidget *vbox3 = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
302     gtk_box_pack_start(GTK_BOX(box), vbox3, FALSE, TRUE, 10);
303     calculated = gtk_check_button_new_with_label(_("only embedded"));
304     gtk_widget_set_tooltip_text(calculated, _("per default the interface sends some (limited) metadata beside the image to remote storage.\n"
305         "to avoid this and let only image embedded dt xmp metadata, check this flag.\n"
306         "if remote storage doesn't understand dt xmp metadata, you can use calculated metadata instead"));
307     gtk_box_pack_start(GTK_BOX(vbox3), calculated, FALSE, TRUE, 0);
308   }
309 
310   GtkWidget *geotag = gtk_check_button_new_with_label(_("geo tags"));
311   gtk_widget_set_tooltip_text(geotag, _("export geo tags"));
312   gtk_box_pack_start(GTK_BOX(vbox2), geotag, FALSE, TRUE, 0);
313   GtkWidget *dttag = gtk_check_button_new_with_label(_("tags"));
314   gtk_widget_set_tooltip_text(dttag, _("export tags (to Xmp.dc.Subject)"));
315   gtk_box_pack_start(GTK_BOX(vbox2), dttag, FALSE, TRUE, 0);
316 
317   GtkWidget *box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
318   gtk_box_pack_start(GTK_BOX(vbox2), box, FALSE, TRUE, 0);
319   GtkWidget *vbox3 = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
320   gtk_box_pack_start(GTK_BOX(box), vbox3, FALSE, TRUE, 10);
321   GtkWidget *private = gtk_check_button_new_with_label(_("private tags"));
322   gtk_widget_set_tooltip_text(private, _("export private tags"));
323   gtk_box_pack_start(GTK_BOX(vbox3), private, FALSE, TRUE, 0);
324   GtkWidget *synonyms = gtk_check_button_new_with_label(_("synonyms"));
325   gtk_widget_set_tooltip_text(synonyms, _("export tags synonyms"));
326   gtk_box_pack_start(GTK_BOX(vbox3), synonyms, FALSE, TRUE, 0);
327   GtkWidget *omithierarchy = gtk_check_button_new_with_label(_("omit hierarchy"));
328   gtk_widget_set_tooltip_text(omithierarchy, _("only the last part of the hierarchical tags is included. can be useful if categories are not used"));
329   gtk_box_pack_start(GTK_BOX(vbox3), omithierarchy, FALSE, TRUE, 0);
330 
331   GtkWidget *hierarchical = gtk_check_button_new_with_label(_("hierarchical tags"));
332   gtk_widget_set_tooltip_text(hierarchical, _("export hierarchical tags (to Xmp.lr.Hierarchical Subject)"));
333   gtk_box_pack_start(GTK_BOX(vbox2), hierarchical, FALSE, TRUE, 0);
334   GtkWidget *dthistory = gtk_check_button_new_with_label(_("develop history"));
335   gtk_widget_set_tooltip_text(dthistory, _("export dt development data (recovery purpose in case of loss of database or xmp file)"));
336   gtk_box_pack_start(GTK_BOX(vbox2), dthistory, FALSE, TRUE, 0);
337 
338   // specific rules
339   vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 8);
340   gtk_container_set_border_width(GTK_CONTAINER(vbox), 8);
341   gtk_container_add(GTK_CONTAINER(hbox), vbox);
342   label = gtk_label_new(_("per metadata settings"));
343   gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, TRUE, 0);
344 
345   GtkWidget *w = gtk_scrolled_window_new(NULL, NULL);
346   gtk_widget_set_size_request(w, DT_PIXEL_APPLY_DPI(450), DT_PIXEL_APPLY_DPI(100));
347   gtk_widget_set_hexpand(w, TRUE);
348   gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
349   gtk_box_pack_start(GTK_BOX(vbox), w, TRUE, TRUE, 0);
350   GtkTreeView *view = GTK_TREE_VIEW(gtk_tree_view_new());
351   d->view = view;
352   gtk_container_add(GTK_CONTAINER(w), GTK_WIDGET(view));
353   gtk_widget_set_tooltip_text(GTK_WIDGET(view), _("list of available tags"));
354   gtk_tree_selection_set_mode(gtk_tree_view_get_selection(view), GTK_SELECTION_SINGLE);
355   GtkCellRenderer *renderer = gtk_cell_renderer_text_new();
356   GtkTreeViewColumn *col = gtk_tree_view_column_new_with_attributes(_("redefined tag"), renderer, "text", 0, NULL);
357   gtk_tree_view_append_column(view, col);
358   renderer = gtk_cell_renderer_text_new();
359   g_object_set(renderer, "editable", TRUE, NULL);
360   g_signal_connect(G_OBJECT(renderer), "edited", G_CALLBACK(formula_edited), (gpointer)d);
361   col = gtk_tree_view_column_new_with_attributes(_("formula"), renderer, "text", 2, NULL);
362   gtk_tree_view_append_column(view, col);
363   char *tooltip_text = dt_gtkentry_build_completion_tooltip_text(
364                         _("list of calculated metadata\n"
365                         "if formula is empty, the corresponding metadata is removed from exported file,\n"
366                         "if formula is \'=\', the exif metadata is exported even if exif data are disabled\n"
367                         "otherwise the corresponding metadata is calculated and added to exported file\n"
368                         "click on formula cell to edit. recognized variables:\n"),
369                         dt_gtkentry_get_default_path_compl_list());
370   gtk_widget_set_tooltip_text(GTK_WIDGET(view), tooltip_text);
371   g_free(tooltip_text);
372   g_signal_connect(G_OBJECT(view), "key_press_event", G_CALLBACK(key_press_on_list), (gpointer)d);
373 
374   GtkListStore *liststore = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);
375   d->liststore = liststore;
376   gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(liststore), DT_LIB_EXPORT_METADATA_COL_XMP, GTK_SORT_ASCENDING);
377   gtk_tree_view_set_model(view, GTK_TREE_MODEL(liststore));
378   g_object_unref(liststore);
379   d->taglist = (GList *)dt_exif_get_exiv2_taglist();
380   GList *list = dt_util_str_to_glist("\1", metadata_presets);
381   int32_t flags = 0;
382   if (list)
383   {
384     char *flags_hexa = list->data;
385     flags = strtol(flags_hexa, NULL, 16);
386     list = g_list_remove(list, flags_hexa);
387     g_free(flags_hexa);
388     if (list)
389     {
390       for (GList *tags = list; tags; tags = g_list_next(tags))
391       {
392         GtkTreeIter iter;
393         const char *tagname = (char *)tags->data;
394         tags = g_list_next(tags);
395         if (!tags) break;
396         const char *formula = (char *)tags->data;
397         gtk_list_store_append(d->liststore, &iter);
398         gtk_list_store_set(d->liststore, &iter, DT_LIB_EXPORT_METADATA_COL_XMP, tagname,
399           DT_LIB_EXPORT_METADATA_COL_FORMULA, formula, -1);
400       }
401     }
402   }
403   g_list_free_full(list, g_free);
404 
405   gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(exiftag), flags & DT_META_EXIF);
406   gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(dtmetadata), flags & DT_META_METADATA);
407   gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(geotag), flags & DT_META_GEOTAG);
408   gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(dttag), flags & DT_META_TAG);
409   gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(private), flags & DT_META_PRIVATE_TAG);
410   gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(synonyms), flags & DT_META_SYNONYMS_TAG);
411   gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(hierarchical), flags & DT_META_HIERARCHICAL_TAG);
412   gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(dthistory), flags & DT_META_DT_HISTORY);
413   if (!ondisk)
414     gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(calculated), flags & DT_META_CALCULATED);
415   gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(omithierarchy), flags & DT_META_OMIT_HIERARCHY);
416 
417   box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
418   gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, TRUE, 0);
419 
420   GtkWidget *button = dtgtk_button_new(dtgtk_cairo_paint_plus_simple, CPF_STYLE_FLAT, NULL);
421   gtk_widget_set_tooltip_text(button, _("add an output metadata tag"));
422   gtk_box_pack_end(GTK_BOX(box), button, FALSE, TRUE, 0);
423   g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(add_tag_button_clicked), (gpointer)d);
424 
425   button = dtgtk_button_new(dtgtk_cairo_paint_minus_simple, CPF_STYLE_FLAT, NULL);
426   gtk_widget_set_tooltip_text(button, _("delete metadata tag"));
427   gtk_box_pack_end(GTK_BOX(box), button, FALSE, TRUE, 0);
428   g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(delete_tag_button_clicked), (gpointer)d);
429 
430 #ifdef GDK_WINDOWING_QUARTZ
431   dt_osx_disallow_fullscreen(dialog);
432 #endif
433   gtk_widget_show_all(dialog);
434 
435   char *newlist = metadata_presets;
436   if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_YES)
437   {
438     const gint newflags = (
439                     (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(exiftag)) ? DT_META_EXIF : 0) |
440                     (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(dtmetadata)) ? DT_META_METADATA : 0) |
441                     (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(geotag)) ? DT_META_GEOTAG : 0) |
442                     (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(dttag)) ? DT_META_TAG : 0) |
443                     (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(private)) ? DT_META_PRIVATE_TAG : 0) |
444                     (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(synonyms)) ? DT_META_SYNONYMS_TAG : 0) |
445                     (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(hierarchical)) ? DT_META_HIERARCHICAL_TAG : 0) |
446                     (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(dthistory)) ? DT_META_DT_HISTORY : 0) |
447                     (!ondisk  ? (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(calculated)) ? DT_META_CALCULATED : 0) : 0) |
448                     (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(omithierarchy)) ? DT_META_OMIT_HIERARCHY : 0)
449                     );
450 
451     newlist = dt_util_dstrcat(NULL,"%x", newflags);
452     GtkTreeIter iter;
453     gboolean valid = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(d->liststore), &iter);
454     while(valid)
455     {
456       char *tagname, *formula;
457       gtk_tree_model_get(GTK_TREE_MODEL(d->liststore), &iter, DT_LIB_EXPORT_METADATA_COL_XMP, &tagname,
458           DT_LIB_EXPORT_METADATA_COL_FORMULA, &formula, -1);
459       // metadata presets are stored into a single string with '\1' as a separator
460       newlist = dt_util_dstrcat(newlist,"\1%s\1%s", tagname, formula);
461       g_free(tagname);
462       g_free(formula);
463       valid = gtk_tree_model_iter_next (GTK_TREE_MODEL(d->liststore), &iter);
464     }
465     g_free(metadata_presets);
466     dt_lib_export_metadata_set_conf(newlist);
467   }
468   gtk_widget_destroy(dialog);
469   free(d);
470   return newlist;
471 }
472 
473 // modelines: These editor modelines have been set for all relevant files by tools/update_modelines.sh
474 // vim: shiftwidth=2 expandtab tabstop=2 cindent
475 // kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
476