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