Trim trailing white spaces.
[geeqie.git] / src / bar_exif.c
index 179d2c2..9c1cc47 100644 (file)
@@ -1,9 +1,9 @@
 /*
  * Geeqie
  * (C) 2004 John Ellis
- * Copyright (C) 2008 - 2009 The Geeqie Team
+ * Copyright (C) 2008 - 2012 The Geeqie Team
  *
- * Author: John Ellis
+ * Author: Vladimir Nadvornik
  *
  * This software is released under the GNU General Public License (GNU GPL).
  * Please read the included file COPYING for more information.
 #include "history_list.h"
 #include "misc.h"
 #include "ui_misc.h"
+#include "ui_menu.h"
 #include "bar.h"
+#include "rcfile.h"
+#include "dnd.h"
+#include "ui_utildlg.h"
 
 
 #include <math.h>
 
-ExifUI ExifUIList[]={
-       { 0, 0, EXIF_UI_IFSET,  EXIF_FORMATTED("Camera")},
-       { 0, 0, EXIF_UI_IFSET,  EXIF_FORMATTED("DateTime")},
-       { 0, 0, EXIF_UI_IFSET,  EXIF_FORMATTED("ShutterSpeed")},
-       { 0, 0, EXIF_UI_IFSET,  EXIF_FORMATTED("Aperture")},
-       { 0, 0, EXIF_UI_IFSET,  EXIF_FORMATTED("ExposureBias")},
-       { 0, 0, EXIF_UI_IFSET,  EXIF_FORMATTED("ISOSpeedRating")},
-       { 0, 0, EXIF_UI_IFSET,  EXIF_FORMATTED("FocalLength")},
-       { 0, 0, EXIF_UI_IFSET,  EXIF_FORMATTED("FocalLength35mmFilm")},
-       { 0, 0, EXIF_UI_IFSET,  EXIF_FORMATTED("Flash")},
-       { 0, 0, EXIF_UI_IFSET,  "Exif.Photo.ExposureProgram"},
-       { 0, 0, EXIF_UI_IFSET,  "Exif.Photo.MeteringMode"},
-       { 0, 0, EXIF_UI_IFSET,  "Exif.Photo.LightSource"},
-       { 0, 0, EXIF_UI_IFSET,  EXIF_FORMATTED("ColorProfile")},
-       { 0, 0, EXIF_UI_IFSET,  EXIF_FORMATTED("SubjectDistance")},
-       { 0, 0, EXIF_UI_IFSET,  EXIF_FORMATTED("Resolution")},
-       { 0, 0, EXIF_UI_IFSET,  "Exif.Image.Orientation"},
-       { 0, 0, EXIF_UI_IFSET,  EXIF_FORMATTED("GPSPosition")},
-       { 0, 0, EXIF_UI_IFSET,  EXIF_FORMATTED("GPSAltitude")},
-       { 0, 0, EXIF_UI_IFSET,  "Exif.Image.ImageDescription"},
-       { 0, 0, EXIF_UI_IFSET,  "Exif.Image.Copyright"},
-       { 0, 0, EXIF_UI_OFF,    NULL}
-};
-
-
+#define MIN_HEIGHT 25
 /*
  *-------------------------------------------------------------------
- * table util
+ * EXIF widget
  *-------------------------------------------------------------------
  */
 
-static void table_add_line_custom(GtkWidget *table, gint x, gint y,
-                                 const gchar *text1, const gchar *text2,
-                                 GtkWidget **label1, GtkWidget **label2,
-                                 GtkWidget **remove)
-{
-       GtkWidget *label;
-       gchar *buf;
-
-       buf = g_strconcat((text1) ? text1 : "fixme", ":", NULL);
-       if (!text2) text2 = "";
-
-       label = gtk_label_new(buf);
-       gtk_misc_set_alignment(GTK_MISC(label), 1.0, 0.0);
-       pref_label_bold(label, TRUE, FALSE);
-       gtk_table_attach(GTK_TABLE(table), label,
-                        x + 1, x + 2, y, y + 1,
-                        GTK_FILL, GTK_FILL,
-                        2, 2);
-       *label1 = label;
-
-       label = gtk_label_new(text2);
-       gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
-       gtk_table_attach(GTK_TABLE(table), label,
-                        x + 2, x + 3, y, y + 1,
-                        GTK_FILL, GTK_FILL,
-                        2, 2);
-       *label2 = label;
-
-       if (remove)
-               {
-               *remove = gtk_check_button_new();
-               gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(*remove), TRUE);
-
-               gtk_table_attach(GTK_TABLE(table), *remove,
-                                x, x + 1, y, y + 1,
-                                GTK_FILL, GTK_FILL,
-                                2, 2);
-               }
-
-       g_free(buf);
-}
+typedef struct _ExifEntry ExifEntry;
+typedef struct _PaneExifData PaneExifData;
 
-static GtkWidget *table_add_line(GtkWidget *table, gint x, gint y,
-                                const gchar *description, const gchar *text,
-                                GtkWidget **keyret)
+struct _ExifEntry
 {
-       GtkWidget *key;
-       GtkWidget *label;
-
-       table_add_line_custom(table, x, y, description, text, &key, &label, NULL);
-       gtk_widget_show(key);
-       gtk_widget_show(label);
-       if (keyret) *keyret = key;
+       GtkWidget *ebox;
+       GtkWidget *box;
+       GtkWidget *title_label;
+       GtkWidget *value_widget;
 
-       return label;
-}
+       gchar *key;
+       gchar *title;
+       gboolean if_set;
+       gboolean auto_title;
+       gboolean editable;
 
-
-/*
- *-------------------------------------------------------------------
- * EXIF widget
- *-------------------------------------------------------------------
- */
-
-typedef struct _PaneExifData PaneExifData;
+       PaneExifData *ped;
+};
+       
+       
 struct _PaneExifData
 {
        PaneData pane;
        GtkWidget *vbox;
-       GtkWidget *scrolled;
-       GtkWidget *table;
-       GtkWidget **keys;
-       GtkWidget **labels;
-
-       GtkWidget *custom_sep;
-       GtkWidget *custom_name[EXIF_BAR_CUSTOM_COUNT];
-       GtkWidget *custom_value[EXIF_BAR_CUSTOM_COUNT];
-       GtkWidget *custom_remove[EXIF_BAR_CUSTOM_COUNT];
+       GtkWidget *widget;
+       GtkSizeGroup *size_group;
 
+       gint min_height;
+       
+       gboolean all_hidden;
+       gboolean show_all;
+       
        FileData *fd;
+};
 
-       gint allow_search;
+typedef struct _ConfDialogData ConfDialogData;
+struct _ConfDialogData
+{
+       GtkWidget *widget; /* pane or entry, devidet by presenceof "pane_data" or "entry_data" */
+
+       /* dialog parts */
+       GenericDialog *gd;
+       GtkWidget *key_entry;
+       GtkWidget *title_entry;
+       gboolean if_set;
+       gboolean editable;
 };
 
-static void bar_pane_exif_sensitive(PaneExifData *ped, gint enable)
+static void bar_pane_exif_entry_dnd_init(GtkWidget *entry);
+static void bar_pane_exif_entry_update_title(ExifEntry *ee);
+static void bar_pane_exif_update(PaneExifData *ped);
+static gboolean bar_pane_exif_menu_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data);
+static void bar_pane_exif_notify_cb(FileData *fd, NotifyType type, gpointer data);
+
+
+static void bar_pane_exif_entry_changed(GtkEntry *text_entry, gpointer data)
 {
-       gtk_widget_set_sensitive(ped->table, enable);
+       ExifEntry *ee = data;
+       gchar *text;
+       if (!ee->ped->fd) return;
+
+       text = text_widget_text_pull(ee->value_widget);
+       metadata_write_string(ee->ped->fd, ee->key, text);
+       g_free(text);
 }
 
-static void bar_pane_exif_update(PaneExifData *ped)
+static void bar_pane_exif_entry_destroy(GtkWidget *widget, gpointer data)
+{
+       ExifEntry *ee = data;
+
+       g_free(ee->key);
+       g_free(ee->title);
+       g_free(ee);
+}
+
+static void bar_pane_exif_setup_entry_box(PaneExifData *ped, ExifEntry *ee)
 {
-       ExifData *exif;
-       gint i;
-       GList *list;
+       gboolean horizontal = !ee->editable;
+       gboolean editable = ee->editable;
+
+       if (ee->box) gtk_widget_destroy(ee->box);
+
+       ee->box = horizontal ? gtk_hbox_new(FALSE, 0) : gtk_vbox_new(FALSE, 0);
+       gtk_container_add(GTK_CONTAINER(ee->ebox), ee->box);
+       gtk_widget_show(ee->box);
 
-       /* do we have any exif at all ? */
-       exif = exif_read_fd(ped->fd);
+       ee->title_label = gtk_label_new(NULL);
+       gtk_misc_set_alignment(GTK_MISC(ee->title_label), horizontal ? 1.0 : 0.0, 0.5);
+       gtk_size_group_add_widget(ped->size_group, ee->title_label);
+       gtk_box_pack_start(GTK_BOX(ee->box), ee->title_label, FALSE, TRUE, 0);
+       gtk_widget_show(ee->title_label);
+
+       if (editable)
+               {
+               ee->value_widget = gtk_entry_new();
+               g_signal_connect(G_OBJECT(ee->value_widget), "changed",
+                        G_CALLBACK(bar_pane_exif_entry_changed), ee);
+
+               }
+       else
+               {
+               ee->value_widget = gtk_label_new(NULL);
+//             gtk_label_set_width_chars(GTK_LABEL(ee->value_widget), 20);
+               gtk_label_set_ellipsize(GTK_LABEL(ee->value_widget), PANGO_ELLIPSIZE_END);
+//             gtk_widget_set_size_request(ee->value_widget, 100, -1);
+               gtk_misc_set_alignment(GTK_MISC(ee->value_widget), 0.0, 0.5);
+               }
+               
+       gtk_box_pack_start(GTK_BOX(ee->box), ee->value_widget, TRUE, TRUE, 1);
+       gtk_widget_show(ee->value_widget);
+}
 
-       if (!exif)
+static GtkWidget *bar_pane_exif_add_entry(PaneExifData *ped, const gchar *key, const gchar *title, gboolean if_set, gboolean editable)
+{
+       ExifEntry *ee = g_new0(ExifEntry, 1);
+       
+       ee->key = g_strdup(key);
+       if (title && title[0])
                {
-               bar_pane_exif_sensitive(ped, FALSE);
-               return;
+               ee->title = g_strdup(title);
                }
        else
                {
-               /* we will use high level functions so we can release it for now.
-                  it will stay in the cache */
-               exif_free_fd(ped->fd, exif);
-               exif = NULL;
+               ee->title = exif_get_description_by_key(key);
+               ee->auto_title = TRUE;
                }
+               
+       ee->if_set = if_set;
+       ee->editable = editable;
+       
+       ee->ped = ped;
        
+       ee->ebox = gtk_event_box_new();
+       g_object_set_data(G_OBJECT(ee->ebox), "entry_data", ee);
+       g_signal_connect_after(G_OBJECT(ee->ebox), "destroy",
+                              G_CALLBACK(bar_pane_exif_entry_destroy), ee);
+       
+       gtk_box_pack_start(GTK_BOX(ped->vbox), ee->ebox, FALSE, FALSE, 0);
 
-       bar_pane_exif_sensitive(ped, TRUE);
+       bar_pane_exif_entry_dnd_init(ee->ebox);
+       g_signal_connect(ee->ebox, "button_release_event", G_CALLBACK(bar_pane_exif_menu_cb), ped);
+       
+       bar_pane_exif_setup_entry_box(ped, ee);
+        
+       bar_pane_exif_entry_update_title(ee);
+       bar_pane_exif_update(ped);
+       
+       return ee->ebox;
+}
 
-       for (i = 0; ExifUIList[i].key; i++)
-               {
-               gchar *text;
+static void bar_pane_exif_reparent_entry(GtkWidget *entry, GtkWidget *pane)
+{
+       PaneExifData *ped = g_object_get_data(G_OBJECT(pane), "pane_data");
+       PaneExifData *old_ped;
+       ExifEntry *ee = g_object_get_data(G_OBJECT(entry), "entry_data");
+       
+       if (!ped || !ee) return;
+       
+       old_ped = ee->ped;
+       
+       g_object_ref(entry);
+       
+       gtk_size_group_remove_widget(old_ped->size_group, ee->title_label);
+       gtk_container_remove(GTK_CONTAINER(old_ped->vbox), entry);
+       
+       ee->ped = ped;
+       gtk_size_group_add_widget(ped->size_group, ee->title_label);
+       gtk_box_pack_start(GTK_BOX(ped->vbox), entry, FALSE, FALSE, 0);
+}
+
+static void bar_pane_exif_entry_update_title(ExifEntry *ee)
+{
+       gchar *markup;
+
+       markup = g_markup_printf_escaped("<span size='small'>%s:</span>", (ee->title) ? ee->title : _("<empty label, fixme>"));
+       gtk_label_set_markup(GTK_LABEL(ee->title_label), markup);
+       g_free(markup);
+}
 
-               if (ExifUIList[i].current == EXIF_UI_OFF)
+static void bar_pane_exif_update_entry(PaneExifData *ped, GtkWidget *entry, gboolean update_title)
+{
+       gchar *text;
+       ExifEntry *ee = g_object_get_data(G_OBJECT(entry), "entry_data");
+       
+       if (!ee) return;
+       text = metadata_read_string(ped->fd, ee->key, ee->editable ? METADATA_PLAIN : METADATA_FORMATTED);
+
+       if (!ped->show_all && ee->if_set && !ee->editable && (!text || !*text))
+               {
+               gtk_label_set_text(GTK_LABEL(ee->value_widget), NULL);
+               gtk_widget_hide(entry);
+               }
+       else
+               {
+               if (ee->editable)
                        {
-                       gtk_widget_hide(ped->labels[i]);
-                       gtk_widget_hide(ped->keys[i]);
-                       continue;
+                       g_signal_handlers_block_by_func(ee->value_widget, bar_pane_exif_entry_changed, ee);
+                       gtk_entry_set_text(GTK_ENTRY(ee->value_widget), text ? text : "");
+                       g_signal_handlers_unblock_by_func(ee->value_widget, bar_pane_exif_entry_changed, ee);
+                       gtk_widget_set_tooltip_text(ee->box, NULL);
                        }
-               text =  metadata_read_string(ped->fd, ExifUIList[i].key, METADATA_FORMATTED);
-               if (ExifUIList[i].current == EXIF_UI_IFSET
-                   && (!text || !*text))
+               else
                        {
-                       gtk_widget_hide(ped->labels[i]);
-                       gtk_widget_hide(ped->keys[i]);
-                       g_free(text);
-                       continue;
+                       gtk_label_set_text(GTK_LABEL(ee->value_widget), text);
+                       gtk_widget_set_tooltip_text(ee->box, text);
                        }
-               gtk_widget_show(ped->labels[i]);
-               gtk_widget_show(ped->keys[i]);
-               gtk_label_set_text(GTK_LABEL(ped->labels[i]), text);
-               g_free(text);
+               gtk_widget_show(entry);
+               ped->all_hidden = FALSE;
                }
+               
+       g_free(text);
+       
+       if (update_title) bar_pane_exif_entry_update_title(ee);
+}
+
+static void bar_pane_exif_update(PaneExifData *ped)
+{
+       GList *list, *work;
+
+       ped->all_hidden = TRUE;
 
-       list = g_list_last(history_list_get_by_key("exif_extras"));
-       if (list)
+       list = gtk_container_get_children(GTK_CONTAINER(ped->vbox));
+       work = list;
+       while (work)
                {
-               gtk_widget_show(ped->custom_sep);
+               GtkWidget *entry = work->data;
+               work = work->next;
+               
+               bar_pane_exif_update_entry(ped, entry, FALSE);
                }
-       else
+       g_list_free(list);
+
+       gtk_widget_set_sensitive(ped->pane.title, !ped->all_hidden);
+}
+
+void bar_pane_exif_set_fd(GtkWidget *widget, FileData *fd)
+{
+       PaneExifData *ped;
+
+       ped = g_object_get_data(G_OBJECT(widget), "pane_data");
+       if (!ped) return;
+
+       file_data_unref(ped->fd);
+       ped->fd = file_data_ref(fd);
+
+       bar_pane_exif_update(ped);
+}
+
+gint bar_pane_exif_event(GtkWidget *bar, GdkEvent *event)
+{
+       PaneExifData *ped;
+       gboolean ret = FALSE;
+       GList *list, *work;
+
+       ped = g_object_get_data(G_OBJECT(bar), "pane_data");
+       if (!ped) return FALSE;
+
+       list = gtk_container_get_children(GTK_CONTAINER(ped->vbox));
+       work = list;
+       while (!ret && work)
                {
-               gtk_widget_hide(ped->custom_sep);
+               GtkWidget *entry = work->data;
+               ExifEntry *ee = g_object_get_data(G_OBJECT(entry), "entry_data");
+               work = work->next;
+
+               if (ee->editable && gtk_widget_has_focus(ee->value_widget)) ret = gtk_widget_event(ee->value_widget, event);
                }
-       i = 0;
-       while (list && i < EXIF_BAR_CUSTOM_COUNT)
+       g_list_free(list);
+       return ret;
+}
+
+static void bar_pane_exif_notify_cb(FileData *fd, NotifyType type, gpointer data)
+{
+       PaneExifData *ped = data;
+       if ((type & (NOTIFY_REREAD | NOTIFY_CHANGE | NOTIFY_METADATA)) && fd == ped->fd)
                {
-               gchar *text;
-               gchar *name;
-               gchar *buf;
-               gchar *description;
+               DEBUG_1("Notify pane_exif: %s %04x", fd->path, type);
+               bar_pane_exif_update(ped);
+               }
+}
 
-               name = list->data;
-               list = list->prev;
-               
-               text =  metadata_read_string(ped->fd, name, METADATA_FORMATTED);
 
-               description = exif_get_tag_description_by_key(name);
-               if (!description || *description == '\0') 
-                       {
-                       g_free(description);
-                       description = g_strdup(name);
-                       }
-               buf = g_strconcat(description, ":", NULL);
-               g_free(description);
-               
-               gtk_label_set_text(GTK_LABEL(ped->custom_name[i]), buf);
-               g_free(buf);
-               gtk_label_set_text(GTK_LABEL(ped->custom_value[i]), text);
-               g_free(text);
-
-               gtk_widget_show(ped->custom_name[i]);
-               gtk_widget_show(ped->custom_value[i]);
-               g_object_set_data(G_OBJECT(ped->custom_remove[i]), "key", name);
-               gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(ped->custom_remove[i]), TRUE);
-               gtk_widget_show(ped->custom_remove[i]);
-
-               i++;
+/*
+ *-------------------------------------------------------------------
+ * dnd
+ *-------------------------------------------------------------------
+ */
+
+static GtkTargetEntry bar_pane_exif_drag_types[] = {
+       { TARGET_APP_EXIF_ENTRY_STRING, GTK_TARGET_SAME_APP, TARGET_APP_EXIF_ENTRY },
+       { "text/plain", 0, TARGET_TEXT_PLAIN }
+};
+static gint n_exif_entry_drag_types = 2;
+
+static GtkTargetEntry bar_pane_exif_drop_types[] = {
+       { TARGET_APP_EXIF_ENTRY_STRING, GTK_TARGET_SAME_APP, TARGET_APP_EXIF_ENTRY },
+       { "text/plain", 0, TARGET_TEXT_PLAIN }
+};
+static gint n_exif_entry_drop_types = 2;
+
+
+static void bar_pane_exif_entry_dnd_get(GtkWidget *entry, GdkDragContext *context,
+                                    GtkSelectionData *selection_data, guint info,
+                                    guint time, gpointer data)
+{
+       ExifEntry *ee = g_object_get_data(G_OBJECT(entry), "entry_data");
+
+       switch (info)
+               {
+               case TARGET_APP_EXIF_ENTRY:
+                       gtk_selection_data_set(selection_data, gtk_selection_data_get_target(selection_data),
+                                              8, (gpointer) &entry, sizeof(entry));
+                       break;
+
+               case TARGET_TEXT_PLAIN:
+               default:
+                       gtk_selection_data_set_text(selection_data, ee->key, -1);
+                       break;
                }
-       while (i < EXIF_BAR_CUSTOM_COUNT)
+       
+}
+
+static void bar_pane_exif_dnd_receive(GtkWidget *pane, GdkDragContext *context,
+                                         gint x, gint y,
+                                         GtkSelectionData *selection_data, guint info,
+                                         guint time, gpointer data)
+{
+       PaneExifData *ped;
+       GList *work, *list;
+       gint pos;
+       GtkWidget *new_entry = NULL;
+       
+       ped = g_object_get_data(G_OBJECT(pane), "pane_data");
+       if (!ped) return;
+
+       switch (info)
                {
-               g_object_set_data(G_OBJECT(ped->custom_remove[i]), "key", NULL);
-               gtk_widget_hide(ped->custom_name[i]);
-               gtk_widget_hide(ped->custom_value[i]);
-               gtk_widget_hide(ped->custom_remove[i]);
+               case TARGET_APP_EXIF_ENTRY:
+                       new_entry = *(gpointer *)gtk_selection_data_get_data(selection_data);
+                       
+                       if (gtk_widget_get_parent(new_entry) && gtk_widget_get_parent(new_entry) != ped->vbox) bar_pane_exif_reparent_entry(new_entry, pane);
+                       
+                       break;
+               default:
+                       /* FIXME: this needs a check for valid exif keys */
+                       new_entry = bar_pane_exif_add_entry(ped, (gchar *)gtk_selection_data_get_data(selection_data), NULL, TRUE, FALSE);
+                       break;
+               }
 
-               i++;
+       list = gtk_container_get_children(GTK_CONTAINER(ped->vbox));
+       work = list;
+       pos = 0;
+       while (work)
+               {
+               gint nx, ny;
+               GtkWidget *entry = work->data;
+               GtkAllocation allocation;
+               work = work->next;
+               
+               if (entry == new_entry) continue;
+               
+               gtk_widget_get_allocation(entry, &allocation);
+               
+               if (gtk_widget_is_drawable(entry) &&
+                   gtk_widget_translate_coordinates(pane, entry, x, y, &nx, &ny) &&
+                   ny < allocation.height / 2) break;
+               pos++;
                }
+       g_list_free(list);
+
+       gtk_box_reorder_child(GTK_BOX(ped->vbox), new_entry, pos);
+}
+
+static void bar_pane_exif_entry_dnd_begin(GtkWidget *entry, GdkDragContext *context, gpointer data)
+{
+       ExifEntry *ee = g_object_get_data(G_OBJECT(entry), "entry_data");
+       
+       if (!ee) return;
+       dnd_set_drag_label(entry, context, ee->key);
+}
+
+static void bar_pane_exif_entry_dnd_end(GtkWidget *widget, GdkDragContext *context, gpointer data)
+{
 }
 
-static void bar_pane_exif_clear(PaneExifData *ped)
+static void bar_pane_exif_entry_dnd_init(GtkWidget *entry)
 {
-       gint i;
+       ExifEntry *ee = g_object_get_data(G_OBJECT(entry), "entry_data");
+
+       gtk_drag_source_set(entry, GDK_BUTTON1_MASK | GDK_BUTTON2_MASK,
+                           bar_pane_exif_drag_types, n_exif_entry_drag_types,
+                           GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK);
+       g_signal_connect(G_OBJECT(entry), "drag_data_get",
+                        G_CALLBACK(bar_pane_exif_entry_dnd_get), ee);
+
+       g_signal_connect(G_OBJECT(entry), "drag_begin",
+                        G_CALLBACK(bar_pane_exif_entry_dnd_begin), ee);
+       g_signal_connect(G_OBJECT(entry), "drag_end",
+                        G_CALLBACK(bar_pane_exif_entry_dnd_end), ee);
+}
 
-       if (!GTK_WIDGET_SENSITIVE(ped->labels[0])) return;
+static void bar_pane_exif_dnd_init(GtkWidget *pane)
+{
+       gtk_drag_dest_set(pane,
+                         GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_HIGHLIGHT | GTK_DEST_DEFAULT_DROP,
+                         bar_pane_exif_drop_types, n_exif_entry_drop_types,
+                         GDK_ACTION_COPY | GDK_ACTION_MOVE);
+       g_signal_connect(G_OBJECT(pane), "drag_data_received",
+                        G_CALLBACK(bar_pane_exif_dnd_receive), NULL);
+}
 
-       for (i = 0; ExifUIList[i].key; i++)
+static void bar_pane_exif_edit_close_cb(GtkWidget *widget, gpointer data)
+{
+       GenericDialog *gd = data;
+       generic_dialog_close(gd);
+}
+
+static void bar_pane_exif_edit_destroy_cb(GtkWidget *widget, gpointer data)
+{
+       ConfDialogData *cdd = data;
+       g_signal_handlers_disconnect_by_func(cdd->widget, G_CALLBACK(bar_pane_exif_edit_close_cb), cdd->gd);
+       g_free(cdd);
+}
+
+static void bar_pane_exif_edit_cancel_cb(GenericDialog *gd, gpointer data)
+{
+}
+
+static void bar_pane_exif_edit_ok_cb(GenericDialog *gd, gpointer data)
+{
+       ConfDialogData *cdd = data;
+       
+       /* either one or the other */
+       PaneExifData *ped = g_object_get_data(G_OBJECT(cdd->widget), "pane_data");
+       ExifEntry *ee = g_object_get_data(G_OBJECT(cdd->widget), "entry_data");
+
+       if (ped)
                {
-               gtk_label_set_text(GTK_LABEL(ped->labels[i]), "");
+               bar_pane_exif_add_entry(ped,
+                                       gtk_entry_get_text(GTK_ENTRY(cdd->key_entry)),
+                                       gtk_entry_get_text(GTK_ENTRY(cdd->title_entry)),
+                                       cdd->if_set, cdd->editable);
                }
-       for (i = 0; i < EXIF_BAR_CUSTOM_COUNT; i++)
+
+       if (ee)
                {
-               gtk_label_set_text(GTK_LABEL(ped->custom_value[i]), "");
+               const gchar *title;
+               GtkWidget *pane = gtk_widget_get_parent(cdd->widget);
+               
+               while (pane)
+                       {
+                       ped = g_object_get_data(G_OBJECT(pane), "pane_data");
+                       if (ped) break;
+                       pane = gtk_widget_get_parent(pane);
+                       }
+               
+               if (!pane) return;
+               
+               g_free(ee->key);
+               ee->key = g_strdup(gtk_entry_get_text(GTK_ENTRY(cdd->key_entry)));
+               title = gtk_entry_get_text(GTK_ENTRY(cdd->title_entry));
+               if (!title || strlen(title) == 0)
+                       {
+                       g_free(ee->title);
+                       ee->title = exif_get_description_by_key(ee->key);
+                       ee->auto_title = TRUE;
+                       }
+               else if (!ee->title || strcmp(ee->title, title) != 0)
+                       {
+                       g_free(ee->title);
+                       ee->title = g_strdup(title);
+                       ee->auto_title = FALSE;
+                       }
+               
+               ee->if_set = cdd->if_set;
+               ee->editable = cdd->editable;
+               
+               bar_pane_exif_setup_entry_box(ped, ee);
+
+               bar_pane_exif_entry_update_title(ee);
+               bar_pane_exif_update(ped);
                }
 }
 
-void bar_pane_exif_set_fd(GtkWidget *widget, FileData *fd)
+static void bar_pane_exif_conf_dialog(GtkWidget *widget)
 {
-       PaneExifData *ped;
+       ConfDialogData *cdd;
+       GenericDialog *gd;
+       GtkWidget *table;
 
-       ped = g_object_get_data(G_OBJECT(widget), "pane_data");
-       if (!ped) return;
+       /* the widget can be either ExifEntry (for editing) or Pane (for new entry)
+          we can decide it by the attached data */
+       ExifEntry *ee = g_object_get_data(G_OBJECT(widget), "entry_data");
 
-       /* store this, advanced view toggle needs to reload data */
-       file_data_unref(ped->fd);
-       ped->fd = file_data_ref(fd);
+       cdd = g_new0(ConfDialogData, 1);
+       
+       cdd->widget = widget;
+
+
+       cdd->if_set = ee ? ee->if_set : TRUE;
+       cdd->editable = ee ? ee->editable : FALSE;
+       
+       cdd->gd = gd = generic_dialog_new(ee ? _("Configure entry") : _("Add entry"), "exif_entry_edit",
+                               widget, TRUE,
+                               bar_pane_exif_edit_cancel_cb, cdd);
+       g_signal_connect(G_OBJECT(gd->dialog), "destroy",
+                        G_CALLBACK(bar_pane_exif_edit_destroy_cb), cdd);
+
+       /* in case the entry is deleted during editing */
+       g_signal_connect(G_OBJECT(widget), "destroy",
+                        G_CALLBACK(bar_pane_exif_edit_close_cb), gd);
+
+       generic_dialog_add_message(gd, NULL, ee ? _("Configure entry") : _("Add entry"), NULL);
+
+       generic_dialog_add_button(gd, GTK_STOCK_OK, NULL,
+                                 bar_pane_exif_edit_ok_cb, TRUE);
 
-       bar_pane_exif_clear(ped);
+       table = pref_table_new(gd->vbox, 3, 2, FALSE, TRUE);
+       pref_table_label(table, 0, 0, _("Key:"), 1.0);
+
+       cdd->key_entry = gtk_entry_new();
+       gtk_widget_set_size_request(cdd->key_entry, 300, -1);
+       if (ee) gtk_entry_set_text(GTK_ENTRY(cdd->key_entry), ee->key);
+       gtk_table_attach_defaults(GTK_TABLE(table), cdd->key_entry, 1, 2, 0, 1);
+       generic_dialog_attach_default(gd, cdd->key_entry);
+       gtk_widget_show(cdd->key_entry);
+
+       pref_table_label(table, 0, 1, _("Title:"), 1.0);
+
+       cdd->title_entry = gtk_entry_new();
+       gtk_widget_set_size_request(cdd->title_entry, 300, -1);
+       if (ee) gtk_entry_set_text(GTK_ENTRY(cdd->title_entry), ee->title);
+       gtk_table_attach_defaults(GTK_TABLE(table), cdd->title_entry, 1, 2, 1, 2);
+       generic_dialog_attach_default(gd, cdd->title_entry);
+       gtk_widget_show(cdd->title_entry);
+
+       pref_checkbox_new_int(gd->vbox, _("Show only if set"), cdd->if_set, &cdd->if_set);
+       pref_checkbox_new_int(gd->vbox, _("Editable (supported only for XMP)"), cdd->editable, &cdd->editable);
+
+       gtk_widget_show(gd->dialog);
+}
+
+static void bar_pane_exif_conf_dialog_cb(GtkWidget *menu_widget, gpointer data)
+{
+       GtkWidget *widget = data;
+       bar_pane_exif_conf_dialog(widget);
+}
+
+static void bar_pane_exif_delete_entry_cb(GtkWidget *menu_widget, gpointer data)
+{
+       GtkWidget *entry = data;
+       gtk_widget_destroy(entry);
+}
+
+static void bar_pane_exif_toggle_show_all_cb(GtkWidget *menu_widget, gpointer data)
+{
+       PaneExifData *ped = data;
+       ped->show_all = !ped->show_all;
        bar_pane_exif_update(ped);
 }
 
-static void bar_pane_exif_remove_advanced_cb(GtkWidget *widget, gpointer data)
+static void bar_pane_exif_menu_popup(GtkWidget *widget, PaneExifData *ped)
+{
+       GtkWidget *menu;
+       /* the widget can be either ExifEntry (for editing) or Pane (for new entry)
+          we can decide it by the attached data */
+       ExifEntry *ee = g_object_get_data(G_OBJECT(widget), "entry_data");
+
+       menu = popup_menu_short_lived();
+
+       if (ee)
+               {
+               /* for the entry */
+               gchar *conf = g_strdup_printf(_("Configure \"%s\""), ee->title);
+               gchar *del = g_strdup_printf(_("Remove \"%s\""), ee->title);
+               
+               menu_item_add_stock(menu, conf, GTK_STOCK_EDIT, G_CALLBACK(bar_pane_exif_conf_dialog_cb), widget);
+               menu_item_add_stock(menu, del, GTK_STOCK_DELETE, G_CALLBACK(bar_pane_exif_delete_entry_cb), widget);
+               menu_item_add_divider(menu);
+               
+               g_free(conf);
+               g_free(del);
+               }
+
+       /* for the pane */
+       menu_item_add_stock(menu, _("Add entry"), GTK_STOCK_ADD, G_CALLBACK(bar_pane_exif_conf_dialog_cb), ped->widget);
+       menu_item_add_check(menu, _("Show hidden entries"), ped->show_all, G_CALLBACK(bar_pane_exif_toggle_show_all_cb), ped);
+       
+       gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, 0, GDK_CURRENT_TIME);
+}
+
+static gboolean bar_pane_exif_menu_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data)
 {
        PaneExifData *ped = data;
-       const gchar *key;
+       if (bevent->button == MOUSE_BUTTON_RIGHT)
+               {
+               bar_pane_exif_menu_popup(widget, ped);
+               return TRUE;
+               }
+       return FALSE;
+}
 
