prepared exif pane for popup menu
[geeqie.git] / src / bar_exif.c
1 /*
2  * Geeqie
3  * (C) 2004 John Ellis
4  * Copyright (C) 2008 - 2009 The Geeqie Team
5  *
6  * Author: John Ellis
7  *
8  * This software is released under the GNU General Public License (GNU GPL).
9  * Please read the included file COPYING for more information.
10  * This software comes with no warranty of any kind, use at your own risk!
11  */
12
13
14 #include "main.h"
15 #include "bar_exif.h"
16
17 #include "exif.h"
18 #include "metadata.h"
19 #include "filedata.h"
20 #include "history_list.h"
21 #include "misc.h"
22 #include "ui_misc.h"
23 #include "ui_menu.h"
24 #include "bar.h"
25 #include "rcfile.h"
26 #include "dnd.h"
27
28
29 #include <math.h>
30
31 #define MIN_HEIGHT 25
32 /*
33  *-------------------------------------------------------------------
34  * EXIF widget
35  *-------------------------------------------------------------------
36  */
37
38 typedef struct _ExifEntry ExifEntry;
39 struct _ExifEntry
40 {
41         GtkWidget *ebox;
42         GtkWidget *hbox;
43         GtkWidget *title_label;
44         GtkWidget *value_label;
45
46         gchar *key;
47         gchar *title;
48         gboolean if_set;
49         gboolean auto_title;
50 };
51         
52         
53 typedef struct _PaneExifData PaneExifData;
54 struct _PaneExifData
55 {
56         PaneData pane;
57         GtkWidget *vbox;
58         GtkWidget *widget;
59         GtkSizeGroup *size_group;
60
61         gint min_height;
62         
63         FileData *fd;
64 };
65
66 static void bar_pane_exif_entry_dnd_init(GtkWidget *entry);
67 static void bar_pane_exif_update_entry(PaneExifData *ped, GtkWidget *entry, gboolean update_title);
68
69 static void bar_pane_exif_entry_destroy(GtkWidget *widget, gpointer data)
70 {
71         ExifEntry *ee = data;
72
73         g_free(ee->key);
74         g_free(ee->title);
75         g_free(ee);
76 }
77
78
79 static GtkWidget *bar_pane_exif_add_entry(PaneExifData *ped, const gchar *key, const gchar *title, gint if_set)
80 {
81         ExifEntry *ee = g_new0(ExifEntry, 1);
82         
83         ee->key = g_strdup(key);
84         if (title && title[0])
85                 {
86                 ee->title = g_strdup(title);
87                 }
88         else
89                 {
90                 ee->title = exif_get_description_by_key(key);
91                 ee->auto_title = TRUE;
92                 }
93                 
94         ee->if_set = if_set;
95         
96         ee->ebox = gtk_event_box_new();
97         g_object_set_data(G_OBJECT(ee->ebox), "entry_data", ee);
98         g_signal_connect_after(G_OBJECT(ee->ebox), "destroy",
99                                G_CALLBACK(bar_pane_exif_entry_destroy), ee);
100         
101         ee->hbox = gtk_hbox_new(FALSE, 0);
102         gtk_container_add(GTK_CONTAINER(ee->ebox), ee->hbox);
103         gtk_widget_show(ee->hbox);
104
105         ee->title_label = gtk_label_new(NULL);
106         gtk_misc_set_alignment(GTK_MISC(ee->title_label), 1.0, 0.5);
107         gtk_size_group_add_widget(ped->size_group, ee->title_label);
108         gtk_box_pack_start(GTK_BOX(ee->hbox), ee->title_label, FALSE, TRUE, 0);
109         gtk_widget_show(ee->title_label);
110         
111         ee->value_label = gtk_label_new(NULL);
112 //      gtk_label_set_width_chars(GTK_LABEL(ee->value_label), 20);
113         gtk_label_set_ellipsize(GTK_LABEL(ee->value_label), PANGO_ELLIPSIZE_END);
114 //      gtk_widget_set_size_request(ee->value_label, 100, -1);
115         gtk_misc_set_alignment(GTK_MISC(ee->value_label), 0.0, 0.5);
116         gtk_box_pack_start(GTK_BOX(ee->hbox), ee->value_label, TRUE, TRUE, 1);
117         gtk_widget_show(ee->value_label);
118         gtk_box_pack_start(GTK_BOX(ped->vbox), ee->ebox, FALSE, FALSE, 0);
119
120         bar_pane_exif_entry_dnd_init(ee->ebox);
121         
122         bar_pane_exif_update_entry(ped, ee->ebox, TRUE);
123         return ee->ebox;
124 }
125
126 static void bar_pane_exif_reparent_entry(GtkWidget *entry, GtkWidget *pane)
127 {
128         GtkWidget *old_pane = entry->parent;
129         PaneExifData *ped = g_object_get_data(G_OBJECT(pane), "pane_data");
130         PaneExifData *old_ped = g_object_get_data(G_OBJECT(old_pane), "pane_data");
131         ExifEntry *ee = g_object_get_data(G_OBJECT(entry), "entry_data");
132         if (!ped || !old_ped || !ee) return;
133         
134         g_object_ref(entry);
135         
136         gtk_size_group_remove_widget(old_ped->size_group, ee->title_label);
137         gtk_container_remove(GTK_CONTAINER(old_ped->vbox), entry);
138         
139         gtk_size_group_add_widget(ped->size_group, ee->title_label);
140         gtk_box_pack_start(GTK_BOX(ped->vbox), entry, FALSE, FALSE, 0);
141 }
142
143 static void bar_pane_exif_entry_update_title(ExifEntry *ee)
144 {
145         gchar *markup;
146
147         markup = g_markup_printf_escaped("<span size='small'>%s:</span>", (ee->title) ? ee->title : "fixme");
148         gtk_label_set_markup(GTK_LABEL(ee->title_label), markup);
149         g_free(markup);
150 }
151
152 static void bar_pane_exif_update_entry(PaneExifData *ped, GtkWidget *entry, gboolean update_title)
153 {
154         gchar *text;
155         ExifEntry *ee = g_object_get_data(G_OBJECT(entry), "entry_data");
156         if (!ee) return;
157         text = metadata_read_string(ped->fd, ee->key, METADATA_FORMATTED);
158
159         if (ee->if_set && (!text || !*text))
160                 {
161                 gtk_label_set_text(GTK_LABEL(ee->value_label), NULL);
162                 gtk_widget_hide(entry);
163                 }
164         else
165                 {
166                 gtk_label_set_text(GTK_LABEL(ee->value_label), text);
167 #if GTK_CHECK_VERSION(2,12,0)
168                 gtk_widget_set_tooltip_text(ee->hbox, text);
169 #endif
170                 gtk_widget_show(entry);
171                 }
172                 
173         g_free(text);
174         
175         if (update_title) bar_pane_exif_entry_update_title(ee);
176 }
177
178 static void bar_pane_exif_update(PaneExifData *ped)
179 {
180         GList *list, *work;
181
182 #if 0
183         ExifData *exif;
184         /* do we have any exif at all ? */
185         exif = exif_read_fd(ped->fd);
186
187         if (!exif)
188                 {
189                 bar_pane_exif_sensitive(ped, FALSE);
190                 return;
191                 }
192         else
193                 {
194                 /* we will use high level functions so we can release it for now.
195                    it will stay in the cache */
196                 exif_free_fd(ped->fd, exif);
197                 exif = NULL;
198                 }
199
200         bar_pane_exif_sensitive(ped, TRUE);
201 #endif  
202         list = gtk_container_get_children(GTK_CONTAINER(ped->vbox));    
203         work = list;
204         while (work)
205                 {
206                 GtkWidget *entry = work->data;
207                 work = work->next;
208         
209                 
210                 bar_pane_exif_update_entry(ped, entry, FALSE);
211                 }
212         g_list_free(list);
213 }
214
215 void bar_pane_exif_set_fd(GtkWidget *widget, FileData *fd)
216 {
217         PaneExifData *ped;
218
219         ped = g_object_get_data(G_OBJECT(widget), "pane_data");
220         if (!ped) return;
221
222         file_data_unref(ped->fd);
223         ped->fd = file_data_ref(fd);
224
225         bar_pane_exif_update(ped);
226 }
227
228 /*
229  *-------------------------------------------------------------------
230  * dnd
231  *-------------------------------------------------------------------
232  */
233
234 static GtkTargetEntry bar_pane_exif_drag_types[] = {
235         { TARGET_APP_EXIF_ENTRY_STRING, GTK_TARGET_SAME_APP, TARGET_APP_EXIF_ENTRY },
236         { "text/plain", 0, TARGET_TEXT_PLAIN }
237 };
238 static gint n_exif_entry_drag_types = 2;
239
240 static GtkTargetEntry bar_pane_exif_drop_types[] = {
241         { TARGET_APP_EXIF_ENTRY_STRING, GTK_TARGET_SAME_APP, TARGET_APP_EXIF_ENTRY },
242         { "text/plain", 0, TARGET_TEXT_PLAIN }
243 };
244 static gint n_exif_entry_drop_types = 2;
245
246
247 static void bar_pane_exif_entry_dnd_get(GtkWidget *entry, GdkDragContext *context,
248                                      GtkSelectionData *selection_data, guint info,
249                                      guint time, gpointer data)
250 {
251         ExifEntry *ee = g_object_get_data(G_OBJECT(entry), "entry_data");
252
253         switch (info)
254                 {
255
256                 case TARGET_APP_EXIF_ENTRY:
257                         gtk_selection_data_set(selection_data, selection_data->target,
258                                                8, (gpointer) &entry, sizeof(entry));
259                         break;
260
261                 case TARGET_TEXT_PLAIN:
262                 default:
263                         gtk_selection_data_set_text(selection_data, ee->key, -1);
264                         break;
265                 }
266         
267 }
268
269 static void bar_pane_exif_dnd_receive(GtkWidget *pane, GdkDragContext *context,
270                                           gint x, gint y,
271                                           GtkSelectionData *selection_data, guint info,
272                                           guint time, gpointer data)
273 {
274         PaneExifData *ped;
275         GList *work, *list;
276         gint pos;
277         GtkWidget *new_entry = NULL;
278         ped = g_object_get_data(G_OBJECT(pane), "pane_data");
279         if (!ped) return;
280
281         switch (info)
282                 {
283                 case TARGET_APP_EXIF_ENTRY:
284                         new_entry = *(gpointer *)selection_data->data;
285                         
286                         if (new_entry->parent && new_entry->parent != ped->vbox) bar_pane_exif_reparent_entry(new_entry, pane);
287                         
288                         break;
289                 default:
290                         /* FIXME: this needs a check for valid exif keys */
291                         new_entry = bar_pane_exif_add_entry(ped, (gchar *)selection_data->data, NULL, TRUE);
292                         break;
293                 }
294
295
296         list = gtk_container_get_children(GTK_CONTAINER(ped->vbox));    
297         work = list;
298         pos = 0;
299         while (work)
300                 {
301                 gint nx, ny;
302                 GtkWidget *entry = work->data;
303                 work = work->next;
304                 
305                 if (entry == new_entry) continue;
306                 
307                 if (GTK_WIDGET_DRAWABLE(entry) && 
308                     gtk_widget_translate_coordinates(pane, entry, x, y, &nx, &ny) &&
309                     ny < entry->allocation.height / 2) break;
310                 pos++;
311                 }
312         g_list_free(list);
313
314         gtk_box_reorder_child(GTK_BOX(ped->vbox), new_entry, pos);
315 }
316
317 static void bar_pane_exif_entry_dnd_begin(GtkWidget *widget, GdkDragContext *context, gpointer data)
318 {
319 //      gtk_drag_set_icon_default(context);
320 }
321
322 static void bar_pane_exif_entry_dnd_end(GtkWidget *widget, GdkDragContext *context, gpointer data)
323 {
324 }
325
326 static void bar_pane_exif_entry_dnd_init(GtkWidget *entry)
327 {
328         ExifEntry *ee = g_object_get_data(G_OBJECT(entry), "entry_data");
329
330         gtk_drag_source_set(entry, GDK_BUTTON1_MASK | GDK_BUTTON2_MASK,
331                             bar_pane_exif_drag_types, n_exif_entry_drag_types,
332                             GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK);
333         g_signal_connect(G_OBJECT(entry), "drag_data_get",
334                          G_CALLBACK(bar_pane_exif_entry_dnd_get), ee);
335
336         g_signal_connect(G_OBJECT(entry), "drag_begin",
337                          G_CALLBACK(bar_pane_exif_entry_dnd_begin), ee);
338         g_signal_connect(G_OBJECT(entry), "drag_end",
339                          G_CALLBACK(bar_pane_exif_entry_dnd_end), ee);
340 }
341
342 static void bar_pane_exif_dnd_init(GtkWidget *pane)
343 {
344         gtk_drag_dest_set(pane,
345                           GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_HIGHLIGHT | GTK_DEST_DEFAULT_DROP,
346                           bar_pane_exif_drop_types, n_exif_entry_drop_types,
347                           GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_ASK);
348         g_signal_connect(G_OBJECT(pane), "drag_data_received",
349                          G_CALLBACK(bar_pane_exif_dnd_receive), NULL);
350 }
351
352
353
354 static void bar_pane_exif_menu_popup(GtkWidget *data)
355 {
356         GtkWidget *menu;
357
358         menu = popup_menu_short_lived();
359
360         menu_item_add_stock(menu, _("Configure"), GTK_STOCK_GO_UP, NULL, data);
361         gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, data, 0, GDK_CURRENT_TIME);
362 }
363
364
365 static gboolean bar_pane_exif_menu_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data) 
366
367         if (bevent->button == MOUSE_BUTTON_RIGHT)
368                 {
369                 bar_pane_exif_menu_popup(widget);
370                 return TRUE;
371                 }
372         return FALSE;
373
374
375
376
377
378 static void bar_pane_exif_entry_write_config(GtkWidget *entry, GString *outstr, gint indent)
379 {
380         ExifEntry *ee = g_object_get_data(G_OBJECT(entry), "entry_data");
381         if (!ee) return;
382
383         WRITE_STRING("<entry\n");
384         indent++;
385         WRITE_CHAR(*ee, key);
386         if (!ee->auto_title) WRITE_CHAR(*ee, title);
387         WRITE_BOOL(*ee, if_set);
388         indent--;
389         WRITE_STRING("/>\n");
390 }
391
392 static void bar_pane_exif_write_config(GtkWidget *pane, GString *outstr, gint indent)
393 {
394         PaneExifData *ped;
395         GList *work, *list;
396         
397         ped = g_object_get_data(G_OBJECT(pane), "pane_data");
398         if (!ped) return;
399
400         WRITE_STRING("<pane_exif\n");
401         indent++;
402         write_char_option(outstr, indent, "pane.title", gtk_label_get_text(GTK_LABEL(ped->pane.title)));
403         WRITE_BOOL(*ped, pane.expanded);
404         indent--;
405         WRITE_STRING(">\n");
406         indent++;
407         
408         list = gtk_container_get_children(GTK_CONTAINER(ped->vbox));    
409         work = list;
410         while (work)
411                 {
412                 GtkWidget *entry = work->data;
413                 work = work->next;
414                 
415                 bar_pane_exif_entry_write_config(entry, outstr, indent);
416                 }
417         g_list_free(list);
418         indent--;
419         WRITE_STRING("</pane_exif>\n");
420 }
421
422
423 void bar_pane_exif_close(GtkWidget *widget)
424 {
425         PaneExifData *ped;
426
427         ped = g_object_get_data(G_OBJECT(widget), "pane_data");
428         if (!ped) return;
429
430         gtk_widget_destroy(ped->vbox);
431 }
432
433 static void bar_pane_exif_destroy(GtkWidget *widget, gpointer data)
434 {
435         PaneExifData *ped = data;
436
437         g_object_unref(ped->size_group);
438         file_data_unref(ped->fd);
439         g_free(ped);
440 }
441
442 static void bar_pane_exif_size_request(GtkWidget *pane, GtkRequisition *requisition, gpointer data)
443 {
444         PaneExifData *ped = data;
445         if (requisition->height < ped->min_height)
446                 {
447                 requisition->height = ped->min_height;
448                 }
449 }
450
451 static void bar_pane_exif_size_allocate(GtkWidget *pane, GtkAllocation *alloc, gpointer data)
452 {
453         PaneExifData *ped = data;
454         ped->min_height = alloc->height;
455 }
456
457 GtkWidget *bar_pane_exif_new(const gchar *title, gboolean expanded, gboolean populate)
458 {
459         PaneExifData *ped;
460
461         ped = g_new0(PaneExifData, 1);
462
463         ped->pane.pane_set_fd = bar_pane_exif_set_fd;
464         ped->pane.pane_write_config = bar_pane_exif_write_config;
465         ped->pane.title = gtk_label_new(title);
466         ped->pane.expanded = expanded;
467
468         ped->size_group = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
469         ped->widget = gtk_event_box_new();;
470         ped->vbox = gtk_vbox_new(FALSE, PREF_PAD_GAP);
471         gtk_container_add(GTK_CONTAINER(ped->widget), ped->vbox);
472         gtk_widget_show(ped->vbox);
473
474         ped->min_height = MIN_HEIGHT;
475         g_object_set_data(G_OBJECT(ped->widget), "pane_data", ped);
476         g_signal_connect_after(G_OBJECT(ped->widget), "destroy",
477                                G_CALLBACK(bar_pane_exif_destroy), ped);
478         g_signal_connect(G_OBJECT(ped->widget), "size-request",
479                          G_CALLBACK(bar_pane_exif_size_request), ped);
480         g_signal_connect(G_OBJECT(ped->widget), "size-allocate",
481                          G_CALLBACK(bar_pane_exif_size_allocate), ped);
482         
483         bar_pane_exif_dnd_init(ped->widget);
484         g_signal_connect(ped->widget, "button_press_event", G_CALLBACK(bar_pane_exif_menu_cb), ped);
485
486         if (populate)
487                 {
488                 bar_pane_exif_add_entry(ped, EXIF_FORMATTED("Camera"), NULL, TRUE);
489                 bar_pane_exif_add_entry(ped, EXIF_FORMATTED("DateTime"), NULL, TRUE);
490                 bar_pane_exif_add_entry(ped, EXIF_FORMATTED("ShutterSpeed"), NULL, TRUE);
491                 bar_pane_exif_add_entry(ped, EXIF_FORMATTED("Aperture"), NULL, TRUE);
492                 bar_pane_exif_add_entry(ped, EXIF_FORMATTED("ExposureBias"), NULL, TRUE);
493                 bar_pane_exif_add_entry(ped, EXIF_FORMATTED("ISOSpeedRating"), NULL, TRUE);
494                 bar_pane_exif_add_entry(ped, EXIF_FORMATTED("FocalLength"), NULL, TRUE);
495                 bar_pane_exif_add_entry(ped, EXIF_FORMATTED("FocalLength35mmFilm"), NULL, TRUE);
496                 bar_pane_exif_add_entry(ped, EXIF_FORMATTED("Flash"), NULL, TRUE);
497                 bar_pane_exif_add_entry(ped, "Exif.Photo.ExposureProgram", NULL, TRUE);
498                 bar_pane_exif_add_entry(ped, "Exif.Photo.MeteringMode", NULL, TRUE);
499                 bar_pane_exif_add_entry(ped, "Exif.Photo.LightSource", NULL, TRUE);
500                 bar_pane_exif_add_entry(ped, EXIF_FORMATTED("ColorProfile"), NULL, TRUE);
501                 bar_pane_exif_add_entry(ped, EXIF_FORMATTED("SubjectDistance"), NULL, TRUE);
502                 bar_pane_exif_add_entry(ped, EXIF_FORMATTED("Resolution"), NULL, TRUE);
503                 bar_pane_exif_add_entry(ped, "Exif.Image.Orientation", NULL, TRUE);
504                 bar_pane_exif_add_entry(ped, EXIF_FORMATTED("GPSPosition"), NULL, TRUE);
505                 bar_pane_exif_add_entry(ped, EXIF_FORMATTED("GPSAltitude"), NULL, TRUE);
506                 bar_pane_exif_add_entry(ped, "Exif.Image.ImageDescription", NULL, TRUE);
507                 bar_pane_exif_add_entry(ped, "Exif.Image.Copyright", NULL, TRUE);
508                 }
509         
510         gtk_widget_show(ped->widget);
511
512         return ped->widget;
513 }
514
515 GtkWidget *bar_pane_exif_new_from_config(const gchar **attribute_names, const gchar **attribute_values)
516 {
517         gchar *title = g_strdup(_("NoName"));
518         gboolean expanded = TRUE;
519
520         while (*attribute_names)
521                 {
522                 const gchar *option = *attribute_names++;
523                 const gchar *value = *attribute_values++;
524
525                 if (READ_CHAR_FULL("pane.title", title)) continue;
526                 if (READ_BOOL_FULL("pane.expanded", expanded)) continue;
527                 
528
529                 DEBUG_1("unknown attribute %s = %s", option, value);
530                 }
531         
532         return bar_pane_exif_new(title, expanded, FALSE);
533 }
534
535 void bar_pane_exif_entry_add_from_config(GtkWidget *pane, const gchar **attribute_names, const gchar **attribute_values)
536 {
537         PaneExifData *ped;
538         gchar *key = NULL;
539         gchar *title = NULL;
540         gboolean if_set = TRUE;
541
542         ped = g_object_get_data(G_OBJECT(pane), "pane_data");
543         if (!ped) return;
544
545         while (*attribute_names)
546                 {
547                 const gchar *option = *attribute_names++;
548                 const gchar *value = *attribute_values++;
549
550                 if (READ_CHAR_FULL("key", key)) continue;
551                 if (READ_CHAR_FULL("title", title)) continue;
552                 if (READ_BOOL_FULL("if_set", if_set)) continue;
553                 
554
555                 DEBUG_1("unknown attribute %s = %s", option, value);
556                 }
557         if (key && key[0]) bar_pane_exif_add_entry(ped, key, title, if_set);
558 }
559
560
561 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */