Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
libs/metadata.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2010-2017 Tobias Ellinghaus.
4 Copyright (C) 2011 Alexandre Prokoudine.
5 Copyright (C) 2011 Antony Dovgal.
6 Copyright (C) 2011 Brian Teague.
7 Copyright (C) 2011, 2013 Henrik Andersson.
8 Copyright (C) 2011, 2013 johannes hanika.
9 Copyright (C) 2011 Jérémy Rosen.
10 Copyright (C) 2011 Robert Bieber.
11 Copyright (C) 2012 Richard Wonka.
12 Copyright (C) 2013 José Carlos García Sogo.
13 Copyright (C) 2013-2016 Roman Lebedev.
14 Copyright (C) 2013 Thomas Pryds.
15 Copyright (C) 2014, 2019-2021 Pascal Obry.
16 Copyright (C) 2018, 2020 Heiko Bauke.
17 Copyright (C) 2018 Maurizio Paglia.
18 Copyright (C) 2018 rawfiner.
19 Copyright (C) 2019, 2022-2023, 2025-2026 Aurélien PIERRE.
20 Copyright (C) 2019-2022 Philippe Weyland.
21 Copyright (C) 2019 Sam Smith.
22 Copyright (C) 2020-2021 Aldric Renaudin.
23 Copyright (C) 2020-2021 Chris Elston.
24 Copyright (C) 2020-2022 Diederik Ter Rahe.
25 Copyright (C) 2020-2021 Hubert Kowalski.
26 Copyright (C) 2020 Marco.
27 Copyright (C) 2020 parafin.
28 Copyright (C) 2020-2021 Ralf Brown.
29 Copyright (C) 2021 luzpaz.
30 Copyright (C) 2021 Vincent THOMAS.
31 Copyright (C) 2022 Martin Bařinka.
32
33 darktable is free software: you can redistribute it and/or modify
34 it under the terms of the GNU General Public License as published by
35 the Free Software Foundation, either version 3 of the License, or
36 (at your option) any later version.
37
38 darktable is distributed in the hope that it will be useful,
39 but WITHOUT ANY WARRANTY; without even the implied warranty of
40 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
41 GNU General Public License for more details.
42
43 You should have received a copy of the GNU General Public License
44 along with darktable. If not, see <http://www.gnu.org/licenses/>.
45*/
46
47#include "common/metadata.h"
48#include "common/collection.h"
49#include "common/selection.h"
50#include "common/darktable.h"
51#include "gui/gdkkeys.h"
52#include "common/debug.h"
53#include "control/conf.h"
54#include "control/control.h"
55
56static sqlite3_stmt *_metadata_update_stmt = NULL;
57#include "control/signal.h"
58#include "dtgtk/button.h"
59
60#include "gui/gtk.h"
61#include "libs/lib.h"
62#include "libs/lib_api.h"
63#ifdef GDK_WINDOWING_QUARTZ
64#include "osx/osx.h"
65#endif
66#include <gdk/gdkkeysyms.h>
67
68DT_MODULE(3)
69
78
87
88const char *name(struct dt_lib_module_t *self)
89{
90 return _("Metadata");
91}
92
93const char **views(dt_lib_module_t *self)
94{
95 static const char *v[] = {"lighttable", "tethering", NULL};
96 return v;
97}
98
100{
102}
103
104void _textbuffer_changed(GtkTextBuffer *textbuffer, dt_lib_module_t *self);
105
106static gboolean _is_leave_unchanged(GtkTextView *textview)
107{
108 return GPOINTER_TO_INT(g_object_get_data(G_OBJECT(textview), "tv_multiple"));
109}
110
111static gchar *_get_buffer_text(GtkTextView *textview)
112{
113 GtkTextBuffer *buffer = gtk_text_view_get_buffer(textview);
114 GtkTextIter start, end;
115 gtk_text_buffer_get_bounds(buffer, &start, &end);
116 return gtk_text_buffer_get_text(buffer, &start, &end, TRUE);
117}
118
119static void _text_set_all_selected(GtkTextView *textview, const gboolean selected)
120{
121 GtkTextBuffer *buffer = gtk_text_view_get_buffer(textview);
122 GtkTextIter start, end;
123 gtk_text_buffer_get_bounds(buffer, &start, &end);
124 gtk_text_buffer_select_range(buffer, selected ? &start : &end, &end);
125}
126
127static void _text_set_italic(GtkTextView *textview, const gboolean italic)
128{
129 GtkTextBuffer *buffer = gtk_text_view_get_buffer(textview);
130 GtkTextIter start, end;
131 gtk_text_buffer_get_bounds(buffer, &start, &end);
132 if(italic)
133 gtk_text_buffer_apply_tag_by_name(buffer, "italic", &start, &end);
134 else
135 gtk_text_buffer_remove_tag_by_name(buffer, "italic", &start, &end);
136}
137
138static void _set_text_buffer(GtkTextBuffer *buffer, const char *text)
139{
140 g_signal_handlers_block_matched(buffer, G_SIGNAL_MATCH_FUNC, 0, 0, NULL, _textbuffer_changed, NULL);
141 gtk_text_buffer_set_text(buffer, text, -1);
142 g_signal_handlers_unblock_matched(buffer, G_SIGNAL_MATCH_FUNC, 0, 0, NULL, _textbuffer_changed, NULL);
143}
144
145static void _fill_text_view(const uint32_t i, const uint32_t count, dt_lib_module_t *self)
146{
148 gboolean multi = FALSE;
149
150 GtkTextBuffer *buffer = gtk_text_view_get_buffer(d->textview[i]);
151 if(count == 0) // no metadata value
152 {
153 _set_text_buffer(buffer, "");
154 }
155 else if(count == 1) // images with different metadata values
156 {
157 _set_text_buffer(buffer, _("<leave unchanged>"));
158 multi = TRUE;
159 }
160 else // one or several images with the same metadata value
161 {
162 _set_text_buffer(buffer, (char *)d->metadata_list[i]->data);
163 }
164 g_object_set_data(G_OBJECT(d->textview[i]), "tv_multiple", GINT_TO_POINTER(multi));
165 _text_set_italic(d->textview[i], multi);
166}
167
168static void _update(dt_lib_module_t *self)
169{
172
173 GList *imgs = dt_act_on_get_images();
174
175 // first we want to make sure the list of images to act on has changed
176 // this is not the case if mouse hover change but still stay in selection for ex.
177 if(IS_NULL_PTR(imgs) && IS_NULL_PTR(d->last_act_on)) return;
178 if(imgs && d->last_act_on)
179 {
180 gboolean changed = FALSE;
181 GList *l = d->last_act_on;
182 GList *ll = (GList *)imgs;
183 while(l && ll)
184 {
185 if(GPOINTER_TO_INT(l->data) != GPOINTER_TO_INT(ll->data))
186 {
187 changed = TRUE;
188 break;
189 }
190 l = g_list_next(l);
191 ll = g_list_next(ll);
192 }
193 if(!changed)
194 {
195 g_list_free(imgs);
196 imgs = NULL;
197 return;
198 }
199 }
200 g_list_free(d->last_act_on);
201 d->last_act_on = NULL;
202 d->last_act_on = imgs;
203
204 GList *metadata[DT_METADATA_NUMBER];
205 uint32_t metadata_count[DT_METADATA_NUMBER];
206
207 for(unsigned int i = 0; i < DT_METADATA_NUMBER; i++)
208 {
209 metadata[i] = NULL;
210 metadata_count[i] = 0;
211 }
212
213 // using dt_metadata_get() is not possible here. we want to do all this in a single pass, everything else
214 // takes ages.
215 const uint32_t imgs_count = g_list_length((GList *)imgs);
216
217 if(imgs_count > 0)
218 {
220 {
221 // clang-format off
223 "SELECT m.key, m.value, COUNT(m.id) AS ct"
224 " FROM main.meta_data AS m"
225 " JOIN main.selected_images AS s ON s.imgid = m.id"
226 " GROUP BY m.key, m.value ORDER BY m.value",
227 -1, &_metadata_update_stmt, NULL);
228 // clang-format on
229 }
230 sqlite3_stmt *stmt = _metadata_update_stmt;
231 sqlite3_reset(stmt);
232 sqlite3_clear_bindings(stmt);
233
234 while(sqlite3_step(stmt) == SQLITE_ROW)
235 {
236 if(sqlite3_column_bytes(stmt, 1))
237 {
238 const uint32_t key = (uint32_t)sqlite3_column_int(stmt, 0);
240 continue;
241 char *value = g_strdup((char *)sqlite3_column_text(stmt, 1));
242 const uint32_t count = (uint32_t)sqlite3_column_int(stmt, 2);
243 metadata_count[key] = (count == imgs_count) ? 2 : 1; // if = all images have the same metadata
244 metadata[key] = g_list_append(metadata[key], value);
245 }
246 }
247 }
248
249 for(unsigned int i = 0; i < DT_METADATA_NUMBER; i++)
250 {
251 const uint32_t keyid = dt_metadata_get_keyid_by_display_order(i);
253 continue;
254 g_list_free_full(d->metadata_list[i], dt_free_gpointer);
255 d->metadata_list[i] = metadata[keyid];
256 metadata[keyid] = NULL;
257 _fill_text_view(i, metadata_count[keyid], self);
258 }
259
260 for(unsigned int key = 0; key < DT_METADATA_NUMBER; key++)
261 {
262 g_list_free_full(metadata[key], dt_free_gpointer);
263 metadata[key] = NULL;
264 }
265
266 gtk_widget_set_sensitive(GTK_WIDGET(d->apply_button), imgs_count > 0);
267}
268
269static void _image_selection_changed_callback(gpointer instance, dt_lib_module_t *self)
270{
271 _update(self);
272}
273
274static void _append_kv(GList **l, const gchar *key, const gchar *value)
275{
276 *l = g_list_append(*l, (gchar *)key);
277 *l = g_list_append(*l, (gchar *)value);
278}
279
280static void _metadata_set_list(const int i, GList **key_value, dt_lib_metadata_t *d)
281{
282 const uint32_t keyid = dt_metadata_get_keyid_by_display_order(i);
284 return;
285 gchar *metadata = _get_buffer_text(GTK_TEXT_VIEW(d->textview[i]));
286 if(metadata && !_is_leave_unchanged(GTK_TEXT_VIEW(d->textview[i])))
287 _append_kv(key_value, dt_metadata_get_key(keyid), metadata);
288}
289
290static void _write_metadata(GtkTextView *textview, dt_lib_module_t *self)
291{
293
294 GList *key_value = NULL;
295 if(textview)
296 {
297 const int i = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(textview), "tv_index"));
298 _metadata_set_list(i, &key_value, d);
299 }
300 else
301 {
302 for(unsigned int i = 0; i < DT_METADATA_NUMBER; i++)
303 _metadata_set_list(i, &key_value, d);
304 }
305
307
308 dt_metadata_set_list(imgs, key_value, TRUE);
309
310 for(GList *l = key_value; l; l = l->next)
311 {
312 l = l->next;
313 dt_free(l->data); // metadata value
314 }
315 g_list_free(key_value);
316 key_value = NULL;
317
319
321 g_list_free(imgs);
322 imgs = NULL;
323 _update(self);
324 d->editing = FALSE;
325}
326
327static void _apply_button_clicked(GtkButton *button, dt_lib_module_t *self)
328{
329 _write_metadata(NULL, self);
330}
331
332static gboolean _key_pressed(GtkWidget *textview, GdkEventKey *event, dt_lib_module_t *self)
333{
335
336 guint key = dt_keys_mainpad_alternatives(event->keyval);
337
338 if(dt_modifier_is(event->state, GDK_CONTROL_MASK))
339 {
340 switch(key)
341 {
342 case GDK_KEY_Return:
343 // insert new line
344 event->state &= ~GDK_CONTROL_MASK; //TODO: on Mac, remap Ctrl to Cmd key
345 d->editing = TRUE;
346 break;
347 default:
348 break;
349 }
350 }
351 else
352 {
353 switch(key)
354 {
355 case GDK_KEY_Return:
356 _write_metadata(GTK_TEXT_VIEW(textview), self);
357 _text_set_all_selected(GTK_TEXT_VIEW(textview), FALSE);
358 return TRUE;
359 break;
360 case GDK_KEY_Tab:
361 case GDK_KEY_ISO_Left_Tab:
362 _write_metadata(GTK_TEXT_VIEW(textview), self);
363 break;
364 case GDK_KEY_Escape:
365 {
366 if(dt_modifier_is(event->state, 0))
367 {
368 _update(self);
369 gtk_window_set_focus(GTK_WINDOW(dt_ui_main_window(darktable.gui->ui)), NULL);
370 d->editing = FALSE;
371 return TRUE;
372 }
373 break;
374 }
375 default:
376 break;
377 }
378 }
379
380 return gtk_text_view_im_context_filter_keypress(GTK_TEXT_VIEW(textview), event);
381}
382
383void _textbuffer_changed(GtkTextBuffer *textbuffer, dt_lib_module_t *self)
384{
386 d->editing = TRUE;
387 GtkTextView *textview = GINT_TO_POINTER(g_object_get_data(G_OBJECT(textbuffer), "buffer_tv"));
388 g_object_set_data(G_OBJECT(textview), "tv_multiple", GINT_TO_POINTER(FALSE));
389}
390
391gboolean _textview_focus(GtkWidget *widget, GtkDirectionType d, gpointer user_data)
392{
393 GtkWidget *target = g_object_get_data(G_OBJECT(widget), d == GTK_DIR_TAB_FORWARD ? "meta_next" : "meta_prev");
394 gtk_widget_grab_focus(target);
395 return TRUE;
396}
397
398static gboolean _got_focus(GtkWidget *textview, dt_lib_module_t *self)
399{
401 if(!d->editing)
402 {
403 if(_is_leave_unchanged(GTK_TEXT_VIEW(textview)))
404 {
405 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview));
406 _set_text_buffer(buffer, "");
407 _text_set_italic(GTK_TEXT_VIEW(textview), FALSE);
408 }
409 _text_set_all_selected(GTK_TEXT_VIEW(textview), TRUE);
410 }
411 return TRUE;
412}
413
414static gboolean _lost_focus(GtkWidget *textview, GdkEventFocus *event, dt_lib_module_t *self)
415{
417 d->editing = FALSE;
418 if(_is_leave_unchanged(GTK_TEXT_VIEW(textview)))
419 {
420 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview));
421 _set_text_buffer(buffer, _("<leave unchanged>"));
422 _text_set_italic(GTK_TEXT_VIEW(textview), TRUE);
423 }
424 return FALSE;
425}
426
428{
429 return 4;
430}
431
433{
435
436 GtkWidget *first = NULL, *previous = NULL;
437 for(unsigned int i = 0; i < DT_METADATA_NUMBER; i++)
438 {
440 continue;
443 gchar *setting = g_strdup_printf("plugins/lighttable/metadata/%s_flag", name);
444 const gboolean hidden = type == DT_METADATA_TYPE_INTERNAL ||
446 dt_free(setting);
447
448 GtkWidget *label = gtk_grid_get_child_at(GTK_GRID(self->widget), 0, i);
449 gtk_widget_set_visible(label, !hidden);
450 GtkWidget *current = GTK_WIDGET(d->textview[i]);
451 gtk_widget_set_visible(gtk_widget_get_parent(current), !hidden);
452
453 if(!hidden)
454 {
455 if(IS_NULL_PTR(first)) first = previous = current;
456
457 g_object_set_data(G_OBJECT(previous), "meta_next", current);
458 g_object_set_data(G_OBJECT(current), "meta_prev", previous);
459
460 g_object_set_data(G_OBJECT(current), "meta_next", first);
461 g_object_set_data(G_OBJECT(first), "meta_prev", current);
462
463 previous = current;
464 }
465 }
466}
467
469{
471 for(unsigned int i = 0; i < DT_METADATA_NUMBER; i++)
472 {
474 gchar *setting = g_strdup_printf("plugins/lighttable/metadata/%s_flag", name);
475 const gboolean hidden = dt_conf_get_int(setting) & DT_METADATA_FLAG_HIDDEN;
476 dt_free(setting);
478 // we don't want to lose hidden information
479 if(!hidden && type != DT_METADATA_TYPE_INTERNAL)
480 {
481 GtkTextBuffer *buffer = gtk_text_view_get_buffer(d->textview[i]);
482 _set_text_buffer(buffer, "");
483 _text_set_italic(d->textview[i], FALSE);
484 }
485 }
486 _write_metadata(NULL, self);
487}
488
489static void _toggled_callback(gchar *path_str, gpointer user_data, const int column)
490{
491 GtkListStore *store = (GtkListStore *)user_data;
492 GtkTreeIter iter;
493 GtkTreePath *path = gtk_tree_path_new_from_string(path_str);
494 gboolean toggle;
495
496 gtk_tree_model_get_iter(GTK_TREE_MODEL(store), &iter, path);
497 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, column, &toggle, -1);
498 gtk_list_store_set(store, &iter, column, !toggle, -1);
499
500 gtk_tree_path_free(path);
501}
502
503static void _visible_toggled_callback(GtkCellRendererToggle *cell_renderer, gchar *path_str, gpointer user_data)
504{
506}
507
508static void _private_toggled_callback(GtkCellRendererToggle *cell_renderer, gchar *path_str, gpointer user_data)
509{
511}
512
513void _menuitem_preferences(GtkMenuItem *menuitem, dt_lib_module_t *self)
514{
516 GtkWidget *dialog = gtk_dialog_new_with_buttons(_("metadata settings"), GTK_WINDOW(win),
517 GTK_DIALOG_DESTROY_WITH_PARENT, _("default"), GTK_RESPONSE_YES,
518 _("cancel"), GTK_RESPONSE_NONE, _("save"), GTK_RESPONSE_ACCEPT, NULL);
519 g_signal_connect(dialog, "key-press-event", G_CALLBACK(dt_handle_dialog_enter), NULL);
520 GtkWidget *area = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
521
522 GtkWidget *w = gtk_scrolled_window_new(NULL, NULL);
523 gtk_widget_set_size_request(w, -1, DT_PIXEL_APPLY_DPI(100));
524 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w), GTK_POLICY_NEVER, GTK_POLICY_NEVER);
525 dt_gui_add_class(w, "dt_recessed_scroll");
526 gtk_box_pack_start(GTK_BOX(area), w, TRUE, TRUE, 0);
527
528 GtkListStore *store = gtk_list_store_new(DT_METADATA_PREF_NUM_COLS,
529 G_TYPE_INT, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_BOOLEAN);
530 GtkTreeModel *model = GTK_TREE_MODEL(store);
531 GtkTreeIter iter;
532
534 gboolean visible[DT_METADATA_NUMBER];
535 gboolean private[DT_METADATA_NUMBER];
536 for(unsigned int i = 0; i < DT_METADATA_NUMBER; i++)
537 {
540 {
542 gchar *setting = g_strdup_printf("plugins/lighttable/metadata/%s_flag", name[i]);
543 const uint32_t flag = dt_conf_get_int(setting);
544 dt_free(setting);
545 visible[i] = !(flag & DT_METADATA_FLAG_HIDDEN);
546 private[i] = flag & DT_METADATA_FLAG_PRIVATE;
547 gtk_list_store_append(store, &iter);
548 gtk_list_store_set(store, &iter,
553 -1);
554 }
555 }
556
557 GtkWidget *view = gtk_tree_view_new_with_model(model);
558 g_object_unref(model);
559 GtkCellRenderer *renderer = gtk_cell_renderer_text_new();
560 GtkTreeViewColumn *column = gtk_tree_view_column_new_with_attributes(_("metadata"), renderer,
561 "text", DT_METADATA_PREF_COL_NAME, NULL);
562 gtk_tree_view_column_set_expand(column, TRUE);
563 gtk_tree_view_append_column(GTK_TREE_VIEW(view), column);
564 renderer = gtk_cell_renderer_toggle_new();
565 g_signal_connect(renderer, "toggled", G_CALLBACK(_visible_toggled_callback), store);
566 column = gtk_tree_view_column_new_with_attributes(_("visible"), renderer,
567 "active", DT_METADATA_PREF_COL_VISIBLE, NULL);
568 gtk_tree_view_append_column(GTK_TREE_VIEW(view), column);
569 GtkWidget *header = gtk_tree_view_column_get_button(column);
570 gtk_widget_set_tooltip_text(header,
571 _("tick if the corresponding metadata is of interest for you"
572 "\nit will be visible from metadata editor, collection and import module"
573 "\nit will be also exported"));
574 renderer = gtk_cell_renderer_toggle_new();
575 g_signal_connect(renderer, "toggled", G_CALLBACK(_private_toggled_callback), store);
576 column = gtk_tree_view_column_new_with_attributes(_("private"), renderer,
577 "active", DT_METADATA_PREF_COL_PRIVATE, NULL);
578 gtk_tree_view_append_column(GTK_TREE_VIEW(view), column);
579 header = gtk_tree_view_column_get_button(column);
580 gtk_widget_set_tooltip_text(header,
581 _("tick if you want to keep this information private (not exported with images)"));
582
583 gtk_container_add(GTK_CONTAINER(w), view);
584
585#ifdef GDK_WINDOWING_QUARTZ
587#endif
588 gtk_widget_show_all(dialog);
589
590 int res = gtk_dialog_run(GTK_DIALOG(dialog));
591 while(res == GTK_RESPONSE_YES)
592 {
593 gtk_tree_model_get_iter_first(model, &iter);
594 for(unsigned int i = 0; i < DT_METADATA_NUMBER; i++)
595 {
596 // mimic dt_metadata_init() without saving at this stage
599 {
600 gtk_list_store_set(store, &iter,
603 -1);
604 gtk_tree_model_iter_next(model, &iter);
605 }
606 }
607 res = gtk_dialog_run(GTK_DIALOG(dialog));
608 }
609
610 if(res == GTK_RESPONSE_ACCEPT)
611 {
612 gboolean meta_signal = FALSE;
613 gboolean meta_remove = FALSE;
614 gboolean valid = gtk_tree_model_get_iter_first(model, &iter);
615 while(valid)
616 {
617 gboolean new_visible;
618 gboolean new_private;
619 uint32_t i;
620 gtk_tree_model_get(model, &iter,
622 DT_METADATA_PREF_COL_VISIBLE, &new_visible,
623 DT_METADATA_PREF_COL_PRIVATE, &new_private,
624 -1);
626 {
627 gchar *setting = g_strdup_printf("plugins/lighttable/metadata/%s_flag", name[i]);
628 uint32_t flag = dt_conf_get_int(setting);
629 if(new_visible != visible[i])
630 {
631 flag = !new_visible ? flag | DT_METADATA_FLAG_HIDDEN : flag & ~DT_METADATA_FLAG_HIDDEN;
632 meta_signal = TRUE;
633 meta_remove = !new_visible ? TRUE : meta_remove;
634 }
635 if(new_private != private[i])
636 {
637 flag = new_private ? flag | DT_METADATA_FLAG_PRIVATE : flag & ~DT_METADATA_FLAG_PRIVATE;
638 }
639 dt_conf_set_int(setting, flag);
640 dt_free(setting);
641 }
642 valid = gtk_tree_model_iter_next(model, &iter);
643 }
644 if(meta_signal)
647 }
648 _update_layout(self);
649 gtk_widget_destroy(dialog);
650}
651
652void set_preferences(void *menu, dt_lib_module_t *self)
653{
654 GtkWidget *mi = gtk_menu_item_new_with_label(_("preferences..."));
655 g_signal_connect(G_OBJECT(mi), "activate", G_CALLBACK(_menuitem_preferences), self);
656 gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi);
657}
658
659void _menu_line_activated(GtkMenuItem *menuitem, GtkTextView *textview)
660{
661 GtkTextBuffer *buffer = gtk_text_view_get_buffer(textview);
662 gtk_text_buffer_set_text(buffer, gtk_label_get_text(GTK_LABEL(gtk_bin_get_child(GTK_BIN(menuitem)))), -1);
663}
664
665static void _populate_popup_multi(GtkTextView *textview, GtkWidget *popup, dt_lib_module_t *self)
666{
667 const dt_lib_metadata_t *d = (dt_lib_metadata_t *)self->data;
668
669 // get grid line number
670 const int i = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(textview), "tv_index"));
671
672 if (!d->metadata_list[i] || !_is_leave_unchanged(GTK_TEXT_VIEW(textview))) return;
673
674 gtk_menu_shell_append(GTK_MENU_SHELL(popup),gtk_separator_menu_item_new());
675
676 for(GList *item = d->metadata_list[i]; item; item = g_list_next(item))
677 {
678 GtkWidget *new_line = gtk_menu_item_new_with_label(item->data);
679 g_signal_connect(G_OBJECT(new_line), "activate", G_CALLBACK(_menu_line_activated), textview);
680 gtk_menu_shell_append(GTK_MENU_SHELL(popup), new_line);
681 }
682 gtk_widget_show_all(popup);
683}
684
685static gboolean _metadata_reset(GtkWidget *label, GdkEventButton *event, GtkWidget *widget)
686{
687 if(event->type == GDK_2BUTTON_PRESS)
688 {
689 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(widget));
690 _set_text_buffer(buffer, "");
691
692 GdkEventKey e = {0};
693 e.type = GDK_KEY_PRESS;
694 e.keyval = GDK_KEY_Return;
695 e.send_event = TRUE;
696 e.window = gtk_text_view_get_window(GTK_TEXT_VIEW(widget), GTK_TEXT_WINDOW_TEXT);
697 gboolean ret_val;
698 g_signal_emit_by_name(G_OBJECT(widget), "key-press-event", &e, &ret_val);
699 }
700 return FALSE;
701}
702
704{
706 self->data = (void *)d;
707
708 self->timeout_handle = 0;
709
710 GtkGrid *grid = GTK_GRID(gtk_grid_new());
711 self->widget = GTK_WIDGET(grid);
712 gtk_grid_set_row_spacing(grid, DT_GUI_BOX_SPACING);
713 gtk_grid_set_column_spacing(grid, DT_GUI_BOX_SPACING);
714
715 for(int i = 0; i < DT_METADATA_NUMBER; i++)
716 {
718 continue;
720 GtkWidget *labelev = gtk_event_box_new();
721 gtk_widget_add_events(labelev, GDK_BUTTON_PRESS_MASK);
722 gtk_container_add(GTK_CONTAINER(labelev), label);
723 gtk_grid_attach(grid, labelev, 0, i, 1, 1);
724 gtk_widget_set_tooltip_text(GTK_WIDGET(label),
725 _("metadata text. ctrl-wheel scroll to resize the text box"
726 "\n ctrl-enter inserts a new line (caution, may not be compatible with standard metadata)."
727 "\nif <leave unchanged> selected images have different metadata."
728 "\nin that case, right-click gives the possibility to choose one of them."
729 "\npress escape to exit the popup window"));
730
731 GtkWidget *textview = gtk_text_view_new();
733 dt_gui_textview_set_padding(GTK_TEXT_VIEW(textview));
734
735 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview));
736 g_object_set_data(G_OBJECT(buffer), "buffer_tv", GINT_TO_POINTER(textview));
737 g_object_set_data(G_OBJECT(textview), "tv_index", GINT_TO_POINTER(i));
738 g_object_set_data(G_OBJECT(textview), "tv_multiple", GINT_TO_POINTER(FALSE));
739 gtk_text_buffer_create_tag(gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview)),
740 "italic", "style", PANGO_STYLE_ITALIC, NULL);
741
742 gtk_grid_attach(grid, textview, 1, i, 1, 1);
743 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(textview), GTK_WRAP_WORD_CHAR);
744 gtk_text_view_set_accepts_tab(GTK_TEXT_VIEW(textview), FALSE);
745 gtk_widget_add_events(textview, GDK_FOCUS_CHANGE_MASK);
746
747 g_signal_connect(textview, "key-press-event", G_CALLBACK(_key_pressed), self);
748 g_signal_connect(textview, "focus", G_CALLBACK(_textview_focus), self);
749 g_signal_connect(textview, "populate-popup", G_CALLBACK(_populate_popup_multi), self);
750 g_signal_connect(textview, "grab-focus", G_CALLBACK(_got_focus), self);
751 g_signal_connect(textview, "focus-out-event", G_CALLBACK(_lost_focus), self);
752 g_signal_connect(labelev, "button-press-event", G_CALLBACK(_metadata_reset), textview);
753 g_signal_connect(buffer, "changed", G_CALLBACK(_textbuffer_changed), self);
754
755 d->textview[i] = GTK_TEXT_VIEW(textview);
756 gtk_widget_set_hexpand(textview, TRUE);
757 gtk_widget_set_vexpand(textview, TRUE);
758 }
759
760 // apply button
761 d->apply_button = dt_action_button_new(self, N_("apply"), _apply_button_clicked, self,
762 _("write metadata for selected images"), 0, 0);
763
764 gtk_grid_attach(GTK_GRID(self->widget), GTK_WIDGET(d->apply_button), 0, DT_METADATA_NUMBER, 2, 1);
765
766 // and 2 other interesting signals:
768 G_CALLBACK(_image_selection_changed_callback), self);
769
770 gtk_widget_show_all(self->widget);
771 gtk_widget_set_no_show_all(self->widget, TRUE);
772
773 _update(self);
774 _update_layout(self);
775}
776
778{
779 if(IS_NULL_PTR(self->data)) return;
783
784 for(unsigned int i = 0; i < DT_METADATA_NUMBER; i++)
785 {
787 continue;
788 g_signal_handlers_block_by_func(d->textview[i], _lost_focus, self);
789 g_list_free_full(d->metadata_list[i], dt_free_gpointer);
790 d->metadata_list[i] = NULL;
791 }
793 {
794 sqlite3_finalize(_metadata_update_stmt);
796 }
797
798 g_list_free(d->last_act_on);
799 d->last_act_on = NULL;
800
801 dt_free(self->data);
802}
803
804static void add_rights_preset(dt_lib_module_t *self, char *name, char *string)
805{
806 // to be adjusted the nb of metadata items changes
807 const unsigned int metadata_nb = dt_metadata_get_nb_user_metadata();
808 const unsigned int params_size = strlen(string) + metadata_nb;
809
810 char *params = calloc(sizeof(char), params_size);
811 memcpy(params + 4, string, params_size - metadata_nb);
812 dt_lib_presets_add(name, self->plugin_name, self->version(), params, params_size, TRUE);
813 dt_free(params);
814}
815
817{
818 // <title>\0<description>\0<rights>\0<creator>\0<publisher>
819
820 add_rights_preset(self, _("CC BY"), _("Creative Commons Attribution (CC BY)"));
821 add_rights_preset(self, _("CC BY-SA"), _("Creative Commons Attribution-ShareAlike (CC BY-SA)"));
822 add_rights_preset(self, _("CC BY-ND"), _("Creative Commons Attribution-NoDerivs (CC BY-ND)"));
823 add_rights_preset(self, _("CC BY-NC"), _("Creative Commons Attribution-NonCommercial (CC BY-NC)"));
824 add_rights_preset(self, _("CC BY-NC-SA"),
825 _("Creative Commons Attribution-NonCommercial-ShareAlike (CC BY-NC-SA)"));
826 add_rights_preset(self, _("CC BY-NC-ND"),
827 _("Creative Commons Attribution-NonCommercial-NoDerivs (CC BY-NC-ND)"));
828 add_rights_preset(self, _("all rights reserved"), _("all rights reserved"));
829}
830
831void *legacy_params(dt_lib_module_t *self, const void *const old_params, const size_t old_params_size,
832 const int old_version, int *new_version, size_t *new_size)
833{
834 if(old_version == 1)
835 {
836 const size_t new_params_size = old_params_size + 1;
837 char *new_params = calloc(sizeof(char), new_params_size);
838
839 const char *buf = (const char *)old_params;
840
841 // <title>\0<description>\0<rights>\0<creator>\0<publisher>
842 const char *metadata[DT_METADATA_NUMBER];
843 size_t metadata_len[DT_METADATA_NUMBER];
844 for(unsigned int i = 0; i < DT_METADATA_NUMBER; i++)
845 {
846 metadata[i] = buf;
847 if(!metadata[i])
848 {
849 dt_free(new_params);
850 return NULL;
851 }
852 metadata_len[i] = strlen(metadata[i]) + 1;
853 buf += metadata_len[i];
854 }
855
856 // <creator>\0<publisher>\0<title>\0<description>\0<rights>
857 size_t pos = 0;
858 memcpy(new_params + pos, metadata[3], metadata_len[3]);
859 pos += metadata_len[3];
860 memcpy(new_params + pos, metadata[4], metadata_len[4]);
861 pos += metadata_len[4];
862 memcpy(new_params + pos, metadata[0], metadata_len[0]);
863 pos += metadata_len[0];
864 memcpy(new_params + pos, metadata[1], metadata_len[1]);
865 pos += metadata_len[1];
866 memcpy(new_params + pos, metadata[2], metadata_len[2]);
867
868 *new_size = new_params_size;
869 *new_version = 2;
870 return new_params;
871 }
872 else if(old_version == 2)
873 {
874 const size_t new_params_size = old_params_size + 1;
875 char *new_params = calloc(sizeof(char), new_params_size);
876
877 memcpy(new_params, old_params, old_params_size);
878
879 *new_size = new_params_size;
880 *new_version = 3;
881 return new_params;
882 }
883 return NULL;
884}
885
887{
889
890 *size = 0;
891 char *metadata[DT_METADATA_NUMBER];
892 int32_t metadata_len[DT_METADATA_NUMBER];
893
894 for(unsigned int i = 0; i < DT_METADATA_NUMBER; i++)
895 {
897 continue;
898 const uint32_t keyid = dt_metadata_get_keyid_by_display_order(i);
899 GtkTextBuffer *buffer = gtk_text_view_get_buffer((GtkTextView *)d->textview[i]);
900 GtkTextIter start, end;
901 gtk_text_buffer_get_bounds(buffer, &start, &end);
902 metadata[keyid] = gtk_text_buffer_get_text(buffer, &start, &end, TRUE);
903 if(!metadata[keyid]) metadata[keyid] = g_strdup("");
904 metadata_len[keyid] = strlen(metadata[keyid]) + 1;
905 *size = *size + metadata_len[keyid];
906 }
907
908 char *params = (char *)malloc(*size);
909
910 int pos = 0;
911
912 for(unsigned int i = 0; i < DT_METADATA_NUMBER; i++)
913 {
915 continue;
916 memcpy(params + pos, metadata[i], metadata_len[i]);
917 pos += metadata_len[i];
918 dt_free(metadata[i]);
919 }
920
921 g_assert(pos == *size);
922
923 return params;
924}
925
926// WARNING: also change src/libs/import.c when changing this!
927int set_params(dt_lib_module_t *self, const void *params, int size)
928{
929 if(IS_NULL_PTR(params)) return 1;
931
932 char *buf = (char *)params;
933 char *metadata[DT_METADATA_NUMBER];
934 uint32_t metadata_len[DT_METADATA_NUMBER];
935 uint32_t total_len = 0;
936 for(unsigned int i = 0; i < DT_METADATA_NUMBER; i++)
937 {
939 continue;
940 metadata[i] = buf;
941 if(IS_NULL_PTR(metadata[i])) return 1;
942 metadata_len[i] = strlen(metadata[i]) + 1;
943 buf += metadata_len[i];
944 total_len += metadata_len[i];
945 }
946
947 if(size != total_len)
948 return 1;
949
950 GList *key_value = NULL;
951
952 for(unsigned int i = 0; i < DT_METADATA_NUMBER; i++)
953 {
955 continue;
956 if(metadata[i][0] != '\0') _append_kv(&key_value, dt_metadata_get_key(i), metadata[i]);
957 }
958
959 GList *imgs = dt_act_on_get_images();
960 dt_metadata_set_list(imgs, key_value, TRUE);
961
962 g_list_free(key_value);
963 key_value = NULL;
964
966 g_list_free(imgs);
967 imgs = NULL;
968 // force the ui refresh to update the info from preset
969 g_list_free(d->last_act_on);
970 d->last_act_on = NULL;
971 _update(self);
972 return 0;
973}
974// clang-format off
975// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
976// vim: shiftwidth=2 expandtab tabstop=2 cindent
977// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
978// clang-format on
GList * dt_act_on_get_images()
Definition act_on.c:39
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
size_t params_size(dt_imageio_module_format_t *self)
Definition avif.c:565
void dt_image_synch_xmps(const GList *img)
char * key
const char * dt_metadata_get_key(const uint32_t keyid)
const char * dt_metadata_get_name_by_display_order(const uint32_t order)
void dt_metadata_set_list(const GList *imgs, GList *key_value, const gboolean undo_on)
int type
unsigned int dt_metadata_get_nb_user_metadata()
int dt_metadata_get_type_by_display_order(const uint32_t order)
int dt_metadata_get_type(const uint32_t keyid)
dt_metadata_t dt_metadata_get_keyid_by_display_order(const uint32_t order)
char * name
void dt_conf_set_int(const char *name, int val)
int dt_conf_get_int(const char *name)
uint32_t view(const dt_view_t *self)
Definition darkroom.c:227
darktable_t darktable
Definition darktable.c:181
#define DT_MODULE(MODVER)
Definition darktable.h:140
static void dt_free_gpointer(gpointer ptr)
Definition darktable.h:463
#define dt_free(ptr)
Definition darktable.h:456
static const dt_aligned_pixel_simd_t value
Definition darktable.h:577
static gboolean dt_modifier_is(const GdkModifierType state, const GdkModifierType desired_modifier_mask)
Definition darktable.h:893
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition darktable.h:281
sqlite3 * dt_database_get(const dt_database_t *db)
Definition database.c:3646
#define DT_DEBUG_SQLITE3_PREPARE_V2(a, b, c, d, e)
Definition debug.h:107
int store(dt_imageio_module_storage_t *self, dt_imageio_module_data_t *sdata, const int32_t imgid, dt_imageio_module_format_t *format, dt_imageio_module_data_t *fdata, const int num, const int total, const gboolean high_quality, const gboolean export_masks, dt_colorspaces_color_profile_type_t icc_type, const gchar *icc_filename, dt_iop_color_intent_t icc_intent, dt_export_metadata_t *metadata)
Definition disk.c:252
static guint dt_keys_mainpad_alternatives(const guint key_val)
Remap keypad keys to usual mainpad ones.
Definition gdkkeys.h:113
void dt_gui_textview_set_padding(GtkTextView *textview)
Apply the standard recessed-input text padding to a GtkTextView.
Definition gtk.c:2687
void dt_accels_disconnect_on_text_input(GtkWidget *widget)
Disconnects accels when a text or search entry gets the focus, and reconnects them when it looses it....
Definition gtk.c:3225
void dt_gui_add_class(GtkWidget *widget, const gchar *class_name)
Definition gtk.c:133
GtkWidget * dt_ui_main_window(dt_ui_t *ui)
get the main window widget
#define DT_GUI_BOX_SPACING
Definition gtk.h:109
#define DT_PIXEL_APPLY_DPI(value)
Definition gtk.h:90
static GtkWidget * dt_ui_label_new(const gchar *str)
Definition gtk.h:461
const char flag
Definition image.h:252
const char * model
const float v
gboolean dt_handle_dialog_enter(GtkWidget *widget, GdkEventKey *event, gpointer data)
Definition lib.c:1551
void dt_lib_cancel_postponed_update(dt_lib_module_t *mod)
Definition lib.c:1536
GtkWidget * dt_action_button_new(dt_lib_module_t *self, const gchar *label, gpointer callback, gpointer data, const gchar *tooltip, guint accel_key, GdkModifierType mods)
Definition lib.c:1563
void dt_lib_presets_add(const char *name, const char *plugin_name, const int32_t version, const void *params, const int32_t params_size, gboolean readonly)
Definition lib.c:1353
void gui_reset(dt_lib_module_t *self)
void _menuitem_preferences(GtkMenuItem *menuitem, dt_lib_module_t *self)
int set_params(dt_lib_module_t *self, const void *params, int size)
static void _update(dt_lib_module_t *self)
void * get_params(dt_lib_module_t *self, int *size)
static void add_rights_preset(dt_lib_module_t *self, char *name, char *string)
void set_preferences(void *menu, dt_lib_module_t *self)
gboolean _textview_focus(GtkWidget *widget, GtkDirectionType d, gpointer user_data)
static gboolean _metadata_reset(GtkWidget *label, GdkEventButton *event, GtkWidget *widget)
static void _toggled_callback(gchar *path_str, gpointer user_data, const int column)
static gchar * _get_buffer_text(GtkTextView *textview)
static gboolean _key_pressed(GtkWidget *textview, GdkEventKey *event, dt_lib_module_t *self)
static void _write_metadata(GtkTextView *textview, dt_lib_module_t *self)
static void _apply_button_clicked(GtkButton *button, dt_lib_module_t *self)
static void _update_layout(dt_lib_module_t *self)
void gui_cleanup(dt_lib_module_t *self)
dt_metadata_pref_cols_t
@ DT_METADATA_PREF_COL_INDEX
@ DT_METADATA_PREF_NUM_COLS
@ DT_METADATA_PREF_COL_VISIBLE
@ DT_METADATA_PREF_COL_PRIVATE
@ DT_METADATA_PREF_COL_NAME
void _textbuffer_changed(GtkTextBuffer *textbuffer, dt_lib_module_t *self)
static gboolean _is_leave_unchanged(GtkTextView *textview)
static void _set_text_buffer(GtkTextBuffer *buffer, const char *text)
static void _text_set_all_selected(GtkTextView *textview, const gboolean selected)
void _menu_line_activated(GtkMenuItem *menuitem, GtkTextView *textview)
static void _populate_popup_multi(GtkTextView *textview, GtkWidget *popup, dt_lib_module_t *self)
static void _text_set_italic(GtkTextView *textview, const gboolean italic)
static void _metadata_set_list(const int i, GList **key_value, dt_lib_metadata_t *d)
void init_presets(dt_lib_module_t *self)
static gboolean _lost_focus(GtkWidget *textview, GdkEventFocus *event, dt_lib_module_t *self)
static void _private_toggled_callback(GtkCellRendererToggle *cell_renderer, gchar *path_str, gpointer user_data)
uint32_t container(dt_lib_module_t *self)
static sqlite3_stmt * _metadata_update_stmt
void gui_init(dt_lib_module_t *self)
int position()
const char ** views(dt_lib_module_t *self)
static void _image_selection_changed_callback(gpointer instance, dt_lib_module_t *self)
void * legacy_params(dt_lib_module_t *self, const void *const old_params, const size_t old_params_size, const int old_version, int *new_version, size_t *new_size)
static void _visible_toggled_callback(GtkCellRendererToggle *cell_renderer, gchar *path_str, gpointer user_data)
static void _fill_text_view(const uint32_t i, const uint32_t count, dt_lib_module_t *self)
static void _append_kv(GList **l, const gchar *key, const gchar *value)
static gboolean _got_focus(GtkWidget *textview, dt_lib_module_t *self)
@ DT_METADATA_FLAG_HIDDEN
Definition metadata.h:74
@ DT_METADATA_FLAG_PRIVATE
Definition metadata.h:75
@ DT_METADATA_NUMBER
Definition metadata.h:52
@ DT_METADATA_TYPE_OPTIONAL
Definition metadata.h:59
@ DT_METADATA_TYPE_INTERNAL
Definition metadata.h:60
@ DT_METADATA_SIGNAL_SHOWN
Definition metadata.h:66
@ DT_METADATA_SIGNAL_NEW_VALUE
Definition metadata.h:68
@ DT_METADATA_SIGNAL_HIDDEN
Definition metadata.h:67
size_t size
Definition mipmap_cache.c:3
void dt_osx_disallow_fullscreen(GtkWidget *widget)
Definition osx.mm:104
GList * dt_selection_get_list(struct dt_selection_t *selection)
Definition selection.c:172
#define DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(ctlsig, cb, user_data)
Definition signal.h:368
#define DT_DEBUG_CONTROL_SIGNAL_RAISE(ctlsig, signal,...)
Definition signal.h:347
@ DT_SIGNAL_METADATA_CHANGED
This signal is raised when metadata status (shown/hidden) or value has changed.
Definition signal.h:139
@ DT_SIGNAL_SELECTION_CHANGED
This signal is raised when the selection is changed no param, no returned value.
Definition signal.h:127
#define DT_DEBUG_CONTROL_SIGNAL_CONNECT(ctlsig, signal, cb, user_data)
Definition signal.h:357
struct _GtkWidget GtkWidget
Definition splash.h:29
struct dt_gui_gtk_t * gui
Definition darktable.h:775
struct dt_selection_t * selection
Definition darktable.h:782
const struct dt_database_t * db
Definition darktable.h:779
struct dt_control_signal_t * signals
Definition darktable.h:774
dt_ui_t * ui
Definition gtk.h:164
GtkTextView * textview[DT_METADATA_NUMBER]
GList * metadata_list[DT_METADATA_NUMBER]
GtkWidget * apply_button
char plugin_name[128]
Definition lib.h:82
GModule *void * data
Definition lib.h:80
GtkWidget * widget
Definition lib.h:84
guint timeout_handle
Definition lib.h:90
@ DT_UI_CONTAINER_PANEL_LEFT_CENTER