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