-       /* continue only if the toggle was deactivated */
-       if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget))) return;
 
-       key = g_object_get_data(G_OBJECT(widget), "key");
-       if (!key) return;
 
-       history_list_item_change("exif_extras", key, NULL);
+static void bar_pane_exif_entry_write_config(GtkWidget *entry, GString *outstr, gint indent)
+{
+       ExifEntry *ee = g_object_get_data(G_OBJECT(entry), "entry_data");
+       if (!ee) return;
+
+       WRITE_NL(); WRITE_STRING("<entry ");
+       WRITE_CHAR(*ee, key);
+       if (!ee->auto_title) WRITE_CHAR(*ee, title);
+       WRITE_BOOL(*ee, if_set);
+       WRITE_BOOL(*ee, editable);
+       WRITE_STRING("/>");
+}
 
-       bar_pane_exif_update(ped);
+static void bar_pane_exif_write_config(GtkWidget *pane, GString *outstr, gint indent)
+{
+       PaneExifData *ped;
+       GList *work, *list;
+       
+       ped = g_object_get_data(G_OBJECT(pane), "pane_data");
+       if (!ped) return;
+
+       WRITE_NL(); WRITE_STRING("<pane_exif ");
+       write_char_option(outstr, indent, "id", ped->pane.id);
+       write_char_option(outstr, indent, "title", gtk_label_get_text(GTK_LABEL(ped->pane.title)));
+       WRITE_BOOL(ped->pane, expanded);
+       WRITE_STRING(">");
+       indent++;
+       
+       list = gtk_container_get_children(GTK_CONTAINER(ped->vbox));
+       work = list;
+       while (work)
+               {
+               GtkWidget *entry = work->data;
+               work = work->next;
+               
+               bar_pane_exif_entry_write_config(entry, outstr, indent);
+               }
+       g_list_free(list);
+       indent--;
+       WRITE_NL(); WRITE_STRING("</pane_exif>");
 }
 
