Fix untranslated text
[geeqie.git] / src / bar-exif.cc
1 /*
2  * Copyright (C) 2004 John Ellis
3  * Copyright (C) 2008 - 2016 The Geeqie Team
4  *
5  * Author: Vladimir Nadvornik
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License along
18  * with this program; if not, write to the Free Software Foundation, Inc.,
19  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20  */
21
22 #include "bar-exif.h"
23
24 #include <cstring>
25
26 #include <gdk/gdk.h>
27 #include <glib-object.h>
28 #include <pango/pango.h>
29
30 #include <config.h>
31
32 #include "bar.h"
33 #include "compat.h"
34 #include "debug.h"
35 #include "dnd.h"
36 #include "exif.h"
37 #include "filedata.h"
38 #include "intl.h"
39 #include "layout.h"
40 #include "main-defines.h"
41 #include "metadata.h"
42 #include "misc.h"
43 #include "rcfile.h"
44 #include "typedefs.h"
45 #include "ui-menu.h"
46 #include "ui-misc.h"
47 #include "ui-utildlg.h"
48
49 enum {
50         MIN_HEIGHT = 25
51 };
52 /*
53  *-------------------------------------------------------------------
54  * EXIF widget
55  *-------------------------------------------------------------------
56  */
57
58 struct PaneExifData;
59
60 struct ExifEntry
61 {
62         GtkWidget *ebox;
63         GtkWidget *box;
64         GtkWidget *title_label;
65         GtkWidget *value_widget;
66
67         gchar *key;
68         gchar *title;
69         gboolean if_set;
70         gboolean auto_title;
71         gboolean editable;
72
73         PaneExifData *ped;
74 };
75
76
77 struct PaneExifData
78 {
79         PaneData pane;
80         GtkWidget *vbox;
81         GtkWidget *widget;
82         GtkSizeGroup *size_group;
83
84         gint min_height;
85
86         gboolean all_hidden;
87         gboolean show_all;
88
89         FileData *fd;
90 };
91
92 struct ConfDialogData
93 {
94         GtkWidget *widget; /* pane or entry, devidet by presenceof "pane_data" or "entry_data" */
95
96         /* dialog parts */
97         GenericDialog *gd;
98         GtkWidget *key_entry;
99         GtkWidget *title_entry;
100         gboolean if_set;
101         gboolean editable;
102 };
103
104 static void bar_pane_exif_entry_dnd_init(GtkWidget *entry);
105 static void bar_pane_exif_entry_update_title(ExifEntry *ee);
106 static void bar_pane_exif_update(PaneExifData *ped);
107 static gboolean bar_pane_exif_menu_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data);
108 static void bar_pane_exif_notify_cb(FileData *fd, NotifyType type, gpointer data);
109 static gboolean bar_pane_exif_copy_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data);
110
111 static void bar_pane_exif_entry_changed(GtkEntry *, gpointer data)
112 {
113         auto ee = static_cast<ExifEntry *>(data);
114         gchar *text;
115         if (!ee->ped->fd) return;
116
117         text = text_widget_text_pull(ee->value_widget);
118         metadata_write_string(ee->ped->fd, ee->key, text);
119         g_free(text);
120 }
121
122 static void bar_pane_exif_entry_destroy(GtkWidget *, gpointer data)
123 {
124         auto ee = static_cast<ExifEntry *>(data);
125
126         g_free(ee->key);
127         g_free(ee->title);
128         g_free(ee);
129 }
130
131 static void bar_pane_exif_setup_entry_box(PaneExifData *ped, ExifEntry *ee)
132 {
133         gboolean horizontal = !ee->editable;
134         gboolean editable = ee->editable;
135
136         if (ee->box)
137                 {
138                 gtk_container_remove(GTK_CONTAINER(gtk_widget_get_parent(ee->box)), ee->box);
139                 }
140
141         ee->box = horizontal ? gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0) : gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
142         gq_gtk_container_add(GTK_WIDGET(ee->ebox), ee->box);
143         gtk_widget_show(ee->box);
144
145         ee->title_label = gtk_label_new(nullptr);
146         gtk_label_set_xalign(GTK_LABEL(ee->title_label), horizontal ? 1.0 : 0.0);
147         gtk_label_set_yalign(GTK_LABEL(ee->title_label), 0.5);
148         gtk_size_group_add_widget(ped->size_group, ee->title_label);
149         gq_gtk_box_pack_start(GTK_BOX(ee->box), ee->title_label, FALSE, TRUE, 0);
150         gtk_widget_show(ee->title_label);
151
152         if (editable)
153                 {
154                 ee->value_widget = gtk_entry_new();
155                 g_signal_connect(G_OBJECT(ee->value_widget), "changed",
156                          G_CALLBACK(bar_pane_exif_entry_changed), ee);
157
158                 }
159         else
160                 {
161                 ee->value_widget = gtk_label_new(nullptr);
162                 gtk_label_set_ellipsize(GTK_LABEL(ee->value_widget), PANGO_ELLIPSIZE_END);
163                 gtk_label_set_xalign(GTK_LABEL(ee->value_widget), 0.0);
164                 gtk_label_set_yalign(GTK_LABEL(ee->value_widget), 0.5);
165                 }
166
167         gq_gtk_box_pack_start(GTK_BOX(ee->box), ee->value_widget, TRUE, TRUE, 1);
168         gtk_widget_show(ee->value_widget);
169 }
170
171 static GtkWidget *bar_pane_exif_add_entry(PaneExifData *ped, const gchar *key, const gchar *title, gboolean if_set, gboolean editable)
172 {
173         auto ee = g_new0(ExifEntry, 1);
174
175         ee->key = g_strdup(key);
176         if (title && title[0])
177                 {
178                 ee->title = g_strdup(title);
179                 }
180         else
181                 {
182                 ee->title = exif_get_description_by_key(key);
183                 ee->auto_title = TRUE;
184                 }
185
186         ee->if_set = if_set;
187         ee->editable = editable;
188
189         ee->ped = ped;
190
191         ee->ebox = gtk_event_box_new();
192         g_object_set_data(G_OBJECT(ee->ebox), "entry_data", ee);
193         g_signal_connect_after(G_OBJECT(ee->ebox), "destroy",
194                                G_CALLBACK(bar_pane_exif_entry_destroy), ee);
195
196         gq_gtk_box_pack_start(GTK_BOX(ped->vbox), ee->ebox, FALSE, FALSE, 0);
197
198         bar_pane_exif_entry_dnd_init(ee->ebox);
199         g_signal_connect(ee->ebox, "button_release_event", G_CALLBACK(bar_pane_exif_menu_cb), ped);
200         g_signal_connect(ee->ebox, "button_press_event", G_CALLBACK(bar_pane_exif_copy_cb), ped);
201
202         bar_pane_exif_setup_entry_box(ped, ee);
203
204         bar_pane_exif_entry_update_title(ee);
205         bar_pane_exif_update(ped);
206
207         return ee->ebox;
208 }
209
210 static void bar_pane_exif_reparent_entry(GtkWidget *entry, GtkWidget *pane)
211 {
212         auto ped = static_cast<PaneExifData *>(g_object_get_data(G_OBJECT(pane), "pane_data"));
213         PaneExifData *old_ped;
214         auto ee = static_cast<ExifEntry *>(g_object_get_data(G_OBJECT(entry), "entry_data"));
215
216         if (!ped || !ee) return;
217
218         old_ped = ee->ped;
219
220         g_object_ref(entry);
221
222         gtk_size_group_remove_widget(old_ped->size_group, ee->title_label);
223         gtk_container_remove(GTK_CONTAINER(old_ped->vbox), entry);
224
225         ee->ped = ped;
226         gtk_size_group_add_widget(ped->size_group, ee->title_label);
227         gq_gtk_box_pack_start(GTK_BOX(ped->vbox), entry, FALSE, FALSE, 0);
228 }
229
230 static void bar_pane_exif_entry_update_title(ExifEntry *ee)
231 {
232         gchar *markup;
233
234         markup = g_markup_printf_escaped("<span size='small'>%s:</span>", (ee->title) ? ee->title : _("<empty label, fixme>"));
235         gtk_label_set_markup(GTK_LABEL(ee->title_label), markup);
236         g_free(markup);
237 }
238
239 static void bar_pane_exif_update_entry(PaneExifData *ped, GtkWidget *entry, gboolean update_title)
240 {
241         gchar *text;
242         auto ee = static_cast<ExifEntry *>(g_object_get_data(G_OBJECT(entry), "entry_data"));
243         gshort rating;
244
245         if (!ee) return;
246         if (g_strcmp0(ee->key, "Xmp.xmp.Rating") == 0)
247                 {
248                 rating = metadata_read_int(ee->ped->fd, ee->key, 0);
249                 text = g_strdup_printf("%d", rating);
250                 }
251         else
252                 {
253                 text = metadata_read_string(ped->fd, ee->key, ee->editable ? METADATA_PLAIN : METADATA_FORMATTED);
254                 }
255
256         if (!ped->show_all && ee->if_set && !ee->editable && (!text || !*text))
257                 {
258                 gtk_label_set_text(GTK_LABEL(ee->value_widget), nullptr);
259                 gtk_widget_hide(entry);
260                 }
261         else
262                 {
263                 if (ee->editable)
264                         {
265                         g_signal_handlers_block_by_func(ee->value_widget, (gpointer *)bar_pane_exif_entry_changed, ee);
266                         gq_gtk_entry_set_text(GTK_ENTRY(ee->value_widget), text ? text : "");
267                         g_signal_handlers_unblock_by_func(ee->value_widget, (gpointer)bar_pane_exif_entry_changed, ee);
268                         gtk_widget_set_tooltip_text(ee->box, nullptr);
269                         }
270                 else
271                         {
272                         gtk_label_set_text(GTK_LABEL(ee->value_widget), text);
273                         gtk_widget_set_tooltip_text(ee->box, text);
274                         }
275                 gtk_widget_show(entry);
276                 ped->all_hidden = FALSE;
277                 }
278
279         g_free(text);
280
281         if (update_title) bar_pane_exif_entry_update_title(ee);
282 }
283
284 static void bar_pane_exif_update(PaneExifData *ped)
285 {
286         GList *list;
287         GList *work;
288
289         ped->all_hidden = TRUE;
290
291         list = gtk_container_get_children(GTK_CONTAINER(ped->vbox));
292         work = list;
293         while (work)
294                 {
295                 auto entry = static_cast<GtkWidget *>(work->data);
296                 work = work->next;
297
298                 bar_pane_exif_update_entry(ped, entry, FALSE);
299                 }
300         g_list_free(list);
301
302         gtk_widget_set_sensitive(ped->pane.title, !ped->all_hidden);
303 }
304
305 static void bar_pane_exif_set_fd(GtkWidget *widget, FileData *fd)
306 {
307         PaneExifData *ped;
308
309         ped = static_cast<PaneExifData *>(g_object_get_data(G_OBJECT(widget), "pane_data"));
310         if (!ped) return;
311
312         file_data_unref(ped->fd);
313         ped->fd = file_data_ref(fd);
314
315         bar_pane_exif_update(ped);
316 }
317
318 static gint bar_pane_exif_event(GtkWidget *bar, GdkEvent *event)
319 {
320         PaneExifData *ped;
321         gboolean ret = FALSE;
322         GList *list;
323         GList *work;
324
325         ped = static_cast<PaneExifData *>(g_object_get_data(G_OBJECT(bar), "pane_data"));
326         if (!ped) return FALSE;
327
328         list = gtk_container_get_children(GTK_CONTAINER(ped->vbox));
329         work = list;
330         while (!ret && work)
331                 {
332                 auto entry = static_cast<GtkWidget *>(work->data);
333                 auto ee = static_cast<ExifEntry *>(g_object_get_data(G_OBJECT(entry), "entry_data"));
334                 work = work->next;
335
336                 if (ee->editable && gtk_widget_has_focus(ee->value_widget)) ret = gtk_widget_event(ee->value_widget, event);
337                 }
338         g_list_free(list);
339         return ret;
340 }
341
342 static void bar_pane_exif_notify_cb(FileData *fd, NotifyType type, gpointer data)
343 {
344         auto ped = static_cast<PaneExifData *>(data);
345         if ((type & (NOTIFY_REREAD | NOTIFY_CHANGE | NOTIFY_METADATA)) && fd == ped->fd)
346                 {
347                 DEBUG_1("Notify pane_exif: %s %04x", fd->path, type);
348                 bar_pane_exif_update(ped);
349                 }
350 }
351
352
353 /*
354  *-------------------------------------------------------------------
355  * dnd
356  *-------------------------------------------------------------------
357  */
358
359 static GtkTargetEntry bar_pane_exif_drag_types[] = {
360         { const_cast<gchar *>(TARGET_APP_EXIF_ENTRY_STRING), GTK_TARGET_SAME_APP, TARGET_APP_EXIF_ENTRY },
361         { const_cast<gchar *>("text/plain"), 0, TARGET_TEXT_PLAIN }
362 };
363 static gint n_exif_entry_drag_types = 2;
364
365 static GtkTargetEntry bar_pane_exif_drop_types[] = {
366         { const_cast<gchar *>(TARGET_APP_EXIF_ENTRY_STRING), GTK_TARGET_SAME_APP, TARGET_APP_EXIF_ENTRY },
367         { const_cast<gchar *>("text/plain"), 0, TARGET_TEXT_PLAIN }
368 };
369 static gint n_exif_entry_drop_types = 2;
370
371
372 static void bar_pane_exif_entry_dnd_get(GtkWidget *entry, GdkDragContext *,
373                                      GtkSelectionData *selection_data, guint info,
374                                      guint, gpointer)
375 {
376         auto ee = static_cast<ExifEntry *>(g_object_get_data(G_OBJECT(entry), "entry_data"));
377
378         switch (info)
379                 {
380                 case TARGET_APP_EXIF_ENTRY:
381                         gtk_selection_data_set(selection_data, gtk_selection_data_get_target(selection_data),
382                                                8, reinterpret_cast<const guchar *>(&entry), sizeof(entry));
383                         break;
384
385                 case TARGET_TEXT_PLAIN:
386                 default:
387                         gtk_selection_data_set_text(selection_data, ee->key, -1);
388                         break;
389                 }
390
391 }
392
393 static void bar_pane_exif_dnd_receive(GtkWidget *pane, GdkDragContext *,
394                                           gint x, gint y,
395                                           GtkSelectionData *selection_data, guint info,
396                                           guint, gpointer)
397 {
398         PaneExifData *ped;
399         GList *work;
400         GList *list;
401         gint pos;
402         GtkWidget *new_entry = nullptr;
403
404         ped = static_cast<PaneExifData *>(g_object_get_data(G_OBJECT(pane), "pane_data"));
405         if (!ped) return;
406
407         switch (info)
408                 {
409                 case TARGET_APP_EXIF_ENTRY:
410                         new_entry = GTK_WIDGET(*(gpointer *)gtk_selection_data_get_data(selection_data));
411
412                         if (gtk_widget_get_parent(new_entry) && gtk_widget_get_parent(new_entry) != ped->vbox) bar_pane_exif_reparent_entry(new_entry, pane);
413
414                         break;
415                 default:
416                         /** @FIXME this needs a check for valid exif keys */
417                         new_entry = bar_pane_exif_add_entry(ped, reinterpret_cast<const gchar *>(gtk_selection_data_get_data(selection_data)), nullptr, TRUE, FALSE);
418                         break;
419                 }
420
421         list = gtk_container_get_children(GTK_CONTAINER(ped->vbox));
422         work = list;
423         pos = 0;
424         while (work)
425                 {
426                 gint nx;
427                 gint ny;
428                 auto entry = static_cast<GtkWidget *>(work->data);
429                 GtkAllocation allocation;
430                 work = work->next;
431
432                 if (entry == new_entry) continue;
433
434                 gtk_widget_get_allocation(entry, &allocation);
435
436                 if (gtk_widget_is_drawable(entry) &&
437                     gtk_widget_translate_coordinates(pane, entry, x, y, &nx, &ny) &&
438                     ny < allocation.height / 2) break;
439                 pos++;
440                 }
441         g_list_free(list);
442
443         gtk_box_reorder_child(GTK_BOX(ped->vbox), new_entry, pos);
444 }
445
446 static void bar_pane_exif_entry_dnd_begin(GtkWidget *entry, GdkDragContext *context, gpointer)
447 {
448         auto ee = static_cast<ExifEntry *>(g_object_get_data(G_OBJECT(entry), "entry_data"));
449
450         if (!ee) return;
451         dnd_set_drag_label(entry, context, ee->key);
452 }
453
454 static void bar_pane_exif_entry_dnd_end(GtkWidget *, GdkDragContext *, gpointer)
455 {
456 }
457
458 static void bar_pane_exif_entry_dnd_init(GtkWidget *entry)
459 {
460         auto ee = static_cast<ExifEntry *>(g_object_get_data(G_OBJECT(entry), "entry_data"));
461
462         gtk_drag_source_set(entry, static_cast<GdkModifierType>(GDK_BUTTON1_MASK | GDK_BUTTON2_MASK),
463                             bar_pane_exif_drag_types, n_exif_entry_drag_types,
464                             static_cast<GdkDragAction>(GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK));
465         g_signal_connect(G_OBJECT(entry), "drag_data_get",
466                          G_CALLBACK(bar_pane_exif_entry_dnd_get), ee);
467
468         g_signal_connect(G_OBJECT(entry), "drag_begin",
469                          G_CALLBACK(bar_pane_exif_entry_dnd_begin), ee);
470         g_signal_connect(G_OBJECT(entry), "drag_end",
471                          G_CALLBACK(bar_pane_exif_entry_dnd_end), ee);
472 }
473
474 static void bar_pane_exif_dnd_init(GtkWidget *pane)
475 {
476         gtk_drag_dest_set(pane,
477                           static_cast<GtkDestDefaults>(GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_HIGHLIGHT | GTK_DEST_DEFAULT_DROP),
478                           bar_pane_exif_drop_types, n_exif_entry_drop_types,
479                           static_cast<GdkDragAction>(GDK_ACTION_COPY | GDK_ACTION_MOVE));
480         g_signal_connect(G_OBJECT(pane), "drag_data_received",
481                          G_CALLBACK(bar_pane_exif_dnd_receive), NULL);
482 }
483
484 static void bar_pane_exif_edit_close_cb(GtkWidget *, gpointer data)
485 {
486         auto gd = static_cast<GenericDialog *>(data);
487         generic_dialog_close(gd);
488 }
489
490 static void bar_pane_exif_edit_destroy_cb(GtkWidget *, gpointer data)
491 {
492         auto cdd = static_cast<ConfDialogData *>(data);
493         g_signal_handlers_disconnect_by_func(cdd->widget, (gpointer)(bar_pane_exif_edit_close_cb), cdd->gd);
494         g_free(cdd);
495 }
496
497 static void bar_pane_exif_edit_cancel_cb(GenericDialog *, gpointer)
498 {
499 }
500
501 static void bar_pane_exif_edit_ok_cb(GenericDialog *, gpointer data)
502 {
503         auto cdd = static_cast<ConfDialogData *>(data);
504
505         /* either one or the other */
506         auto ped = static_cast<PaneExifData *>(g_object_get_data(G_OBJECT(cdd->widget), "pane_data"));
507         auto ee = static_cast<ExifEntry *>(g_object_get_data(G_OBJECT(cdd->widget), "entry_data"));
508
509         if (ped)
510                 {
511                 bar_pane_exif_add_entry(ped,
512                                         gq_gtk_entry_get_text(GTK_ENTRY(cdd->key_entry)),
513                                         gq_gtk_entry_get_text(GTK_ENTRY(cdd->title_entry)),
514                                         cdd->if_set, cdd->editable);
515                 }
516
517         if (ee)
518                 {
519                 const gchar *title;
520                 GtkWidget *pane = gtk_widget_get_parent(cdd->widget);
521
522                 while (pane)
523                         {
524                         ped = static_cast<PaneExifData *>(g_object_get_data(G_OBJECT(pane), "pane_data"));
525                         if (ped) break;
526                         pane = gtk_widget_get_parent(pane);
527                         }
528
529                 if (!pane) return;
530
531                 g_free(ee->key);
532                 ee->key = g_strdup(gq_gtk_entry_get_text(GTK_ENTRY(cdd->key_entry)));
533                 title = gq_gtk_entry_get_text(GTK_ENTRY(cdd->title_entry));
534                 if (!title || strlen(title) == 0)
535                         {
536                         g_free(ee->title);
537                         ee->title = exif_get_description_by_key(ee->key);
538                         ee->auto_title = TRUE;
539                         }
540                 else if (!ee->title || strcmp(ee->title, title) != 0)
541                         {
542                         g_free(ee->title);
543                         ee->title = g_strdup(title);
544                         ee->auto_title = FALSE;
545                         }
546
547                 ee->if_set = cdd->if_set;
548                 ee->editable = cdd->editable;
549
550                 bar_pane_exif_setup_entry_box(ped, ee);
551
552                 bar_pane_exif_entry_update_title(ee);
553                 bar_pane_exif_update(ped);
554                 }
555 }
556
557 static void bar_pane_exif_conf_dialog(GtkWidget *widget)
558 {
559         ConfDialogData *cdd;
560         GenericDialog *gd;
561         GtkWidget *table;
562
563         /* the widget can be either ExifEntry (for editing) or Pane (for new entry)
564            we can decide it by the attached data */
565         auto ee = static_cast<ExifEntry *>(g_object_get_data(G_OBJECT(widget), "entry_data"));
566
567         cdd = g_new0(ConfDialogData, 1);
568
569         cdd->widget = widget;
570
571
572         cdd->if_set = ee ? ee->if_set : TRUE;
573         cdd->editable = ee ? ee->editable : FALSE;
574
575         cdd->gd = gd = generic_dialog_new(ee ? _("Configure entry") : _("Add entry"), "exif_entry_edit",
576                                 widget, TRUE,
577                                 bar_pane_exif_edit_cancel_cb, cdd);
578         g_signal_connect(G_OBJECT(gd->dialog), "destroy",
579                          G_CALLBACK(bar_pane_exif_edit_destroy_cb), cdd);
580
581         /* in case the entry is deleted during editing */
582         g_signal_connect(G_OBJECT(widget), "destroy",
583                          G_CALLBACK(bar_pane_exif_edit_close_cb), gd);
584
585         generic_dialog_add_message(gd, nullptr, ee ? _("Configure entry") : _("Add entry"), nullptr, FALSE);
586
587         generic_dialog_add_button(gd, GQ_ICON_OK, "OK",
588                                   bar_pane_exif_edit_ok_cb, TRUE);
589
590         table = pref_table_new(gd->vbox, 3, 2, FALSE, TRUE);
591         pref_table_label(table, 0, 0, _("Key:"), GTK_ALIGN_END);
592
593         cdd->key_entry = gtk_entry_new();
594         gtk_widget_set_size_request(cdd->key_entry, 300, -1);
595         if (ee) gq_gtk_entry_set_text(GTK_ENTRY(cdd->key_entry), ee->key);
596         gq_gtk_grid_attach_default(GTK_GRID(table), cdd->key_entry, 1, 2, 0, 1);
597         generic_dialog_attach_default(gd, cdd->key_entry);
598         gtk_widget_show(cdd->key_entry);
599
600         pref_table_label(table, 0, 1, _("Title:"), GTK_ALIGN_END);
601
602         cdd->title_entry = gtk_entry_new();
603         gtk_widget_set_size_request(cdd->title_entry, 300, -1);
604         if (ee) gq_gtk_entry_set_text(GTK_ENTRY(cdd->title_entry), ee->title);
605         gq_gtk_grid_attach_default(GTK_GRID(table), cdd->title_entry, 1, 2, 1, 2);
606         generic_dialog_attach_default(gd, cdd->title_entry);
607         gtk_widget_show(cdd->title_entry);
608
609         pref_checkbox_new_int(gd->vbox, _("Show only if set"), cdd->if_set, &cdd->if_set);
610         pref_checkbox_new_int(gd->vbox, _("Editable (supported only for XMP)"), cdd->editable, &cdd->editable);
611
612         gtk_widget_show(gd->dialog);
613 }
614
615 static void bar_pane_exif_conf_dialog_cb(GtkWidget *, gpointer data)
616 {
617         auto widget = static_cast<GtkWidget *>(data);
618         bar_pane_exif_conf_dialog(widget);
619 }
620
621 static void bar_pane_exif_delete_entry_cb(GtkWidget *, gpointer data)
622 {
623         auto entry = static_cast<GtkWidget *>(data);
624         gtk_container_remove(GTK_CONTAINER(gtk_widget_get_parent(entry)), entry);
625 }
626
627 #if HAVE_GTK4
628 static void bar_pane_exif_copy_entry_cb(GtkWidget *, gpointer data)
629 {
630 /* @FIXME GTK4 stub */
631 }
632 #else
633 static void bar_pane_exif_copy_entry_cb(GtkWidget *, gpointer data)
634 {
635         auto widget = static_cast<GtkWidget *>(data);
636         GtkClipboard *clipboard;
637         const gchar *value;
638         ExifEntry *ee;
639
640         ee = static_cast<ExifEntry *>(g_object_get_data(G_OBJECT(widget), "entry_data"));
641         value = gtk_label_get_text(GTK_LABEL(ee->value_widget));
642         clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
643         gtk_clipboard_set_text(clipboard, value, -1);
644 }
645 #endif
646
647 static void bar_pane_exif_toggle_show_all_cb(GtkWidget *, gpointer data)
648 {
649         auto ped = static_cast<PaneExifData *>(data);
650         ped->show_all = !ped->show_all;
651         bar_pane_exif_update(ped);
652 }
653
654 static void bar_pane_exif_menu_popup(GtkWidget *widget, PaneExifData *ped)
655 {
656         GtkWidget *menu;
657         /* the widget can be either ExifEntry (for editing) or Pane (for new entry)
658            we can decide it by the attached data */
659         auto ee = static_cast<ExifEntry *>(g_object_get_data(G_OBJECT(widget), "entry_data"));
660
661         menu = popup_menu_short_lived();
662
663         if (ee)
664                 {
665                 /* for the entry */
666                 gchar *conf = g_strdup_printf(_("Configure \"%s\""), ee->title);
667                 gchar *del = g_strdup_printf(_("Remove \"%s\""), ee->title);
668                 gchar *copy = g_strdup_printf(_("Copy \"%s\""), ee->title);
669
670                 menu_item_add_icon(menu, conf, GQ_ICON_EDIT, G_CALLBACK(bar_pane_exif_conf_dialog_cb), widget);
671                 menu_item_add_icon(menu, del, GQ_ICON_DELETE, G_CALLBACK(bar_pane_exif_delete_entry_cb), widget);
672                 menu_item_add_icon(menu, copy, GQ_ICON_COPY, G_CALLBACK(bar_pane_exif_copy_entry_cb), widget);
673                 menu_item_add_divider(menu);
674
675                 g_free(conf);
676                 g_free(del);
677                 }
678
679         /* for the pane */
680         menu_item_add_icon(menu, _("Add entry"), GQ_ICON_ADD, G_CALLBACK(bar_pane_exif_conf_dialog_cb), ped->widget);
681         menu_item_add_check(menu, _("Show hidden entries"), ped->show_all, G_CALLBACK(bar_pane_exif_toggle_show_all_cb), ped);
682
683         gtk_menu_popup_at_pointer(GTK_MENU(menu), nullptr);
684 }
685
686 static gboolean bar_pane_exif_menu_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data)
687 {
688         auto ped = static_cast<PaneExifData *>(data);
689         if (bevent->button == MOUSE_BUTTON_RIGHT)
690                 {
691                 bar_pane_exif_menu_popup(widget, ped);
692                 return TRUE;
693                 }
694         return FALSE;
695 }
696
697 #if HAVE_GTK4
698 static gboolean bar_pane_exif_copy_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer)
699 {
700 /* @FIXME GTK4 stub */
701         return FALSE;
702 }
703 #else
704 static gboolean bar_pane_exif_copy_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer)
705 {
706         const gchar *value;
707         GtkClipboard *clipboard;
708         ExifEntry *ee;
709
710         if (bevent->button == MOUSE_BUTTON_LEFT)
711                 {
712                 ee = static_cast<ExifEntry *>(g_object_get_data(G_OBJECT(widget), "entry_data"));
713                 value = gtk_label_get_text(GTK_LABEL(ee->value_widget));
714                 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
715                 gtk_clipboard_set_text(clipboard, value, -1);
716
717                 return TRUE;
718                 }
719
720         return FALSE;
721 }
722 #endif
723
724 static void bar_pane_exif_entry_write_config(GtkWidget *entry, GString *outstr, gint indent)
725 {
726         auto ee = static_cast<ExifEntry *>(g_object_get_data(G_OBJECT(entry), "entry_data"));
727         if (!ee) return;
728
729         WRITE_NL(); WRITE_STRING("<entry ");
730         WRITE_CHAR(*ee, key);
731         if (!ee->auto_title) WRITE_CHAR(*ee, title);
732         WRITE_BOOL(*ee, if_set);
733         WRITE_BOOL(*ee, editable);
734         WRITE_STRING("/>");
735 }
736
737 static void bar_pane_exif_write_config(GtkWidget *pane, GString *outstr, gint indent)
738 {
739         PaneExifData *ped;
740         GList *work;
741         GList *list;
742
743         ped = static_cast<PaneExifData *>(g_object_get_data(G_OBJECT(pane), "pane_data"));
744         if (!ped) return;
745
746         WRITE_NL(); WRITE_STRING("<pane_exif ");
747         write_char_option(outstr, indent, "id", ped->pane.id);
748         write_char_option(outstr, indent, "title", gtk_label_get_text(GTK_LABEL(ped->pane.title)));
749         WRITE_BOOL(ped->pane, expanded);
750         WRITE_BOOL(*ped, show_all);
751         WRITE_STRING(">");
752         indent++;
753
754         list = gtk_container_get_children(GTK_CONTAINER(ped->vbox));
755         work = list;
756         while (work)
757                 {
758                 auto entry = static_cast<GtkWidget *>(work->data);
759                 work = work->next;
760
761                 bar_pane_exif_entry_write_config(entry, outstr, indent);
762                 }
763         g_list_free(list);
764         indent--;
765         WRITE_NL(); WRITE_STRING("</pane_exif>");
766 }
767
768 GList * bar_pane_exif_list()
769 {
770         PaneExifData *ped;
771         GList *list;
772         GList *work_windows;
773         GList *exif_list = nullptr;
774         LayoutWindow *lw;
775         GtkWidget *bar;
776         GtkWidget *pane;
777         GtkWidget *entry;
778         ExifEntry *ee;
779
780         work_windows = layout_window_list;
781         lw = static_cast<LayoutWindow *>(work_windows->data);
782         bar = lw->bar;
783         pane = bar_find_pane_by_id(bar, PANE_EXIF, "exif");
784         if (pane)
785                 {
786                 ped = static_cast<PaneExifData *>(g_object_get_data(G_OBJECT(pane), "pane_data"));
787
788                 list = gtk_container_get_children(GTK_CONTAINER(ped->vbox));
789                 GList *work = list;
790                 while (work)
791                         {
792                         entry = static_cast<GtkWidget *>(work->data);
793                         work = work->next;
794                         ee = static_cast<ExifEntry *>(g_object_get_data(G_OBJECT(entry), "entry_data"));
795                         exif_list = g_list_append(exif_list, g_strdup(ee->title));
796                         exif_list = g_list_append(exif_list, g_strdup(ee->key));
797                         }
798
799                 g_list_free(list);
800                 }
801         return exif_list;
802 }
803
804 #pragma GCC diagnostic push
805 #pragma GCC diagnostic ignored "-Wunused-function"
806 void bar_pane_exif_close_unused(GtkWidget *widget)
807 {
808         PaneExifData *ped;
809
810         ped = static_cast<PaneExifData *>(g_object_get_data(G_OBJECT(widget), "pane_data"));
811         if (!ped) return;
812
813         g_object_unref(ped->vbox);
814 }
815 #pragma GCC diagnostic pop
816
817 static void bar_pane_exif_destroy(GtkWidget *, gpointer data)
818 {
819         auto ped = static_cast<PaneExifData *>(data);
820
821         file_data_unregister_notify_func(bar_pane_exif_notify_cb, ped);
822         g_object_unref(ped->size_group);
823         file_data_unref(ped->fd);
824         g_free(ped->pane.id);
825         g_free(ped);
826 }
827
828 #pragma GCC diagnostic push
829 #pragma GCC diagnostic ignored "-Wunused-function"
830 static void bar_pane_exif_size_request_unused(GtkWidget *, GtkRequisition *requisition, gpointer data)
831 {
832         auto *ped = static_cast<PaneExifData *>(data);
833         if (requisition->height < ped->min_height)
834                 {
835                 requisition->height = ped->min_height;
836                 }
837 }
838 #pragma GCC diagnostic pop
839
840 static void bar_pane_exif_size_allocate(GtkWidget *, GtkAllocation *alloc, gpointer data)
841 {
842         auto ped = static_cast<PaneExifData *>(data);
843         ped->min_height = alloc->height;
844         gtk_widget_set_size_request(ped->widget, -1, ped->min_height);
845 }
846
847 static GtkWidget *bar_pane_exif_new(const gchar *id, const gchar *title, gboolean expanded, gboolean show_all)
848 {
849         PaneExifData *ped;
850
851         ped = g_new0(PaneExifData, 1);
852
853         ped->pane.pane_set_fd = bar_pane_exif_set_fd;
854         ped->pane.pane_write_config = bar_pane_exif_write_config;
855         ped->pane.pane_event = bar_pane_exif_event;
856         ped->pane.title = bar_pane_expander_title(title);
857         ped->pane.id = g_strdup(id);
858         ped->pane.expanded = expanded;
859         ped->pane.type = PANE_EXIF;
860         ped->show_all = show_all;
861
862         ped->size_group = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
863         ped->widget = gtk_event_box_new();
864         ped->vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, PREF_PAD_GAP);
865         gq_gtk_container_add(GTK_WIDGET(ped->widget), ped->vbox);
866         gtk_widget_show(ped->vbox);
867
868         ped->min_height = MIN_HEIGHT;
869         g_object_set_data(G_OBJECT(ped->widget), "pane_data", ped);
870         g_signal_connect_after(G_OBJECT(ped->widget), "destroy",
871                                G_CALLBACK(bar_pane_exif_destroy), ped);
872         gtk_widget_set_size_request(ped->widget, -1, ped->min_height);
873         g_signal_connect(G_OBJECT(ped->widget), "size-allocate",
874                          G_CALLBACK(bar_pane_exif_size_allocate), ped);
875
876         bar_pane_exif_dnd_init(ped->widget);
877         g_signal_connect(ped->widget, "button_release_event", G_CALLBACK(bar_pane_exif_menu_cb), ped);
878
879         file_data_register_notify_func(bar_pane_exif_notify_cb, ped, NOTIFY_PRIORITY_LOW);
880
881         gtk_widget_show(ped->widget);
882
883         return ped->widget;
884 }
885
886 GtkWidget *bar_pane_exif_new_from_config(const gchar **attribute_names, const gchar **attribute_values)
887 {
888         gchar *title = nullptr;
889         gchar *id = g_strdup("exif");
890         gboolean expanded = TRUE;
891         gboolean show_all = FALSE;
892         GtkWidget *ret;
893
894         while (*attribute_names)
895                 {
896                 const gchar *option = *attribute_names++;
897                 const gchar *value = *attribute_values++;
898
899                 if (READ_CHAR_FULL("id", id)) continue;
900                 if (READ_CHAR_FULL("title", title)) continue;
901                 if (READ_BOOL_FULL("expanded", expanded)) continue;
902                 if (READ_BOOL_FULL("show_all", show_all)) continue;
903
904                 log_printf("unknown attribute %s = %s\n", option, value);
905                 }
906
907         bar_pane_translate_title(PANE_EXIF, id, &title);
908         ret = bar_pane_exif_new(id, title, expanded, show_all);
909         g_free(title);
910         g_free(id);
911         return ret;
912 }
913
914 void bar_pane_exif_update_from_config(GtkWidget *pane, const gchar **attribute_names, const gchar **attribute_values)
915 {
916         PaneExifData *ped;
917         gchar *title = nullptr;
918
919         ped = static_cast<PaneExifData *>(g_object_get_data(G_OBJECT(pane), "pane_data"));
920         if (!ped) return;
921
922         while (*attribute_names)
923                 {
924                 const gchar *option = *attribute_names++;
925                 const gchar *value = *attribute_values++;
926
927                 if (READ_CHAR_FULL("title", title)) continue;
928                 if (READ_BOOL_FULL("expanded", ped->pane.expanded)) continue;
929                 if (READ_BOOL_FULL("show_all", ped->show_all)) continue;
930                 if (READ_CHAR_FULL("id", ped->pane.id)) continue;
931
932
933                 log_printf("unknown attribute %s = %s\n", option, value);
934                 }
935
936         if (title)
937                 {
938                 bar_pane_translate_title(PANE_EXIF, ped->pane.id, &title);
939                 gtk_label_set_text(GTK_LABEL(ped->pane.title), title);
940                 g_free(title);
941                 }
942
943         bar_update_expander(pane);
944         bar_pane_exif_update(ped);
945 }
946
947
948 void bar_pane_exif_entry_add_from_config(GtkWidget *pane, const gchar **attribute_names, const gchar **attribute_values)
949 {
950         PaneExifData *ped;
951         gchar *key = nullptr;
952         gchar *title = nullptr;
953         gboolean if_set = TRUE;
954         gboolean editable = FALSE;
955
956         ped = static_cast<PaneExifData *>(g_object_get_data(G_OBJECT(pane), "pane_data"));
957         if (!ped) return;
958
959         while (*attribute_names)
960                 {
961                 const gchar *option = *attribute_names++;
962                 const gchar *value = *attribute_values++;
963
964                 if (READ_CHAR_FULL("key", key)) continue;
965                 if (READ_CHAR_FULL("title", title)) continue;
966                 if (READ_BOOL_FULL("if_set", if_set)) continue;
967                 if (READ_BOOL_FULL("editable", editable)) continue;
968
969                 log_printf("unknown attribute %s = %s\n", option, value);
970                 }
971
972         if (key && key[0]) bar_pane_exif_add_entry(ped, key, title, if_set, editable);
973 }
974
975
976 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */