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