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