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