+
 void bar_pane_exif_close(GtkWidget *widget)
 {
        PaneExifData *ped;
@@ -311,88 +697,161 @@ static void bar_pane_exif_destroy(GtkWidget *widget, gpointer data)
 {
        PaneExifData *ped = data;
 
-       g_free(ped->keys);
-       g_free(ped->labels);
+       file_data_unregister_notify_func(bar_pane_exif_notify_cb, ped);
+       g_object_unref(ped->size_group);
        file_data_unref(ped->fd);
+       g_free(ped->pane.id);
        g_free(ped);
 }
 
-GtkWidget *bar_pane_exif_new(const gchar *title)
+#if !GTK_CHECK_VERSION(3,0,0)
+static void bar_pane_exif_size_request(GtkWidget *pane, GtkRequisition *requisition, gpointer data)
 {
-       PaneExifData *ped;
-       GtkWidget *table;
-       GtkWidget *viewport;
-       GtkWidget *hbox;
-       gint i;
-       gint exif_len;
+       PaneExifData *ped = data;
+       if (requisition->height < ped->min_height)
+               {
+               requisition->height = ped->min_height;
+               }
+}
+#endif
+
+static void bar_pane_exif_size_allocate(GtkWidget *pane, GtkAllocation *alloc, gpointer data)
+{
+       PaneExifData *ped = data;
+       ped->min_height = alloc->height;
+#if GTK_CHECK_VERSION(3,0,0)
+       gtk_widget_set_size_request(ped->widget, -1, ped->min_height);
+#endif
+}
 
-       for (exif_len = 0; ExifUIList[exif_len].key; exif_len++)
-             ;
+static GtkWidget *bar_pane_exif_new(const gchar *id, const gchar *title, gboolean expanded)
+{
+       PaneExifData *ped;
 
        ped = g_new0(PaneExifData, 1);
 
        ped->pane.pane_set_fd = bar_pane_exif_set_fd;
-       ped->pane.title = g_strdup(title);
-
-       ped->keys = g_new0(GtkWidget *, exif_len);
-       ped->labels = g_new0(GtkWidget *, exif_len);
-
+       ped->pane.pane_write_config = bar_pane_exif_write_config;
+       ped->pane.pane_event = bar_pane_exif_event;
+       ped->pane.title = bar_pane_expander_title(title);
+       ped->pane.id = g_strdup(id);
+       ped->pane.expanded = expanded;
+       ped->pane.type = PANE_EXIF;
+
+       ped->size_group = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
+       ped->widget = gtk_event_box_new();
        ped->vbox = gtk_vbox_new(FALSE, PREF_PAD_GAP);
-       g_object_set_data(G_OBJECT(ped->vbox), "pane_data", ped);
-       g_signal_connect_after(G_OBJECT(ped->vbox), "destroy",
+       gtk_container_add(GTK_CONTAINER(ped->widget), ped->vbox);
+       gtk_widget_show(ped->vbox);
+
+       ped->min_height = MIN_HEIGHT;
+       g_object_set_data(G_OBJECT(ped->widget), "pane_data", ped);
+       g_signal_connect_after(G_OBJECT(ped->widget), "destroy",
                               G_CALLBACK(bar_pane_exif_destroy), ped);
+#if GTK_CHECK_VERSION(3,0,0)
+       gtk_widget_set_size_request(ped->widget, -1, ped->min_height);
+#else
+       g_signal_connect(G_OBJECT(ped->widget), "size-request",
+                        G_CALLBACK(bar_pane_exif_size_request), ped);
+#endif
+       g_signal_connect(G_OBJECT(ped->widget), "size-allocate",
+                        G_CALLBACK(bar_pane_exif_size_allocate), ped);
+       
+       bar_pane_exif_dnd_init(ped->widget);
+       g_signal_connect(ped->widget, "button_release_event", G_CALLBACK(bar_pane_exif_menu_cb), ped);
 
+       file_data_register_notify_func(bar_pane_exif_notify_cb, ped, NOTIFY_PRIORITY_LOW);
 
-       table = gtk_table_new(3, exif_len + 1 + EXIF_BAR_CUSTOM_COUNT, FALSE);
+       gtk_widget_show(ped->widget);
+
+       return ped->widget;
+}
 
-       ped->table = table;
+GtkWidget *bar_pane_exif_new_from_config(const gchar **attribute_names, const gchar **attribute_values)
+{
+       gchar *title = NULL;
+       gchar *id = g_strdup("exif");
+       gboolean expanded = TRUE;
+       GtkWidget *ret;
 
-       for (i = 0; ExifUIList[i].key; i++)
+       while (*attribute_names)
                {
-               gchar *text;
+               const gchar *option = *attribute_names++;
+               const gchar *value = *attribute_values++;
+
+               if (READ_CHAR_FULL("id", id)) continue;
+               if (READ_CHAR_FULL("title", title)) continue;
+               if (READ_BOOL_FULL("expanded", expanded)) continue;
 
-               text = exif_get_description_by_key(ExifUIList[i].key);
-               ped->labels[i] = table_add_line(table, 0, i, text, NULL,
-                     &ped->keys[i]);
-               g_free(text);
+               log_printf("unknown attribute %s = %s\n", option, value);
                }
+       
+       bar_pane_translate_title(PANE_EXIF, id, &title);
+       ret = bar_pane_exif_new(id, title, expanded);
+       g_free(title);
+       g_free(id);
+       return ret;
+}
 
-       ped->custom_sep = gtk_hseparator_new();
-       gtk_table_attach(GTK_TABLE(table), ped->custom_sep, 0, 3,
-                                          exif_len, exif_len + 1,
-                                          GTK_FILL, GTK_FILL, 2, 2);
+void bar_pane_exif_update_from_config(GtkWidget *pane, const gchar **attribute_names, const gchar **attribute_values)
+{
+       PaneExifData *ped;
+       gchar *title = NULL;
+
+       ped = g_object_get_data(G_OBJECT(pane), "pane_data");
+       if (!ped) return;
 
-       for (i = 0; i < EXIF_BAR_CUSTOM_COUNT; i++)
+       while (*attribute_names)
                {
-               table_add_line_custom(table, 0, exif_len + 1 + i,
-                                     "", "",  &ped->custom_name[i], &ped->custom_value[i],
-                                     &ped->custom_remove[i]);
-               g_signal_connect(G_OBJECT(ped->custom_remove[i]), "clicked", 
-                                G_CALLBACK(bar_pane_exif_remove_advanced_cb), ped);
-               }
+               const gchar *option = *attribute_names++;
+               const gchar *value = *attribute_values++;
 
-       ped->scrolled = gtk_scrolled_window_new(NULL, NULL);
-       gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(ped->scrolled),
-                                      GTK_POLICY_AUTOMATIC, GTK_POLICY_NEVER);
+               if (READ_CHAR_FULL("title", title)) continue;
+               if (READ_BOOL_FULL("expanded", ped->pane.expanded)) continue;
+               if (READ_CHAR_FULL("id", ped->pane.id)) continue;
+               
 
-       viewport = gtk_viewport_new(NULL, NULL);
-       gtk_viewport_set_shadow_type(GTK_VIEWPORT(viewport), GTK_SHADOW_NONE);
-       gtk_container_add(GTK_CONTAINER(ped->scrolled), viewport);
-       gtk_widget_show(viewport);
+               log_printf("unknown attribute %s = %s\n", option, value);
+               }
 
-       gtk_container_add(GTK_CONTAINER(viewport), table);
-       gtk_widget_show(table);
+       if (title)
+               {
+               bar_pane_translate_title(PANE_EXIF, ped->pane.id, &title);
+               gtk_label_set_text(GTK_LABEL(ped->pane.title), title);
+               g_free(title);
+               }
 
-       gtk_box_pack_start(GTK_BOX(ped->vbox), ped->scrolled, TRUE, TRUE, 0);
+       bar_update_expander(pane);
+       bar_pane_exif_update(ped);
+}
 
-       hbox = gtk_hbox_new(FALSE, PREF_PAD_SPACE);
-       gtk_box_pack_end(GTK_BOX(ped->vbox), hbox, FALSE, FALSE, 0);
-       gtk_widget_show(hbox);
 
-       gtk_widget_show(ped->scrolled);
+void bar_pane_exif_entry_add_from_config(GtkWidget *pane, const gchar **attribute_names, const gchar **attribute_values)
+{
+       PaneExifData *ped;
+       gchar *key = NULL;
+       gchar *title = NULL;
+       gboolean if_set = TRUE;
+       gboolean editable = FALSE;
 
-       gtk_widget_show(ped->vbox);
+       ped = g_object_get_data(G_OBJECT(pane), "pane_data");
+       if (!ped) return;
+
+       while (*attribute_names)
+               {
+               const gchar *option = *attribute_names++;
+               const gchar *value = *attribute_values++;
 
-       return ped->vbox;
+               if (READ_CHAR_FULL("key", key)) continue;
+               if (READ_CHAR_FULL("title", title)) continue;
+               if (READ_BOOL_FULL("if_set", if_set)) continue;
+               if (READ_BOOL_FULL("editable", editable)) continue;
+               
+               log_printf("unknown attribute %s = %s\n", option, value);
+               }
+       
+       if (key && key[0]) bar_pane_exif_add_entry(ped, key, title, if_set, editable);
 }
+
+
 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */