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