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