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