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