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