1 /*
2     This file is part of darktable,
3     Copyright (C) 2011-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 
19 #include "common/collection.h"
20 #include "common/darktable.h"
21 #include "common/debug.h"
22 #include "common/image_cache.h"
23 #include "common/metadata.h"
24 #include "common/tags.h"
25 #include "control/conf.h"
26 #include "control/control.h"
27 #include "develop/develop.h"
28 #include "gui/accelerators.h"
29 #include "gui/gtk.h"
30 #include "libs/lib.h"
31 #include "libs/lib_api.h"
32 
33 #include <gdk/gdkkeysyms.h>
34 #include <sys/param.h>
35 #ifdef GDK_WINDOWING_QUARTZ
36 #include "osx/osx.h"
37 #endif
38 #ifdef USE_LUA
39 #include "lua/call.h"
40 #include "lua/image.h"
41 #endif
42 
43 DT_MODULE(1)
44 
45 typedef enum dt_metadata_pref_cols_t
46 {
47   DT_METADATA_PREF_COL_INDEX = 0, // index
48   DT_METADATA_PREF_COL_NAME_L,    // displayed name
49   DT_METADATA_PREF_COL_VISIBLE,   // visibility
50   DT_METADATA_PREF_NUM_COLS
51 } dt_metadata_pref_cols_t;
52 
53 typedef struct dt_lib_metadata_view_t
54 {
55   GtkWidget *grid;
56   GList *metadata;
57   GObject *filmroll_event;
58 } dt_lib_metadata_view_t;
59 
60 typedef struct dt_lib_metadata_info_t
61 {
62   int index;          // md_xx value or index inserted by lua
63   int order;          // display order
64   char *name;         // metadata name
65   char *value;        // metadata value
66   char *tooltip;      // tooltip
67   gboolean visible;
68 } dt_lib_metadata_info_t;
69 
70 enum
71 {
72   /* internal */
73   md_internal_filmroll = 0,
74   md_internal_imgid,
75   md_internal_groupid,
76   md_internal_filename,
77   md_internal_version,
78   md_internal_fullpath,
79   md_internal_local_copy,
80   md_internal_import_timestamp,
81   md_internal_change_timestamp,
82   md_internal_export_timestamp,
83   md_internal_print_timestamp,
84   md_internal_flags,
85 
86   /* exif */
87   md_exif_model,
88   md_exif_maker,
89   md_exif_lens,
90   md_exif_aperture,
91   md_exif_exposure,
92   md_exif_exposure_bias,
93   md_exif_focal_length,
94   md_exif_focus_distance,
95   md_exif_iso,
96   md_exif_datetime,
97   md_exif_width,
98   md_exif_height,
99 
100   /* size of final image */
101   md_width,
102   md_height,
103 
104   /* xmp */
105   md_xmp_metadata,
106 
107   /* geotagging */
108   md_geotagging_lat = md_xmp_metadata + DT_METADATA_NUMBER,
109   md_geotagging_lon,
110   md_geotagging_ele,
111 
112   /* tags */
113   md_tag_names,
114   md_categories,
115 
116   /* entries, do not touch! */
117   md_size
118 };
119 
120 static const char *_labels[] = {
121   /* internal */
122   N_("filmroll"),
123   N_("image id"),
124   N_("group id"),
125   N_("filename"),
126   N_("version"),
127   N_("full path"),
128   N_("local copy"),
129   N_("import timestamp"),
130   N_("change timestamp"),
131   N_("export timestamp"),
132   N_("print timestamp"),
133   N_("flags"),
134 
135   /* exif */
136   N_("model"),
137   N_("maker"),
138   N_("lens"),
139   N_("aperture"),
140   N_("exposure"),
141   N_("exposure bias"),
142   N_("focal length"),
143   N_("focus distance"),
144   N_("ISO"),
145   N_("datetime"),
146   N_("width"),
147   N_("height"),
148   N_("export width"),
149   N_("export height"),
150 
151   /* xmp */
152   //FIXME: reserve DT_METADATA_NUMBER places
153   "","","","","","","",
154 
155   /* geotagging */
156   N_("latitude"),
157   N_("longitude"),
158   N_("elevation"),
159 
160   /* tags */
161   N_("tags"),
162   N_("categories"),
163 };
164 
165 static gboolean _dndactive = FALSE;
166 
name(dt_lib_module_t * self)167 const char *name(dt_lib_module_t *self)
168 {
169   return _("image information");
170 }
171 
views(dt_lib_module_t * self)172 const char **views(dt_lib_module_t *self)
173 {
174   static const char *v[] = {"*", NULL};
175   return v;
176 }
177 
container(dt_lib_module_t * self)178 uint32_t container(dt_lib_module_t *self)
179 {
180   return DT_UI_CONTAINER_PANEL_LEFT_CENTER;
181 }
182 
position()183 int position()
184 {
185   return 299;
186 }
187 
_is_metadata_ui(const int i)188 static gboolean _is_metadata_ui(const int i)
189 {
190   // internal metadata ar not to be shown on the ui
191   if(i >= md_xmp_metadata && i < md_xmp_metadata + DT_METADATA_NUMBER)
192   {
193     const uint32_t keyid = dt_metadata_get_keyid_by_display_order(i);
194     return !(dt_metadata_get_type(keyid) == DT_METADATA_TYPE_INTERNAL);
195   }
196   else return TRUE;
197 }
198 
_get_label(const int i)199 static const char *_get_label(const int i)
200 {
201   if(i >= md_xmp_metadata && i < md_xmp_metadata + DT_METADATA_NUMBER)
202   {
203     const uint32_t keyid = dt_metadata_get_keyid_by_display_order(i - md_xmp_metadata);
204     return(dt_metadata_get_name(keyid));
205   }
206   else return _labels[i];
207 }
208 
209 #define NODATA_STRING "-"
210 
211 // initialize the metadata queue
_lib_metadata_init_queue(dt_lib_module_t * self)212 static void _lib_metadata_init_queue(dt_lib_module_t *self)
213 {
214   dt_lib_metadata_view_t *d = (dt_lib_metadata_view_t *)self->data;
215   d->metadata = NULL;
216   for(int i = md_size - 1; i >= 0; i--)
217   {
218     dt_lib_metadata_info_t *m = g_malloc0(sizeof(dt_lib_metadata_info_t));
219     m->name = (char *)_get_label(i);
220     m->value = g_strdup(NODATA_STRING);
221     m->index = m->order = i;
222     m->visible = _is_metadata_ui(i);
223     d->metadata = g_list_prepend(d->metadata, m);
224   }
225 }
226 
227 // helper which eliminates non-printable characters from a string
228 // strings which are already in valid UTF-8 are retained.
_filter_non_printable(char * string,size_t length)229 static void _filter_non_printable(char *string, size_t length)
230 {
231   /* explicitly tell the validator to ignore the trailing nulls, otherwise this fails */
232   if(g_utf8_validate(string, -1, 0)) return;
233 
234   unsigned char *str = (unsigned char *)string;
235   int n = 0;
236 
237   while(*str != '\000' && n < length)
238   {
239     if((*str < 0x20) || (*str >= 0x7f)) *str = '.';
240 
241     str++;
242     n++;
243   }
244 }
245 
_get_metadata_per_index(const int index,dt_lib_module_t * self)246 static dt_lib_metadata_info_t *_get_metadata_per_index(const int index, dt_lib_module_t *self)
247 {
248   dt_lib_metadata_view_t *d = (dt_lib_metadata_view_t *)self->data;
249   for(GList *meta = d->metadata; meta; meta = g_list_next(meta))
250   {
251     dt_lib_metadata_info_t *m = (dt_lib_metadata_info_t *)meta->data;
252     if(m->index == index)
253     {
254       return m;
255     }
256   }
257   return NULL;
258 }
259 
_metadata_update_markup(const gint32 i,const char * const format,dt_lib_module_t * self)260 static void _metadata_update_markup(const gint32 i, const char *const format, dt_lib_module_t *self)
261 {
262   dt_lib_metadata_info_t *m = _get_metadata_per_index(i, self);
263   dt_lib_metadata_view_t *d = (dt_lib_metadata_view_t *)self->data;
264 
265   GtkLabel *label = GTK_LABEL(gtk_grid_get_child_at(GTK_GRID(d->grid), 1, m->order));
266   char *markup = g_markup_printf_escaped(format, gtk_label_get_text(label));
267   gtk_label_set_markup(label, markup);
268   g_free(markup);
269 }
270 
271 /* helper function for updating a metadata value */
_metadata_update_value(const int i,const char * value,dt_lib_module_t * self)272 static void _metadata_update_value(const int i, const char *value, dt_lib_module_t *self)
273 {
274   dt_lib_metadata_view_t *d = (dt_lib_metadata_view_t *)self->data;
275   gboolean validated = g_utf8_validate(value, -1, NULL);
276   const gchar *str = validated ? value : NODATA_STRING;
277   dt_lib_metadata_info_t *m = _get_metadata_per_index(i, self);
278   if(m)
279   {
280     if(m->value) g_free(m->value);
281     m->value = g_strdup(str);
282     GtkWidget *w_value = gtk_grid_get_child_at(GTK_GRID(d->grid), 1, m->order);
283     gtk_label_set_text(GTK_LABEL(w_value), str);
284     const char *tooltip = m->tooltip ? m->tooltip : m->value;
285     gtk_widget_set_tooltip_text(GTK_WIDGET(w_value), tooltip);
286   }
287 }
288 
_metadata_update_tooltip(const int i,const char * tooltip,dt_lib_module_t * self)289 static void _metadata_update_tooltip(const int i, const char *tooltip, dt_lib_module_t *self)
290 {
291   dt_lib_metadata_info_t *m = _get_metadata_per_index(i, self);
292   if(m)
293   {
294     if(m->tooltip) g_free(m->tooltip);
295     m->tooltip = g_strdup(tooltip);
296   }
297 }
298 
_metadata_update_timestamp(const int i,const time_t * value,dt_lib_module_t * self)299 static void _metadata_update_timestamp(const int i, const time_t *value, dt_lib_module_t *self)
300 {
301   char datetime[200];
302   struct tm tm_val;
303   // just %c is too long and includes a time zone that we don't know from exif
304   const size_t datetime_len = strftime(datetime, sizeof(datetime), "%a %x %X", localtime_r(value, &tm_val));
305   if(datetime_len > 0)
306   {
307     const gboolean valid_utf = g_utf8_validate(datetime, datetime_len, NULL);
308     if(valid_utf)
309     {
310       _metadata_update_value(i, datetime, self);
311     }
312     else
313     {
314       GError *error = NULL;
315       gchar *local_datetime = g_locale_to_utf8(datetime,datetime_len,NULL,NULL, &error);
316       if(local_datetime)
317       {
318         _metadata_update_value(i, local_datetime, self);
319         g_free(local_datetime);
320       }
321       else
322       {
323         _metadata_update_value(i, NODATA_STRING, self);
324         fprintf(stderr, "[metadata timestamp] could not convert '%s' to UTF-8: %s\n", datetime, error->message);
325         g_error_free(error);
326       }
327     }
328   }
329   else
330     _metadata_update_value(i, NODATA_STRING, self);
331 }
332 
_lib_metadata_sort_order(gconstpointer a,gconstpointer b)333 static gint _lib_metadata_sort_order(gconstpointer a, gconstpointer b)
334 {
335   dt_lib_metadata_info_t *ma = (dt_lib_metadata_info_t *)a;
336   dt_lib_metadata_info_t *mb = (dt_lib_metadata_info_t *)b;
337   return ma->order - mb->order;
338 }
339 
_lib_metadata_sort_index(gconstpointer a,gconstpointer b)340 static gint _lib_metadata_sort_index(gconstpointer a, gconstpointer b)
341 {
342   dt_lib_metadata_info_t *ma = (dt_lib_metadata_info_t *)a;
343   dt_lib_metadata_info_t *mb = (dt_lib_metadata_info_t *)b;
344   return ma->index - mb->index;
345 }
346 
_metadata_get_flags(const dt_image_t * const img,char * const text,char * const tooltip,const size_t tooltip_size)347 static void _metadata_get_flags(const dt_image_t *const img, char *const text, char *const tooltip, const size_t tooltip_size)
348 {
349 #define EMPTY_FIELD '.'
350 #define FALSE_FIELD '.'
351 #define TRUE_FIELD '!'
352 
353 #define FLAG_NB 15
354 
355   char *flags_tooltip = NULL;
356   char *flag_descriptions[] = { N_("unused"),
357                                 N_("unused/deprecated"),
358                                 N_("ldr"),
359                                 N_("raw"),
360                                 N_("hdr"),
361                                 N_("marked for deletion"),
362                                 N_("auto-applying presets applied"),
363                                 N_("legacy flag. set for all new images"),
364                                 N_("local copy"),
365                                 N_("has .txt"),
366                                 N_("has .wav"),
367                                 N_("monochrome")
368   };
369   char *tooltip_parts[FLAG_NB] = { 0 };
370   int next_tooltip_part = 0;
371 
372   gchar *value = g_strnfill(FLAG_NB, EMPTY_FIELD);
373 
374   int stars = img->flags & 0x7;
375   char *star_string = NULL;
376   if(stars == 6)
377   {
378     value[0] = 'x';
379     tooltip_parts[next_tooltip_part++] = _("image rejected");
380   }
381   else
382   {
383     value[0] = '0' + stars;
384     tooltip_parts[next_tooltip_part++] = star_string = g_strdup_printf(ngettext("image has %d star", "image has %d stars", stars), stars);
385   }
386 
387 
388   if(img->flags & 8)
389   {
390     value[1] = TRUE_FIELD;
391     tooltip_parts[next_tooltip_part++] = _(flag_descriptions[0]);
392   }
393   else
394     value[1] = FALSE_FIELD;
395 
396   if(img->flags & DT_IMAGE_THUMBNAIL_DEPRECATED)
397   {
398     value[2] = TRUE_FIELD;
399     tooltip_parts[next_tooltip_part++] = _(flag_descriptions[1]);
400   }
401   else
402     value[2] = FALSE_FIELD;
403 
404   if(img->flags & DT_IMAGE_LDR)
405   {
406     value[3] = 'l';
407     tooltip_parts[next_tooltip_part++] = _(flag_descriptions[2]);
408   }
409 
410   if(img->flags & DT_IMAGE_RAW)
411   {
412     value[4] = 'r';
413     tooltip_parts[next_tooltip_part++] = _(flag_descriptions[3]);
414   }
415 
416   if(img->flags & DT_IMAGE_HDR)
417   {
418     value[5] = 'h';
419     tooltip_parts[next_tooltip_part++] = _(flag_descriptions[4]);
420   }
421 
422   if(img->flags & DT_IMAGE_REMOVE)
423   {
424     value[6] = 'd';
425     tooltip_parts[next_tooltip_part++] = _(flag_descriptions[5]);
426   }
427 
428   if(img->flags & DT_IMAGE_AUTO_PRESETS_APPLIED)
429   {
430     value[7] = 'a';
431     tooltip_parts[next_tooltip_part++] = _(flag_descriptions[6]);
432   }
433 
434   if(img->flags & DT_IMAGE_NO_LEGACY_PRESETS)
435   {
436     value[8] = 'p';
437     tooltip_parts[next_tooltip_part++] = _(flag_descriptions[7]);
438   }
439 
440   if(img->flags & DT_IMAGE_LOCAL_COPY)
441   {
442     value[9] = 'c';
443     tooltip_parts[next_tooltip_part++] = _(flag_descriptions[8]);
444   }
445 
446   if(img->flags & DT_IMAGE_HAS_TXT)
447   {
448     value[10] = 't';
449     tooltip_parts[next_tooltip_part++] = _(flag_descriptions[9]);
450   }
451 
452   if(img->flags & DT_IMAGE_HAS_WAV)
453   {
454     value[11] = 'w';
455     tooltip_parts[next_tooltip_part++] = _(flag_descriptions[10]);
456   }
457 
458   if(dt_image_monochrome_flags(img))
459   {
460     value[12] = 'm';
461     tooltip_parts[next_tooltip_part++] = _(flag_descriptions[11]);
462   }
463 
464   const int loader = (unsigned int)img->loader < LOADER_COUNT ? img->loader : 0;
465   value[13] = loaders_info[loader].flag;
466   char *loader_tooltip = g_strdup_printf(_("loader: %s"), _(loaders_info[loader].tooltip));
467   tooltip_parts[next_tooltip_part++] = loader_tooltip;
468 
469   value[14] = '\0';
470 
471   flags_tooltip = g_strjoinv("\n", tooltip_parts);
472   g_free(loader_tooltip);
473 
474   (void)g_strlcpy(text, value, FLAG_NB);
475   (void)g_strlcpy(tooltip, flags_tooltip, tooltip_size);
476 
477   g_free(value);
478   g_free(star_string);
479   g_free(flags_tooltip);
480 
481 #undef EMPTY_FIELD
482 #undef FALSE_FIELD
483 #undef TRUE_FIELD
484 #undef FLAG_NB
485 }
486 
487 #ifdef USE_LUA
488 static int lua_update_metadata(lua_State*L);
489 #endif
490 
491 /* update all values to reflect mouse over image id or no data at all */
_metadata_view_update_values(dt_lib_module_t * self)492 static void _metadata_view_update_values(dt_lib_module_t *self)
493 {
494   int32_t mouse_over_id = dt_control_get_mouse_over_id();
495   int32_t count = 0;
496 
497   gchar *images = NULL;
498 
499   if(mouse_over_id == -1)
500   {
501      const dt_view_t *cv = dt_view_manager_get_current_view(darktable.view_manager);
502     if(cv->view(cv) == DT_VIEW_DARKROOM)
503     {
504        mouse_over_id = darktable.develop->image_storage.id;
505     }
506     else
507     {
508       images = dt_view_get_images_to_act_on_query(FALSE);
509       sqlite3_stmt *stmt;
510       gchar *query = dt_util_dstrcat(NULL,
511                                      "SELECT id, COUNT(id) "
512                                      "FROM main.images "
513                                      "WHERE id IN (%s)",
514                                      images);
515       DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), query, -1, &stmt, NULL);
516       if(sqlite3_step(stmt) == SQLITE_ROW)
517       {
518         mouse_over_id = sqlite3_column_int(stmt, 0);
519         count = sqlite3_column_int(stmt, 1);
520       }
521       sqlite3_finalize(stmt);
522       g_free(query);
523 
524       // Still 0 => no selection in progress
525       if(count == 0)
526       {
527         goto fill_minuses;
528       }
529     }
530   }
531   else // over an image
532   {
533     count = 1;
534   }
535 
536   gboolean skip[md_size] = {FALSE};
537 
538   if(count > 1)
539   {
540     if(!images) images = dt_view_get_images_to_act_on_query(FALSE);
541     sqlite3_stmt *stmt = NULL;
542     gchar *query = dt_util_dstrcat(NULL, "SELECT COUNT(DISTINCT film_id), "
543                                          "2, " //id always different
544                                          "COUNT(DISTINCT group_id), "
545                                          "COUNT(DISTINCT filename), "
546                                          "COUNT(DISTINCT version), "
547                                          "COUNT(DISTINCT film_id || '/' || filename), " //path
548                                          "COUNT(DISTINCT flags & 2048), " //local copy
549                                          "COUNT(DISTINCT import_timestamp), "
550                                          "COUNT(DISTINCT change_timestamp), "
551                                          "COUNT(DISTINCT export_timestamp), "
552                                          "COUNT(DISTINCT print_timestamp), "
553                                          "COUNT(DISTINCT flags), "
554                                          "COUNT(DISTINCT model), "
555                                          "COUNT(DISTINCT maker), "
556                                          "COUNT(DISTINCT lens), "
557                                          "COUNT(DISTINCT aperture), "
558                                          "COUNT(DISTINCT exposure), "
559                                          "COUNT(DISTINCT IFNULL(exposure_bias, '')), "
560                                          "COUNT(DISTINCT focal_length), "
561                                          "COUNT(DISTINCT focus_distance), "
562                                          "COUNT(DISTINCT iso), "
563                                          "COUNT(DISTINCT datetime_taken), "
564                                          "COUNT(DISTINCT width), "
565                                          "COUNT(DISTINCT height), "
566                                          "COUNT(DISTINCT IFNULL(output_width, '')), " //exported width
567                                          "COUNT(DISTINCT IFNULL(output_height, '')), " //exported height
568                                          "(SELECT COUNT(DISTINCT IFNULL(value,'')) FROM images LEFT JOIN meta_data ON meta_data.id = images.id AND key = 2 WHERE images.id in (%s)), " //title
569                                          "(SELECT COUNT(DISTINCT IFNULL(value,'')) FROM images LEFT JOIN meta_data ON meta_data.id = images.id AND key = 3 WHERE images.id in (%s)), " //description
570                                          "(SELECT COUNT(DISTINCT IFNULL(value,'')) FROM images LEFT JOIN meta_data ON meta_data.id = images.id AND key = 0 WHERE images.id in (%s)), " //creator
571                                          "(SELECT COUNT(DISTINCT IFNULL(value,'')) FROM images LEFT JOIN meta_data ON meta_data.id = images.id AND key = 1 WHERE images.id in (%s)), " //publisher
572                                          "(SELECT COUNT(DISTINCT IFNULL(value,'')) FROM images LEFT JOIN meta_data ON meta_data.id = images.id AND key = 4 WHERE images.id in (%s)), " //rights
573                                          "(SELECT COUNT(DISTINCT IFNULL(value,'')) FROM images LEFT JOIN meta_data ON meta_data.id = images.id AND key = 5 WHERE images.id in (%s)), " //notes
574                                          "(SELECT COUNT(DISTINCT IFNULL(value,'')) FROM images LEFT JOIN meta_data ON meta_data.id = images.id AND key = 6 WHERE images.id in (%s)), " //version name
575                                          "COUNT(DISTINCT IFNULL(latitude, '')), "
576                                          "COUNT(DISTINCT IFNULL(longitude, '')), "
577                                          "COUNT(DISTINCT IFNULL(altitude, '')) "
578                                          "FROM main.images "
579                                          "WHERE id IN (%s)",
580                                    images, images, images, images, images, images, images, images);
581 
582     DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), query, -1, &stmt, NULL);
583 
584     sqlite3_stmt *stmt_tags = NULL;
585     DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db),
586                                 dt_util_dstrcat(NULL, "SELECT flags, COUNT(DISTINCT imgid) FROM main.tagged_images JOIN data.tags ON data.tags.id = main.tagged_images.tagid AND name NOT LIKE 'darktable|%%' WHERE imgid in (%s) GROUP BY tagid", images),
587                                 -1, &stmt_tags, NULL);
588     g_free(query);
589 
590     if(sqlite3_step(stmt) == SQLITE_ROW)
591     {
592       for(int32_t md = 0; md < md_tag_names; md++)
593       {
594         skip[md] = (sqlite3_column_int(stmt, md) > 1);
595       }
596     }
597     sqlite3_finalize(stmt);
598 
599     // Tags and categories management
600     gboolean same_tags = TRUE;
601     gboolean same_categories = TRUE;
602 
603     while(sqlite3_step(stmt_tags) == SQLITE_ROW)
604     {
605       if(sqlite3_column_int(stmt_tags, 0) & DT_TF_CATEGORY)
606       {
607         same_categories &= (sqlite3_column_int(stmt_tags, 1) == count);
608       }
609       else
610       {
611         same_tags &= (sqlite3_column_int(stmt_tags, 1) == count);
612       }
613     }
614 
615     skip[md_tag_names] = ! same_tags;
616     skip[md_categories] = ! same_categories;
617 
618     sqlite3_finalize(stmt_tags);
619   }
620 
621   g_free(images);
622 
623   int img_id = mouse_over_id;
624   const dt_image_t *img = dt_image_cache_get(darktable.image_cache, img_id, 'r');
625 
626   if(!img) goto fill_minuses;
627 
628   if(img->film_id == -1)
629   {
630     dt_image_cache_read_release(darktable.image_cache, img);
631     goto fill_minuses;
632   }
633 
634   // Update the metadata values
635   for(int32_t md = 0; md < md_size; md++)
636   {
637     if(skip[md] == TRUE)
638     {
639       if(md == md_internal_flags)
640       {
641         _metadata_update_tooltip(md, NULL, self);
642       }
643 
644       if(md == md_internal_filmroll)
645       {
646         _metadata_update_tooltip(md, NULL, self);
647       }
648       _metadata_update_value(md, _("<various values>"), self);
649       _metadata_update_markup(md, "<span style=\"italic\">%s</span>", self);
650       continue;
651     }
652 
653     char text[PATH_MAX] = {0};
654 
655     switch(md)
656     {
657       case md_internal_filmroll:
658       {
659         char tooltip_filmroll[300] = {0};
660         dt_image_film_roll(img, text, sizeof(text));
661         snprintf(tooltip_filmroll, sizeof(tooltip_filmroll), _("double click to jump to film roll\n%s"), text);
662         _metadata_update_tooltip(md_internal_filmroll, tooltip_filmroll, self);
663         _metadata_update_value(md_internal_filmroll, text, self);
664       }
665         break;
666 
667       case md_internal_imgid:
668         (void)g_snprintf(text, sizeof(text), "%d", img->id);
669         _metadata_update_value(md_internal_imgid, text, self);
670         break;
671 
672       case md_internal_groupid:
673         (void)g_snprintf(text, sizeof(text), "%d", img->group_id);
674         _metadata_update_value(md_internal_groupid, text, self);
675         break;
676 
677       case md_internal_filename:
678         _metadata_update_value(md_internal_filename, img->filename, self);
679         break;
680 
681       case md_internal_version:
682         (void)g_snprintf(text, sizeof(text), "%d", img->version);
683         _metadata_update_value(md_internal_version, text, self);
684         break;
685 
686       case md_internal_fullpath:
687       {
688         gboolean from_cache = FALSE;
689         dt_image_full_path(img->id, text, sizeof(text), &from_cache);
690         _metadata_update_value(md_internal_fullpath, text, self);
691       }
692       break;
693 
694       case md_internal_local_copy:
695         (void)g_strlcpy(text, (img->flags & DT_IMAGE_LOCAL_COPY) ? _("yes") : _("no"), sizeof(text));
696         _metadata_update_value(md_internal_local_copy, text, self);
697         break;
698 
699       case md_internal_import_timestamp:
700         if (img->import_timestamp >= 0)
701           _metadata_update_timestamp(md_internal_import_timestamp, &img->import_timestamp, self);
702         else
703           _metadata_update_value(md_internal_import_timestamp, NODATA_STRING, self);
704         break;
705 
706       case md_internal_change_timestamp:
707         if (img->change_timestamp >=0)
708           _metadata_update_timestamp(md_internal_change_timestamp, &img->change_timestamp, self);
709         else
710           _metadata_update_value(md_internal_change_timestamp, NODATA_STRING, self);
711         break;
712 
713       case md_internal_export_timestamp:
714         if (img->export_timestamp >=0)
715           _metadata_update_timestamp(md_internal_export_timestamp, &img->export_timestamp, self);
716         else
717           _metadata_update_value(md_internal_export_timestamp, NODATA_STRING, self);
718         break;
719 
720       case md_internal_print_timestamp:
721         if (img->print_timestamp >=0)
722           _metadata_update_timestamp(md_internal_print_timestamp, &img->print_timestamp, self);
723         else
724           _metadata_update_value(md_internal_print_timestamp, NODATA_STRING, self);
725         break;
726 
727       case md_internal_flags:
728       {
729         char tooltip_flags[300] = {0};
730         _metadata_get_flags(img, text, tooltip_flags, sizeof(tooltip_flags));
731         _metadata_update_tooltip(md_internal_flags, tooltip_flags, self);
732         _metadata_update_value(md_internal_flags, text, self);
733       }
734         break;
735 
736       case md_exif_model:
737         _metadata_update_value(md_exif_model, img->camera_alias, self);
738         break;
739 
740       case md_exif_maker:
741         _metadata_update_value(md_exif_maker, img->camera_maker, self);
742         break;
743 
744       case md_exif_lens:
745         _metadata_update_value(md_exif_lens, img->exif_lens, self);
746         break;
747 
748       case md_exif_aperture:
749         (void)g_snprintf(text, sizeof(text), "f/%.1f", (double)img->exif_aperture);
750         _metadata_update_value(md_exif_aperture, text, self);
751         break;
752 
753       case md_exif_exposure:
754       {
755         gchar *const str = dt_util_format_exposure(img->exif_exposure);
756         _metadata_update_value(md_exif_exposure, str, self);
757         g_free(str);
758       }
759       break;
760 
761       case md_exif_exposure_bias:
762         g_strlcpy(text, NODATA_STRING, sizeof(text));
763         if(!(isnan(img->exif_exposure_bias)))
764         {
765           (void)g_snprintf(text, sizeof(text), _("%+.2f EV"), (double)img->exif_exposure_bias);
766         }
767         _metadata_update_value(md_exif_exposure_bias, text, self);
768         break;
769 
770       case md_exif_focal_length:
771         (void)g_snprintf(text, sizeof(text), "%.0f mm", (double)img->exif_focal_length);
772         _metadata_update_value(md_exif_focal_length, text, self);
773         break;
774 
775       case md_exif_focus_distance:
776         (void)g_strlcpy(text, NODATA_STRING, sizeof(text));
777         if(!(isnan(img->exif_focus_distance) || (fpclassify(img->exif_focus_distance) == FP_ZERO) ))
778         {
779           (void)g_snprintf(text, sizeof(text), _("%.2f m"), (double)img->exif_focus_distance);
780         }
781         _metadata_update_value(md_exif_focus_distance, text, self);
782         break;
783 
784       case md_exif_iso:
785         (void)g_snprintf(text, sizeof(text), "%.0f", (double)img->exif_iso);
786         _metadata_update_value(md_exif_iso, text, self);
787         break;
788 
789       case md_exif_datetime:
790       {
791         struct tm tt_exif = { 0 };
792         if(sscanf(img->exif_datetime_taken, "%d:%d:%d %d:%d:%d", &tt_exif.tm_year, &tt_exif.tm_mon,
793                   &tt_exif.tm_mday, &tt_exif.tm_hour, &tt_exif.tm_min, &tt_exif.tm_sec) == 6)
794         {
795           tt_exif.tm_year -= 1900;
796           tt_exif.tm_mon--;
797           tt_exif.tm_isdst = -1;
798           const time_t exif_timestamp = mktime(&tt_exif);
799           _metadata_update_timestamp(md_exif_datetime, &exif_timestamp, self);
800         }
801         else
802           _metadata_update_value(md_exif_datetime, img->exif_datetime_taken, self);
803       }
804       break;
805 
806       case md_exif_width:
807         if(img->p_width && (img->p_width != img->width))
808         {
809           (void)g_snprintf(text, sizeof(text), "%d (%d)", img->p_width, img->width);
810           _metadata_update_value(md_exif_width, text, self);
811         }
812         else
813         {
814           (void)g_snprintf(text, sizeof(text), "%d", img->width);
815           _metadata_update_value(md_exif_width, text, self);
816         }
817         break;
818 
819       case md_exif_height:
820         if(img->p_height && (img->p_height != img->height))
821         {
822           (void)g_snprintf(text, sizeof(text), "%d (%d)", img->p_height, img->height);
823           _metadata_update_value(md_exif_height, text, self);
824         }
825         else
826         {
827           (void)g_snprintf(text, sizeof(text), "%d", img->height);
828           _metadata_update_value(md_exif_height, text, self);
829         }
830         break;
831 
832       case md_width:
833         (void)g_strlcpy(text, NODATA_STRING, sizeof(text));
834         if(img->final_width > 0)
835         {
836           (void)g_snprintf(text, sizeof(text), "%d", img->final_width);
837         }
838         _metadata_update_value(md_width, text, self);
839         break;
840 
841       case md_height:
842         (void)g_strlcpy(text, NODATA_STRING, sizeof(text));
843         if(img->final_height > 0)
844         {
845           (void)g_snprintf(text, sizeof(text), "%d", img->final_height);
846         }
847         _metadata_update_value(md_height, text, self);
848         break;
849 
850 //      case md_xmp_metadata: //managed below the switch()
851 //          break;
852 
853       case md_geotagging_lat:
854         if(isnan(img->geoloc.latitude))
855         {
856           _metadata_update_value(md_geotagging_lat, NODATA_STRING, self);
857         }
858         else
859         {
860           if(dt_conf_get_bool("plugins/lighttable/metadata_view/pretty_location"))
861           {
862             gchar *latitude = dt_util_latitude_str((float)img->geoloc.latitude);
863             _metadata_update_value(md_geotagging_lat, latitude, self);
864             g_free(latitude);
865           }
866           else
867           {
868             const gchar NS = img->geoloc.latitude < 0 ? 'S' : 'N';
869             (void)g_snprintf(text, sizeof(text), "%c %09.6f", NS, fabs(img->geoloc.latitude));
870             _metadata_update_value(md_geotagging_lat, text, self);
871           }
872         }
873         break;
874 
875       case md_geotagging_lon:
876         if(isnan(img->geoloc.longitude))
877         {
878           _metadata_update_value(md_geotagging_lon, NODATA_STRING, self);
879         }
880         else
881         {
882           if(dt_conf_get_bool("plugins/lighttable/metadata_view/pretty_location"))
883           {
884             gchar *longitude = dt_util_longitude_str((float)img->geoloc.longitude);
885             _metadata_update_value(md_geotagging_lon, longitude, self);
886             g_free(longitude);
887           }
888           else
889           {
890             const gchar EW = img->geoloc.longitude < 0 ? 'W' : 'E';
891             (void)g_snprintf(text, sizeof(text), "%c %010.6f", EW, fabs(img->geoloc.longitude));
892             _metadata_update_value(md_geotagging_lon, text, self);
893           }
894         }
895         break;
896 
897       case md_geotagging_ele:
898         if(isnan(img->geoloc.elevation))
899         {
900           _metadata_update_value(md_geotagging_ele, NODATA_STRING, self);
901         }
902         else
903         {
904           if(dt_conf_get_bool("plugins/lighttable/metadata_view/pretty_location"))
905           {
906             gchar *elevation = dt_util_elevation_str((float)img->geoloc.elevation);
907             _metadata_update_value(md_geotagging_ele, elevation, self);
908             g_free(elevation);
909           }
910           else
911           {
912             (void)g_snprintf(text, sizeof(text), "%.2f %s", img->geoloc.elevation, _("m"));
913             _metadata_update_value(md_geotagging_ele, text, self);
914           }
915         }
916         break;
917 
918       case md_tag_names:
919       case md_categories:
920       {
921         GList *tags = NULL;
922         char *tagstring = NULL;
923         char *categoriesstring = NULL;
924         if(dt_tag_get_attached(img->id, &tags, TRUE))
925         {
926           uint64_t length = 0u;
927           for(GList *taglist = tags; taglist; taglist = g_list_next(taglist))
928           {
929             const char *tagname = ((dt_tag_t *)taglist->data)->leave;
930             if(!(((dt_tag_t *)taglist->data)->flags & DT_TF_CATEGORY))
931             {
932               // tags - just keywords
933               length = length + strlen(tagname) + 2u;
934               if(length < 45u)
935                 tagstring = dt_util_dstrcat(tagstring, "%s, ", tagname);
936               else
937               {
938                 tagstring = dt_util_dstrcat(tagstring, "\n%s, ", tagname);
939                 length = strlen(tagname) + 2u;
940               }
941             }
942             else
943             {
944               // categories - needs parent category to make sense
945               char *category = g_strdup(((dt_tag_t *)taglist->data)->tag);
946               char *catend = g_strrstr(category, "|");
947               if(catend)
948               {
949                 catend[0] = '\0';
950                 char *catstart = g_strrstr(category, "|");
951                 catstart = catstart ? catstart + 1 : category;
952                 categoriesstring = dt_util_dstrcat(categoriesstring, categoriesstring ? "\n%s: %s " : "%s: %s ",
953                                                    catstart, ((dt_tag_t *)taglist->data)->leave);
954               }
955               else
956                 categoriesstring = dt_util_dstrcat(categoriesstring, categoriesstring ? "\n%s" : "%s",
957                                                    ((dt_tag_t *)taglist->data)->leave);
958               g_free(category);
959             }
960           }
961           if(tagstring) tagstring[strlen(tagstring)-2] = '\0';
962         }
963 
964         if (md == md_tag_names)
965           _metadata_update_value(md_tag_names, tagstring ? tagstring : NODATA_STRING, self);
966         else
967           _metadata_update_value(md_categories, categoriesstring ? categoriesstring : NODATA_STRING, self);
968 
969         g_free(tagstring);
970         g_free(categoriesstring);
971         dt_tag_free_result(&tags);
972       }
973       break;
974 
975       default:
976         break;
977     }
978 
979     //cases not handled by switch
980     if(md >= md_xmp_metadata && md < (md_xmp_metadata + DT_METADATA_NUMBER))
981     {
982       g_strlcpy(text, NODATA_STRING, sizeof(text));
983 
984       const uint32_t keyid = dt_metadata_get_keyid_by_display_order((uint32_t)(md - md_xmp_metadata));
985       const gchar *const key = dt_metadata_get_key(keyid);
986       const gboolean hidden = dt_metadata_get_type(keyid) == DT_METADATA_TYPE_INTERNAL;
987       if(! hidden)
988       {
989         GList *res = dt_metadata_get(img->id, key, NULL);
990         if(res)
991         {
992           g_strlcpy(text, (char *)res->data, sizeof(text));
993           _filter_non_printable(text, sizeof(text));
994           g_list_free_full(res, &g_free);
995         }
996       }
997       _metadata_update_value(md, text, self);
998     }
999   }
1000   dt_image_cache_read_release(darktable.image_cache, img);
1001 
1002   if(mouse_over_id >= 0)
1003   {
1004 #ifdef USE_LUA
1005     dt_lua_async_call_alien(lua_update_metadata,
1006                             0,NULL,NULL,
1007                             LUA_ASYNC_TYPENAME,"void*",self,
1008                             LUA_ASYNC_TYPENAME,"int32_t",mouse_over_id,LUA_ASYNC_DONE);
1009 #endif
1010   }
1011 
1012   return;
1013 
1014 /* reset */
1015 fill_minuses:
1016   for(int k = 0; k < md_size; k++) _metadata_update_value(k, NODATA_STRING, self);
1017 #ifdef USE_LUA
1018   dt_lua_async_call_alien(lua_update_metadata,
1019                           0,NULL,NULL,
1020                           LUA_ASYNC_TYPENAME,"void*",self,
1021                           LUA_ASYNC_TYPENAME,"int32_t",-1,LUA_ASYNC_DONE);
1022 #endif
1023 }
1024 
_jump_to()1025 static void _jump_to()
1026 {
1027   int32_t imgid = dt_control_get_mouse_over_id();
1028   if(imgid == -1)
1029   {
1030     sqlite3_stmt *stmt;
1031 
1032     DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), "SELECT imgid FROM main.selected_images", -1, &stmt,
1033                                 NULL);
1034 
1035     if(sqlite3_step(stmt) == SQLITE_ROW) imgid = sqlite3_column_int(stmt, 0);
1036     sqlite3_finalize(stmt);
1037   }
1038   if(imgid != -1)
1039   {
1040     char path[512];
1041     const dt_image_t *img = dt_image_cache_get(darktable.image_cache, imgid, 'r');
1042     dt_image_film_roll_directory(img, path, sizeof(path));
1043     dt_image_cache_read_release(darktable.image_cache, img);
1044     char collect[1024];
1045     snprintf(collect, sizeof(collect), "1:0:0:%s$", path);
1046     dt_collection_deserialize(collect);
1047   }
1048 }
1049 
_filmroll_clicked(GtkWidget * widget,GdkEventButton * event,gpointer null)1050 static gboolean _filmroll_clicked(GtkWidget *widget, GdkEventButton *event, gpointer null)
1051 {
1052   if(event->type != GDK_2BUTTON_PRESS) return FALSE;
1053   _jump_to();
1054   return TRUE;
1055 }
1056 
_jump_to_accel(GtkAccelGroup * accel_group,GObject * acceleratable,guint keyval,GdkModifierType modifier,gpointer data)1057 static gboolean _jump_to_accel(GtkAccelGroup *accel_group, GObject *acceleratable, guint keyval,
1058                                GdkModifierType modifier, gpointer data)
1059 {
1060   _jump_to();
1061   return TRUE;
1062 }
1063 
1064 /* callback for the mouse over image change signal */
_mouse_over_image_callback(gpointer instance,gpointer user_data)1065 static void _mouse_over_image_callback(gpointer instance, gpointer user_data)
1066 {
1067   dt_lib_module_t *self = (dt_lib_module_t *)user_data;
1068   if(dt_control_running()) _metadata_view_update_values(self);
1069 }
1070 
init_key_accels(dt_lib_module_t * self)1071 void init_key_accels(dt_lib_module_t *self)
1072 {
1073   dt_accel_register_lib(self, NC_("accel", "jump to film roll"), GDK_KEY_j, GDK_CONTROL_MASK);
1074 }
1075 
connect_key_accels(dt_lib_module_t * self)1076 void connect_key_accels(dt_lib_module_t *self)
1077 {
1078   GClosure *closure = g_cclosure_new(G_CALLBACK(_jump_to_accel), (gpointer)self, NULL);
1079   dt_accel_connect_lib(self, "jump to film roll", closure);
1080 }
1081 
_get_current_configuration(dt_lib_module_t * self)1082 static char *_get_current_configuration(dt_lib_module_t *self)
1083 {
1084   dt_lib_metadata_view_t *d = (dt_lib_metadata_view_t *)self->data;
1085   char *pref = NULL;
1086 
1087   d->metadata = g_list_sort(d->metadata, _lib_metadata_sort_order);
1088   for(GList *meta = d->metadata; meta; meta= g_list_next(meta))
1089   {
1090     dt_lib_metadata_info_t *m = (dt_lib_metadata_info_t *)meta->data;
1091     if(_is_metadata_ui(m->index))
1092       pref = dt_util_dstrcat(pref, "%s%s,", m->visible ? "" : "|", m->name);
1093   }
1094   if(pref)
1095   {
1096     pref[strlen(pref) - 1] = '\0';
1097   }
1098   return pref;
1099 }
1100 
_lib_metadata_refill_grid(dt_lib_module_t * self)1101 static void _lib_metadata_refill_grid(dt_lib_module_t *self)
1102 {
1103   dt_lib_metadata_view_t *d = (dt_lib_metadata_view_t *)self->data;
1104   d->metadata = g_list_sort(d->metadata, _lib_metadata_sort_order);
1105 
1106   int j = 0;
1107   // initialize the grid with metadata queue content
1108   for(GList *meta = d->metadata; meta; meta = g_list_next(meta))
1109   {
1110     dt_lib_metadata_info_t *m = (dt_lib_metadata_info_t *)meta->data;
1111     m->order = j;
1112     GtkWidget *w_name = gtk_grid_get_child_at(GTK_GRID(d->grid), 0, j);
1113     gtk_label_set_text(GTK_LABEL(w_name), _(m->name));
1114     gtk_widget_set_tooltip_text(w_name, _(m->name));
1115     GtkWidget *w_value = gtk_grid_get_child_at(GTK_GRID(d->grid), 1, j);
1116     gtk_label_set_text(GTK_LABEL(w_value), m->value);
1117     const char *tooltip = m->tooltip ? m->tooltip : m->value;
1118     gtk_widget_set_tooltip_text(w_value, tooltip);
1119 
1120     const int i = m->index;
1121     gtk_label_set_ellipsize(GTK_LABEL(w_value),
1122                             i == md_exif_model || i == md_exif_lens || i == md_exif_maker
1123                             ? PANGO_ELLIPSIZE_END : PANGO_ELLIPSIZE_MIDDLE);
1124     if(i == md_internal_filmroll)
1125     {
1126       // film roll jump to:
1127       if(d->filmroll_event && GTK_IS_WIDGET(d->filmroll_event))
1128         g_signal_handlers_disconnect_by_func(d->filmroll_event, G_CALLBACK(_filmroll_clicked), NULL);
1129       g_signal_connect(G_OBJECT(w_value), "button-press-event", G_CALLBACK(_filmroll_clicked), NULL);
1130       d->filmroll_event = G_OBJECT(w_value);
1131     }
1132 
1133     gtk_widget_set_visible(w_name, m->visible);
1134     gtk_widget_set_visible(w_value, m->visible);
1135     j++;
1136   }
1137 }
1138 
_lib_metadata_setup_grid(dt_lib_module_t * self)1139 static void _lib_metadata_setup_grid(dt_lib_module_t *self)
1140 {
1141   dt_lib_metadata_view_t *d = (dt_lib_metadata_view_t *)self->data;
1142 
1143   int j = 0;
1144   // initialize the grid with metadata queue content
1145   for(GList *meta = d->metadata; meta; meta = g_list_next(meta))
1146   {
1147     dt_lib_metadata_info_t *m = (dt_lib_metadata_info_t *)meta->data;
1148     GtkWidget *w_name = gtk_label_new(_(m->name));
1149     gtk_widget_set_halign(w_name, GTK_ALIGN_START);
1150     gtk_label_set_xalign(GTK_LABEL(w_name), 0.0f);
1151     gtk_label_set_ellipsize(GTK_LABEL(w_name), PANGO_ELLIPSIZE_END);
1152     gtk_widget_set_tooltip_text(w_name, _(m->name));
1153 
1154     GtkWidget *w_value= gtk_label_new(m->value);
1155     gtk_widget_set_name(w_value, "brightbg");
1156     gtk_label_set_selectable(GTK_LABEL(w_value), TRUE);
1157     gtk_widget_set_halign(w_value, GTK_ALIGN_FILL);
1158     gtk_label_set_xalign(GTK_LABEL(w_value), 0.0f);
1159 
1160     gtk_grid_attach(GTK_GRID(d->grid), w_name, 0, j, 1, 1);
1161     gtk_grid_attach(GTK_GRID(d->grid), w_value, 1, j, 1, 1);
1162     j++;
1163   }
1164 }
1165 
_apply_preferences(const char * prefs_list,dt_lib_module_t * self)1166 static void _apply_preferences(const char *prefs_list, dt_lib_module_t *self)
1167 {
1168   if(!prefs_list || !prefs_list[0]) return;
1169   dt_lib_metadata_view_t *d = (dt_lib_metadata_view_t *)self->data;
1170 
1171   GList *prefs = dt_util_str_to_glist(",", prefs_list);
1172   int k = 0;
1173   for(GList *pref = prefs; pref; pref = g_list_next(pref))
1174   {
1175     const char *name = (char *)pref->data;
1176     gboolean visible = TRUE;
1177     if(name)
1178     {
1179       if(name[0] == '|')
1180       {
1181         name++;
1182         visible = FALSE;
1183       }
1184       for(GList *meta = d->metadata; meta; meta= g_list_next(meta))
1185       {
1186         dt_lib_metadata_info_t *m = (dt_lib_metadata_info_t *)meta->data;
1187         if(name && !g_strcmp0(name, m->name))
1188         {
1189           m->order = k;
1190           m->visible = visible;
1191           break;
1192         }
1193       }
1194     }
1195     else continue;
1196     k++;
1197   }
1198   g_list_free_full(prefs, g_free);
1199 
1200   _lib_metadata_refill_grid(self);
1201 }
1202 
_save_preferences(dt_lib_module_t * self)1203 static void _save_preferences(dt_lib_module_t *self)
1204 {
1205   char *pref = _get_current_configuration(self);
1206   dt_conf_set_string("plugins/lighttable/metadata_view/visible", pref);
1207   g_free(pref);
1208 }
1209 
_select_toggled_callback(GtkCellRendererToggle * cell_renderer,gchar * path_str,gpointer user_data)1210 static void _select_toggled_callback(GtkCellRendererToggle *cell_renderer, gchar *path_str, gpointer user_data)
1211 {
1212   GtkListStore *store = (GtkListStore *)user_data;
1213   GtkTreeIter iter;
1214   GtkTreePath *path = gtk_tree_path_new_from_string(path_str);
1215   gboolean selected;
1216 
1217   gtk_tree_model_get_iter(GTK_TREE_MODEL(store), &iter, path);
1218   gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, DT_METADATA_PREF_COL_VISIBLE, &selected, -1);
1219   gtk_list_store_set(store, &iter, DT_METADATA_PREF_COL_VISIBLE, !selected, -1);
1220 
1221   gtk_tree_path_free(path);
1222 }
1223 
_drag_data_inserted(GtkTreeModel * tree_model,GtkTreePath * path,GtkTreeIter * iter,gpointer user_data)1224 static void _drag_data_inserted(GtkTreeModel *tree_model, GtkTreePath *path, GtkTreeIter *iter, gpointer user_data)
1225 {
1226   _dndactive = TRUE;
1227 }
1228 
_menuitem_preferences(GtkMenuItem * menuitem,dt_lib_module_t * self)1229 void _menuitem_preferences(GtkMenuItem *menuitem, dt_lib_module_t *self)
1230 {
1231   dt_lib_metadata_view_t *d = (dt_lib_metadata_view_t *)self->data;
1232 
1233   GtkWidget *win = dt_ui_main_window(darktable.gui->ui);
1234   GtkWidget *dialog = gtk_dialog_new_with_buttons(_("metadata settings"), GTK_WINDOW(win),
1235                                        GTK_DIALOG_DESTROY_WITH_PARENT, _("default"), GTK_RESPONSE_ACCEPT,
1236                                        _("cancel"), GTK_RESPONSE_NONE, _("save"), GTK_RESPONSE_YES, NULL);
1237   GtkWidget *area = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
1238 
1239   GtkWidget *w = gtk_scrolled_window_new(NULL, NULL);
1240   gtk_widget_set_size_request(w, -1, DT_PIXEL_APPLY_DPI(600));
1241   gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w), GTK_POLICY_NEVER, GTK_POLICY_ALWAYS);
1242   gtk_scrolled_window_set_overlay_scrolling(GTK_SCROLLED_WINDOW(w), FALSE);
1243   gtk_box_pack_start(GTK_BOX(area), w, TRUE, TRUE, 0);
1244 
1245   GtkListStore *store = gtk_list_store_new(DT_METADATA_PREF_NUM_COLS,
1246                                            G_TYPE_INT, G_TYPE_STRING, G_TYPE_BOOLEAN);
1247   GtkTreeModel *model = GTK_TREE_MODEL(store);
1248 
1249   GtkTreeIter iter;
1250   d->metadata = g_list_sort(d->metadata, _lib_metadata_sort_order);
1251   for(GList *meta = d->metadata; meta; meta= g_list_next(meta))
1252   {
1253     dt_lib_metadata_info_t *m = (dt_lib_metadata_info_t *)meta->data;
1254     if(!_is_metadata_ui(m->index))
1255       continue;
1256     gtk_list_store_append(store, &iter);
1257     gtk_list_store_set(store, &iter,
1258                        DT_METADATA_PREF_COL_INDEX, m->index,
1259                        DT_METADATA_PREF_COL_NAME_L, _(m->name),
1260                        DT_METADATA_PREF_COL_VISIBLE, m->visible,
1261                        -1);
1262   }
1263 
1264   GtkWidget *view = gtk_tree_view_new_with_model(model);
1265   g_object_unref(model);
1266   GtkCellRenderer *renderer = gtk_cell_renderer_text_new();
1267   GtkTreeViewColumn *column = gtk_tree_view_column_new_with_attributes(_("metadata"), renderer,
1268                                                     "text", DT_METADATA_PREF_COL_NAME_L, NULL);
1269   gtk_tree_view_column_set_expand(column, TRUE);
1270   gtk_tree_view_append_column(GTK_TREE_VIEW(view), column);
1271   GtkWidget *header = gtk_tree_view_column_get_button(column);
1272   gtk_widget_set_tooltip_text(header,
1273                 _("drag and drop one row at a time until you get the desired order"
1274                 "\nuntick to hide metadata which are not of interest for you"
1275                 "\nif different settings are needed, use presets"));
1276   renderer = gtk_cell_renderer_toggle_new();
1277   g_signal_connect(renderer, "toggled", G_CALLBACK(_select_toggled_callback), store);
1278   column = gtk_tree_view_column_new_with_attributes(_("visible"), renderer,
1279                                                     "active", DT_METADATA_PREF_COL_VISIBLE, NULL);
1280   gtk_tree_view_append_column(GTK_TREE_VIEW(view), column);
1281 
1282   // drag & drop
1283   gtk_tree_view_set_reorderable(GTK_TREE_VIEW(view), TRUE);
1284   g_signal_connect(G_OBJECT(model), "row-inserted", G_CALLBACK(_drag_data_inserted), NULL);
1285 
1286   gtk_container_add(GTK_CONTAINER(w), view);
1287 
1288 #ifdef GDK_WINDOWING_QUARTZ
1289   dt_osx_disallow_fullscreen(dialog);
1290 #endif
1291   gtk_widget_show_all(dialog);
1292 
1293   int res = gtk_dialog_run(GTK_DIALOG(dialog));
1294   while(res == GTK_RESPONSE_ACCEPT)
1295   {
1296     gtk_tree_model_get_iter_first(model, &iter);
1297     d->metadata = g_list_sort(d->metadata, _lib_metadata_sort_index);
1298     for(GList *meta = d->metadata; meta; meta= g_list_next(meta))
1299     {
1300       dt_lib_metadata_info_t *m = (dt_lib_metadata_info_t *)meta->data;
1301       if(!_is_metadata_ui(m->index))
1302         continue;
1303       gtk_list_store_set(store, &iter,
1304                          DT_METADATA_PREF_COL_INDEX, m->index,
1305                          DT_METADATA_PREF_COL_NAME_L, _(m->name),
1306                          DT_METADATA_PREF_COL_VISIBLE, TRUE,
1307                          -1);
1308       gtk_tree_model_iter_next(model, &iter);
1309     }
1310     res = gtk_dialog_run(GTK_DIALOG(dialog));
1311   }
1312 
1313   int i = 0;
1314   if(res == GTK_RESPONSE_YES)
1315   {
1316     gboolean valid = gtk_tree_model_get_iter_first(model, &iter);
1317     while(valid)
1318     {
1319       gboolean visible;
1320       uint32_t index;
1321       gtk_tree_model_get(model, &iter,
1322                          DT_METADATA_PREF_COL_INDEX, &index,
1323                          DT_METADATA_PREF_COL_VISIBLE, &visible,
1324                          -1);
1325       for(GList *meta = d->metadata; meta; meta= g_list_next(meta))
1326       {
1327         dt_lib_metadata_info_t *m = (dt_lib_metadata_info_t *)meta->data;
1328         if(m->index == index)
1329         {
1330           m->order = i;
1331           m->visible = visible;
1332           break;
1333         }
1334       }
1335       i++;
1336       valid = gtk_tree_model_iter_next(model, &iter);
1337     }
1338 
1339     _lib_metadata_refill_grid(self);
1340     _save_preferences(self);
1341   }
1342   gtk_widget_destroy(dialog);
1343 }
1344 
set_preferences(void * menu,dt_lib_module_t * self)1345 void set_preferences(void *menu, dt_lib_module_t *self)
1346 {
1347   GtkWidget *mi = gtk_menu_item_new_with_label(_("preferences..."));
1348   g_signal_connect(G_OBJECT(mi), "activate", G_CALLBACK(_menuitem_preferences), self);
1349   gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi);
1350 }
1351 
init_presets(dt_lib_module_t * self)1352 void init_presets(dt_lib_module_t *self)
1353 {
1354 }
1355 
get_params(dt_lib_module_t * self,int * size)1356 void *get_params(dt_lib_module_t *self, int *size)
1357 {
1358   *size = 0;
1359   char *params = _get_current_configuration(self);
1360   if(params)
1361     *size =strlen(params) + 1;
1362   return params;
1363 }
1364 
set_params(dt_lib_module_t * self,const void * params,int size)1365 int set_params(dt_lib_module_t *self, const void *params, int size)
1366 {
1367   if(!params) return 1;
1368 
1369   _apply_preferences(params, self);
1370   _save_preferences(self);
1371   return 0;
1372 }
1373 
_display_default(dt_lib_module_t * self)1374 static void _display_default(dt_lib_module_t *self)
1375 {
1376   dt_lib_metadata_view_t *d = (dt_lib_metadata_view_t *)self->data;
1377 
1378   for(GList *meta = d->metadata; meta; meta= g_list_next(meta))
1379   {
1380     dt_lib_metadata_info_t *m = (dt_lib_metadata_info_t *)meta->data;
1381     m->order = m->index;
1382     m->visible = _is_metadata_ui(m->index);
1383   }
1384   _lib_metadata_refill_grid(self);
1385 }
1386 
gui_init(dt_lib_module_t * self)1387 void gui_init(dt_lib_module_t *self)
1388 {
1389   /* initialize ui */
1390   dt_lib_metadata_view_t *d = (dt_lib_metadata_view_t *)g_malloc0(sizeof(dt_lib_metadata_view_t));
1391   self->data = (void *)d;
1392 
1393   _lib_metadata_init_queue(self);
1394 
1395   GtkWidget *child_grid_window = gtk_grid_new();
1396   d->grid = child_grid_window;
1397   gtk_grid_set_column_spacing(GTK_GRID(child_grid_window), DT_PIXEL_APPLY_DPI(5));
1398 
1399   self->widget = dt_ui_scroll_wrap(child_grid_window, 200, "plugins/lighttable/metadata_view/windowheight");
1400   dt_gui_add_help_link(self->widget, dt_get_help_url(self->plugin_name));
1401 
1402   gtk_widget_show_all(d->grid);
1403   gtk_widget_set_no_show_all(d->grid, TRUE);
1404   _lib_metadata_setup_grid(self);
1405   char *pref = dt_conf_get_string("plugins/lighttable/metadata_view/visible");
1406   if(!strlen(pref))
1407     _display_default(self);
1408   _apply_preferences(pref, self);
1409   g_free(pref);
1410 
1411   /* lets signup for mouse over image change signals */
1412   DT_DEBUG_CONTROL_SIGNAL_CONNECT(darktable.signals, DT_SIGNAL_MOUSE_OVER_IMAGE_CHANGE,
1413                             G_CALLBACK(_mouse_over_image_callback), self);
1414 
1415   DT_DEBUG_CONTROL_SIGNAL_CONNECT(darktable.signals, DT_SIGNAL_SELECTION_CHANGED,
1416                                   G_CALLBACK(_mouse_over_image_callback), self);
1417 
1418   /* lets signup for develop image changed signals */
1419   DT_DEBUG_CONTROL_SIGNAL_CONNECT(darktable.signals, DT_SIGNAL_DEVELOP_IMAGE_CHANGED,
1420                             G_CALLBACK(_mouse_over_image_callback), self);
1421 
1422   /* signup for develop initialize to update info of current
1423      image in darkroom when enter */
1424   DT_DEBUG_CONTROL_SIGNAL_CONNECT(darktable.signals, DT_SIGNAL_DEVELOP_INITIALIZE,
1425                             G_CALLBACK(_mouse_over_image_callback), self);
1426 
1427   /* signup for tags changes */
1428   DT_DEBUG_CONTROL_SIGNAL_CONNECT(darktable.signals, DT_SIGNAL_TAG_CHANGED,
1429                             G_CALLBACK(_mouse_over_image_callback), self);
1430 
1431   /* signup for metadata changes */
1432   DT_DEBUG_CONTROL_SIGNAL_CONNECT(darktable.signals, DT_SIGNAL_METADATA_UPDATE,
1433                             G_CALLBACK(_mouse_over_image_callback), self);
1434 }
1435 
_free_metadata_queue(dt_lib_metadata_info_t * m)1436 static void _free_metadata_queue(dt_lib_metadata_info_t *m)
1437 {
1438   if(m->value) g_free(m->value);
1439   if(m->tooltip) g_free(m->tooltip);
1440   g_free(m);
1441 }
1442 
gui_cleanup(dt_lib_module_t * self)1443 void gui_cleanup(dt_lib_module_t *self)
1444 {
1445   DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(darktable.signals, G_CALLBACK(_mouse_over_image_callback), self);
1446   dt_lib_metadata_view_t *d = (dt_lib_metadata_view_t *)self->data;
1447   g_list_free_full(d->metadata,  (GDestroyNotify)_free_metadata_queue);
1448   g_free(self->data);
1449   self->data = NULL;
1450 }
1451 
gui_reset(dt_lib_module_t * self)1452 void gui_reset(dt_lib_module_t *self)
1453 {
1454   dt_lib_metadata_view_t *d = (dt_lib_metadata_view_t *)self->data;
1455 
1456   for(GList *meta = d->metadata; meta; meta= g_list_next(meta))
1457   {
1458     dt_lib_metadata_info_t *m = (dt_lib_metadata_info_t *)meta->data;
1459     m->order = m->index;
1460     m->visible = _is_metadata_ui(m->index);
1461   }
1462   _lib_metadata_refill_grid(self);
1463   _save_preferences(self);
1464 }
1465 
1466 #ifdef USE_LUA
lua_update_values(lua_State * L)1467 static int lua_update_values(lua_State *L)
1468 {
1469   dt_lib_module_t *self = lua_touserdata(L, 1);
1470   dt_lua_module_entry_push(L, "lib", self->plugin_name);
1471   lua_getuservalue(L, 2);
1472   lua_getfield(L, 3, "values");
1473   lua_getfield(L, 3, "indexes");
1474   lua_pushnil(L);
1475   while(lua_next(L, 4) != 0)
1476   {
1477     lua_getfield(L, 5, lua_tostring(L,-2));
1478     int index = lua_tointeger(L, -1);
1479     _metadata_update_value(index, luaL_checkstring(L, 7), self);
1480     lua_pop(L, 2);
1481   }
1482   return 0;
1483 }
lua_update_metadata(lua_State * L)1484 static int lua_update_metadata(lua_State *L)
1485 {
1486   dt_lib_module_t *self = lua_touserdata(L, 1);
1487   int32_t imgid = lua_tointeger(L, 2);
1488   gboolean have_updates = false;
1489   dt_lua_module_entry_push(L, "lib", self->plugin_name);
1490   lua_getuservalue(L, -1);
1491   lua_getfield(L, 4, "callbacks");
1492   lua_getfield(L, 4, "values");
1493   lua_pushnil(L);
1494   while(lua_next(L, 5) != 0)
1495   {
1496     have_updates = true;
1497     if(imgid > 0)
1498     {
1499       lua_pushvalue(L, -1);
1500       luaA_push(L, dt_lua_image_t, &imgid);
1501       lua_call(L, 1, 1);
1502     }
1503     else
1504     {
1505       lua_pushstring(L, "-");
1506     }
1507     lua_pushvalue(L, 7);
1508     lua_pushvalue(L, 9);
1509     lua_settable(L, 6);
1510     lua_pop(L, 2);
1511   }
1512   if(have_updates)
1513   {
1514     lua_pushcfunction(L, lua_update_values);
1515     dt_lua_gtk_wrap(L);
1516     lua_pushlightuserdata(L, self);
1517     lua_call(L, 1, 0);
1518   }
1519   return 0;
1520 }
1521 
lua_register_info(lua_State * L)1522 static int lua_register_info(lua_State *L)
1523 {
1524   dt_lib_module_t *self = lua_touserdata(L, lua_upvalueindex(1));
1525   dt_lua_module_entry_push(L, "lib", self->plugin_name);
1526   lua_getuservalue(L, -1);
1527   const char* key = luaL_checkstring(L, 1);
1528   luaL_checktype(L, 2, LUA_TFUNCTION);
1529   {
1530     lua_getfield(L, -1, "callbacks");
1531     lua_pushstring(L, key);
1532     lua_pushvalue(L, 2);
1533     lua_settable(L, 5);
1534     lua_pop(L, 1);
1535   }
1536   {
1537     lua_getfield(L, -1, "values");
1538     lua_pushstring(L, key);
1539     lua_pushstring(L, NODATA_STRING);
1540     lua_settable(L, 5);
1541     lua_pop(L, 1);
1542   }
1543   {
1544     dt_lib_metadata_view_t *d = (dt_lib_metadata_view_t *)self->data;
1545     dt_lib_metadata_info_t *m = g_malloc0(sizeof(dt_lib_metadata_info_t));
1546     m->name = (char *)key;
1547     m->value = g_strdup(NODATA_STRING);
1548     const int index = g_list_length(d->metadata);
1549     m->index = m->order = index;
1550     m->visible = TRUE;
1551 
1552     GtkWidget *w_name = gtk_label_new(_(m->name));
1553     gtk_widget_set_halign(w_name, GTK_ALIGN_START);
1554     gtk_label_set_xalign(GTK_LABEL(w_name), 0.0f);
1555     gtk_label_set_ellipsize(GTK_LABEL(w_name), PANGO_ELLIPSIZE_END);
1556     gtk_widget_set_tooltip_text(w_name, _(m->name));
1557 
1558     gboolean validated = g_utf8_validate(m->value, -1, NULL);
1559     const gchar *str = validated ? m->value : NODATA_STRING;
1560 
1561     GtkWidget *w_value= gtk_label_new(str);
1562     gtk_widget_set_name(w_value, "brightbg");
1563     gtk_label_set_selectable(GTK_LABEL(w_value), TRUE);
1564     gtk_widget_set_halign(w_value, GTK_ALIGN_FILL);
1565     gtk_label_set_xalign(GTK_LABEL(w_value), 0.0f);
1566     gtk_label_set_ellipsize(GTK_LABEL(w_value), PANGO_ELLIPSIZE_MIDDLE);
1567     gtk_grid_attach(GTK_GRID(d->grid), w_name, 0, index, 1, 1);
1568     gtk_grid_attach(GTK_GRID(d->grid), w_value, 1, index, 1, 1);
1569 
1570     d->metadata = g_list_append(d->metadata, m);
1571 
1572     {
1573       lua_getfield(L, -1, "indexes");
1574       lua_pushstring(L, key);
1575       lua_pushinteger(L, index);
1576       lua_settable(L, 5);
1577       lua_pop(L, 1);
1578     }
1579     // apply again preferences because it's already done
1580     char *pref = dt_conf_get_string("plugins/lighttable/metadata_view/visible");
1581     _apply_preferences(pref, self);
1582     g_free(pref);
1583   }
1584   return 0;
1585 }
1586 
lua_destroy_info(lua_State * L)1587 static int lua_destroy_info(lua_State *L)
1588 {
1589   dt_lib_module_t *self = lua_touserdata(L, lua_upvalueindex(1));
1590   dt_lua_module_entry_push(L, "lib", self->plugin_name);
1591   lua_getuservalue(L, -1);
1592   const char* key = luaL_checkstring(L, 1);
1593   {
1594     lua_getfield(L, -1, "callbacks");
1595     lua_pushstring(L, key);
1596     lua_pushnil(L);
1597     lua_settable(L, 4);
1598     lua_pop(L, 1);
1599   }
1600   {
1601     lua_getfield(L, -1, "values");
1602     lua_pushstring(L, key);
1603     lua_pushnil(L);
1604     lua_settable(L, 4);
1605     lua_pop(L, 1);
1606   }
1607   lua_getfield(L, -1, "indexes");
1608   lua_getfield(L, -1, key);
1609   const int index = lua_tointeger(L, -1);
1610   lua_pop(L, 1);
1611   {
1612     lua_pushstring(L, key);
1613     lua_pushnil(L);
1614     lua_settable(L, 4);
1615   }
1616   // decrement all indexes > index
1617   lua_pushnil(L);
1618   while(lua_next(L, -2) != 0) {
1619     int i = lua_tointeger(L, -1);
1620     if(i > index)
1621     {
1622       lua_pop(L, 1);
1623       lua_pushvalue(L, -1);
1624       i--;
1625       lua_pushinteger(L, i);
1626       lua_settable(L, -4);
1627     }
1628     else lua_pop(L, 1);
1629   }
1630 
1631   {
1632     dt_lib_metadata_view_t *d = (dt_lib_metadata_view_t *)self->data;
1633     // find metadata key in the list and remove it
1634     GList *tbr = NULL;
1635     for(GList *meta = d->metadata; meta; meta = g_list_next(meta))
1636     {
1637       dt_lib_metadata_info_t *m = (dt_lib_metadata_info_t *)meta->data;
1638       if(!g_strcmp0(key, m->name))
1639       {
1640         tbr = meta;
1641       }
1642       else if(m->index > index)
1643       {
1644         m->index--;
1645       }
1646     }
1647     if(tbr)
1648     {
1649       dt_lib_metadata_info_t *m = (dt_lib_metadata_info_t *)tbr->data;
1650       d->metadata = g_list_remove_link(d->metadata, tbr);
1651       g_free(m->value);
1652       if(m->tooltip) g_free(m->tooltip);
1653       g_free(m);
1654       g_list_free(tbr);
1655       gtk_grid_remove_row(GTK_GRID(d->grid), 0);
1656       _lib_metadata_refill_grid(self);
1657     }
1658   }
1659   return 0;
1660 }
1661 
init(struct dt_lib_module_t * self)1662 void init(struct dt_lib_module_t *self)
1663 {
1664   lua_State *L = darktable.lua_state.state;
1665   int my_type = dt_lua_module_entry_get_type(L, "lib", self->plugin_name);
1666   lua_pushlightuserdata(L, self);
1667   lua_pushcclosure(L, lua_register_info, 1);
1668   dt_lua_gtk_wrap(L);
1669   lua_pushcclosure(L, dt_lua_type_member_common, 1);
1670   dt_lua_type_register_const_type(L, my_type, "register_info");
1671 
1672   lua_pushlightuserdata(L, self);
1673   lua_pushcclosure(L, lua_destroy_info, 1);
1674   dt_lua_gtk_wrap(L);
1675   lua_pushcclosure(L, dt_lua_type_member_common, 1);
1676   dt_lua_type_register_const_type(L, my_type, "destroy_info");
1677 
1678   dt_lua_module_entry_push(L,"lib",self->plugin_name);
1679   lua_getuservalue(L, -1);
1680   lua_newtable(L);
1681   lua_setfield(L, -2, "callbacks");
1682   lua_newtable(L);
1683   lua_setfield(L, -2, "values");
1684   lua_newtable(L);
1685   lua_setfield(L, -2, "indexes");
1686   lua_pop(L, 2);
1687 }
1688 #endif
1689 // modelines: These editor modelines have been set for all relevant files by tools/update_modelines.sh
1690 // vim: shiftwidth=2 expandtab tabstop=2 cindent
1691 // kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
1692