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