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