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