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