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