[gthumb] allow to keep the comment and tags dialogs open after saving



commit 2067d9c6c51cc1ed288a947d939a9a5d06f1143a
Author: Paolo Bacchilega <paobac src gnome org>
Date:   Sun Dec 4 23:45:17 2011 +0100

    allow to keep the comment and tags dialogs open after saving
    
    [new feature]

 extensions/edit_metadata/Makefile.am               |   10 +-
 extensions/edit_metadata/actions.c                 |   76 +----
 extensions/edit_metadata/actions.h                 |    4 +-
 extensions/edit_metadata/callbacks.c               |  161 +----------
 extensions/edit_metadata/callbacks.h               |    2 -
 extensions/edit_metadata/data/ui/tag-chooser.ui    |  121 ++-------
 extensions/edit_metadata/dlg-edit-metadata.c       |   26 ++-
 extensions/edit_metadata/dlg-edit-metadata.h       |    4 +-
 extensions/edit_metadata/gth-edit-comment-dialog.c |  217 ++++++++++++++
 extensions/edit_metadata/gth-edit-comment-dialog.h |   84 ++++++
 extensions/edit_metadata/gth-edit-comment-page.h   |   53 ----
 ...edit-comment-page.c => gth-edit-general-page.c} |   99 ++++---
 extensions/edit_metadata/gth-edit-general-page.h   |   53 ++++
 .../edit_metadata/gth-edit-metadata-dialog.c       |  170 +----------
 .../edit_metadata/gth-edit-metadata-dialog.h       |   62 +---
 extensions/edit_metadata/gth-edit-tags-dialog.c    |  272 +++++++++++++++++
 extensions/edit_metadata/gth-edit-tags-dialog.h    |   56 ++++
 extensions/edit_metadata/gth-tag-chooser-dialog.c  |  308 --------------------
 extensions/edit_metadata/gth-tag-chooser-dialog.h  |   58 ----
 extensions/edit_metadata/main.c                    |    6 +-
 extensions/exiv2_tools/Makefile.am                 |    4 +-
 extensions/exiv2_tools/gth-edit-exiv2-page.h       |   53 ----
 ...{gth-edit-exiv2-page.c => gth-edit-iptc-page.c} |   66 ++--
 extensions/exiv2_tools/gth-edit-iptc-page.h        |   53 ++++
 extensions/exiv2_tools/main.c                      |    4 +-
 gthumb/dlg-preferences-extensions.c                |   13 +-
 gthumb/gth-browser-actions-callbacks.h             |    2 +-
 gthumb/gth-tags-entry.c                            |  163 ++++++++--
 gthumb/gth-tags-entry.h                            |   26 ++-
 29 files changed, 1076 insertions(+), 1150 deletions(-)
---
diff --git a/extensions/edit_metadata/Makefile.am b/extensions/edit_metadata/Makefile.am
index 77d3a9f..cca3a9c 100644
--- a/extensions/edit_metadata/Makefile.am
+++ b/extensions/edit_metadata/Makefile.am
@@ -12,12 +12,14 @@ libedit_metadata_la_SOURCES = 		\
 	dlg-edit-metadata.h		\
 	gth-delete-metadata-task.c	\
 	gth-delete-metadata-task.h	\
-	gth-edit-comment-page.c		\
-	gth-edit-comment-page.h		\
+	gth-edit-comment-dialog.c	\
+	gth-edit-comment-dialog.h	\
+	gth-edit-general-page.c		\
+	gth-edit-general-page.h		\
 	gth-edit-metadata-dialog.c	\
 	gth-edit-metadata-dialog.h	\
-	gth-tag-chooser-dialog.c	\
-	gth-tag-chooser-dialog.h	\
+	gth-edit-tags-dialog.c		\
+	gth-edit-tags-dialog.h		\
 	gth-tag-task.c			\
 	gth-tag-task.h			\
 	main.c
diff --git a/extensions/edit_metadata/actions.c b/extensions/edit_metadata/actions.c
index d6e6631..58018bd 100644
--- a/extensions/edit_metadata/actions.c
+++ b/extensions/edit_metadata/actions.c
@@ -24,79 +24,27 @@
 #include <gthumb.h>
 #include "dlg-edit-metadata.h"
 #include "gth-delete-metadata-task.h"
-#include "gth-tag-chooser-dialog.h"
-#include "gth-tag-task.h"
+#include "gth-edit-comment-dialog.h"
+#include "gth-edit-tags-dialog.h"
 
 
 void
-gth_browser_activate_action_edit_metadata (GtkAction  *action,
-				 	   GthBrowser *browser)
+gth_browser_activate_action_edit_comment (GtkAction  *action,
+				 	  GthBrowser *browser)
 {
-	dlg_edit_metadata (browser);
-}
-
-
-static void
-tag_chooser_dialog_response_cb (GtkDialog *dialog,
-				int        response_id,
-				gpointer   user_data)
-{
-	GthBrowser *browser = user_data;
-
-	switch (response_id) {
-	case GTK_RESPONSE_HELP:
-		show_help_dialog (GTK_WINDOW (browser), "assign-tags");
-		break;
-
-	case GTK_RESPONSE_OK:
-		{
-			GList    *items;
-			GList    *file_data_list;
-			GList    *file_list;
-			char    **tags;
-			GthTask  *task;
-
-			items = gth_file_selection_get_selected (GTH_FILE_SELECTION (gth_browser_get_file_list_view (browser)));
-			file_data_list = gth_file_list_get_files (GTH_FILE_LIST (gth_browser_get_file_list (browser)), items);
-			file_list = gth_file_data_list_to_file_list (file_data_list);
-			tags = gth_tag_chooser_dialog_get_tags (GTH_TAG_CHOOSER_DIALOG (dialog));
-			task = gth_tag_task_new (file_list, tags);
-			gth_browser_exec_task (browser, task, FALSE);
-			gtk_widget_destroy (GTK_WIDGET (dialog));
-
-			g_object_unref (task);
-			g_strfreev (tags);
-			_g_object_list_unref (file_list);
-			_g_object_list_unref (file_data_list);
-			_gtk_tree_path_list_free (items);
-		}
-		break;
-
-	case GTK_RESPONSE_DELETE_EVENT:
-	case GTK_RESPONSE_CANCEL:
-		gtk_widget_destroy (GTK_WIDGET (dialog));
-		break;
-	}
+	dlg_edit_metadata (browser,
+			   GTH_TYPE_EDIT_COMMENT_DIALOG,
+			   "edit-comment-dialog");
 }
 
 
 void
-gth_browser_activate_action_edit_tag_files (GtkAction  *action,
-					    GthBrowser *browser)
+gth_browser_activate_action_edit_tags (GtkAction  *action,
+				       GthBrowser *browser)
 {
-	GtkWidget *dialog;
-
-	dialog = gth_tag_chooser_dialog_new ();
-	g_signal_connect (dialog,
-			  "delete-event",
-			  G_CALLBACK (gtk_true),
-			  NULL);
-	g_signal_connect (dialog,
-			  "response",
-			  G_CALLBACK (tag_chooser_dialog_response_cb),
-			  browser);
-	gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (browser));
-	gtk_window_present (GTK_WINDOW (dialog));
+	dlg_edit_metadata (browser,
+			   GTH_TYPE_EDIT_TAGS_DIALOG,
+			   "edit-tags-dialog");
 }
 
 
diff --git a/extensions/edit_metadata/actions.h b/extensions/edit_metadata/actions.h
index d7fe22e..f0b5837 100644
--- a/extensions/edit_metadata/actions.h
+++ b/extensions/edit_metadata/actions.h
@@ -26,8 +26,8 @@
 
 #define DEFINE_ACTION(x) void x (GtkAction *action, gpointer data);
 
-DEFINE_ACTION(gth_browser_activate_action_edit_metadata)
-DEFINE_ACTION(gth_browser_activate_action_edit_tag_files)
+DEFINE_ACTION(gth_browser_activate_action_edit_comment)
+DEFINE_ACTION(gth_browser_activate_action_edit_tags)
 DEFINE_ACTION(gth_browser_activate_action_tool_delete_metadata)
 
 #endif /* ACTIONS_H */
diff --git a/extensions/edit_metadata/callbacks.c b/extensions/edit_metadata/callbacks.c
index 09d9033..8f4cb3f 100644
--- a/extensions/edit_metadata/callbacks.c
+++ b/extensions/edit_metadata/callbacks.c
@@ -58,19 +58,13 @@ static const char *fixed_ui_info =
 "  </toolbar>"
 "  <popup name='FileListPopup'>"
 "    <placeholder name='File_LastActions'>"
-"      <menu action='Edit_QuickTag'>"
-"        <separator name='TagListSeparator'/>"
-"        <menuitem action='Edit_QuickTagOther'/>"
-"      </menu>"
+"      <menuitem action='Edit_Tags'/>"
 "      <menuitem action='Edit_Metadata'/>"
 "    </placeholder>"
 "  </popup>"
 "  <popup name='FilePopup'>"
 "    <placeholder name='File_LastActions'>"
-"      <menu action='Edit_QuickTag'>"
-"        <separator name='TagListSeparator'/>"
-"        <menuitem action='Edit_QuickTagOther'/>"
-"      </menu>"
+"      <menuitem action='Edit_Tags'/>"
 "      <menuitem action='Edit_Metadata'/>"
 "    </placeholder>"
 "  </popup>"
@@ -106,13 +100,13 @@ static GthActionEntryExt edit_metadata_action_entries[] = {
 	  N_("Comment"), "<control>M",
 	  N_("Edit the comment and other information of the selected files"),
 	  GTH_ACTION_FLAG_IS_IMPORTANT,
-	  G_CALLBACK (gth_browser_activate_action_edit_metadata) },
+	  G_CALLBACK (gth_browser_activate_action_edit_comment) },
 
-        { "Edit_QuickTagOther", NULL,
-	  N_("Other..."), NULL,
-	  N_("Choose another tag"),
+        { "Edit_Tags", "tag",
+	  N_("Tags"), NULL,
+	  N_("Set the tags of the selected files"),
 	  GTH_ACTION_FLAG_NONE,
-	  G_CALLBACK (gth_browser_activate_action_edit_tag_files) },
+	  G_CALLBACK (gth_browser_activate_action_edit_tags) },
 
 	{ "Tool_DeleteMetadata", NULL,
 	  N_("Delete Metadata"), NULL,
@@ -126,30 +120,16 @@ typedef struct {
 	GthBrowser     *browser;
 	GtkActionGroup *actions;
 	guint           viewer_ui_merge_id;
-	gboolean        tag_menu_loaded;
-	guint           monitor_events;
 } BrowserData;
 
 
 static void
 browser_data_free (BrowserData *data)
 {
-	if (data->monitor_events != 0) {
-		g_signal_handler_disconnect (gth_main_get_default_monitor (), data->monitor_events);
-		data->monitor_events = 0;
-	}
 	g_free (data);
 }
 
 
-static void
-monitor_tags_changed_cb (GthMonitor  *monitor,
-			 BrowserData *data)
-{
-	data->tag_menu_loaded = FALSE;
-}
-
-
 void
 edit_metadata__gth_browser_construct_cb (GthBrowser *browser)
 {
@@ -179,11 +159,6 @@ edit_metadata__gth_browser_construct_cb (GthBrowser *browser)
 		g_error_free (error);
 	}
 
-	data->monitor_events = g_signal_connect (gth_main_get_default_monitor (),
-						 "tags-changed",
-						 G_CALLBACK (monitor_tags_changed_cb),
-						 data);
-
 	g_object_set_data_full (G_OBJECT (browser), BROWSER_DATA_KEY, data, (GDestroyNotify) browser_data_free);
 }
 
@@ -239,120 +214,6 @@ edit_metadata__gth_browser_update_sensitivity_cb (GthBrowser *browser)
 }
 
 
-static void
-tag_item_activate_cb (GtkMenuItem *menuitem,
-		      gpointer     user_data)
-{
-	GthBrowser  *browser = user_data;
-	GList       *items;
-	GList       *file_data_list;
-	GList       *file_list;
-	char        *tag;
-	char       **tags;
-	GthTask     *task;
-
-	if (gtk_menu_item_get_submenu (menuitem) != NULL)
-		return;
-
-	items = gth_file_selection_get_selected (GTH_FILE_SELECTION (gth_browser_get_file_list_view (browser)));
-	file_data_list = gth_file_list_get_files (GTH_FILE_LIST (gth_browser_get_file_list (browser)), items);
-	file_list = gth_file_data_list_to_file_list (file_data_list);
-
-	tag = g_object_get_data (G_OBJECT (menuitem), "tag");
-	tags = g_new0 (char *, 2);
-	tags[0] = g_strdup (tag);
-	tags[1] = NULL;
-
-	task = gth_tag_task_new (file_list, tags);
-	gth_browser_exec_task (browser, task, FALSE);
-
-	g_object_unref (task);
-	g_strfreev (tags);
-	_g_object_list_unref (file_list);
-	_g_object_list_unref (file_data_list);
-	_gtk_tree_path_list_free (items);
-}
-
-
-static void
-insert_tag_menu_item (BrowserData *data,
-		      GtkWidget   *menu,
-		      const char  *tag,
-		      int          pos)
-{
-	GtkWidget *item;
-	GtkWidget *image;
-
-	item = gtk_image_menu_item_new_with_label (tag);
-	gtk_image_menu_item_set_always_show_image (GTK_IMAGE_MENU_ITEM (item), TRUE);
-
-	image = gtk_image_new_from_icon_name ("tag", GTK_ICON_SIZE_MENU);
-	gtk_widget_show (image);
-	gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
-	gtk_widget_show (item);
-	gtk_menu_shell_insert (GTK_MENU_SHELL (menu), item, pos);
-	g_object_set_data_full (G_OBJECT (item), "tag", g_strdup (tag), g_free);
-	g_signal_connect (item, "activate", G_CALLBACK (tag_item_activate_cb), data->browser);
-}
-
-
-static void
-update_tag_menu (BrowserData *data)
-{
-	GtkWidget   *list_menu;
-	GtkWidget   *file_menu;
-	GtkWidget   *separator;
-	char       **tags;
-	int          i;
-
-	list_menu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (gtk_ui_manager_get_widget (gth_browser_get_ui_manager (data->browser), "/FileListPopup/File_LastActions/Edit_QuickTag")));
-	separator = gtk_ui_manager_get_widget (gth_browser_get_ui_manager (data->browser), "/FileListPopup/File_LastActions/Edit_QuickTag/TagListSeparator");
-	_gtk_container_remove_children (GTK_CONTAINER (list_menu), NULL, separator);
-
-	file_menu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (gtk_ui_manager_get_widget (gth_browser_get_ui_manager (data->browser), "/FilePopup/File_LastActions/Edit_QuickTag")));
-	separator = gtk_ui_manager_get_widget (gth_browser_get_ui_manager (data->browser), "/FilePopup/File_LastActions/Edit_QuickTag/TagListSeparator");
-	_gtk_container_remove_children (GTK_CONTAINER (file_menu), NULL, separator);
-
-	tags = g_strdupv (gth_tags_file_get_tags (gth_main_get_default_tag_file ()));
-	for (i = 0; tags[i] != NULL; i++) {
-		insert_tag_menu_item (data, list_menu, tags[i], i);
-		insert_tag_menu_item (data, file_menu, tags[i], i);
-	}
-
-	g_strfreev (tags);
-}
-
-
-void
-edit_metadata__gth_browser_file_list_popup_before_cb (GthBrowser *browser)
-{
-	BrowserData *data;
-
-	data = g_object_get_data (G_OBJECT (browser), BROWSER_DATA_KEY);
-	g_return_if_fail (data != NULL);
-
-	if (! data->tag_menu_loaded) {
-		data->tag_menu_loaded = TRUE;
-		update_tag_menu (data);
-	}
-}
-
-
-void
-edit_metadata__gth_browser_file_popup_before_cb (GthBrowser *browser)
-{
-	BrowserData *data;
-
-	data = g_object_get_data (G_OBJECT (browser), BROWSER_DATA_KEY);
-	g_return_if_fail (data != NULL);
-
-	if (! data->tag_menu_loaded) {
-		data->tag_menu_loaded = TRUE;
-		update_tag_menu (data);
-	}
-}
-
-
 gpointer
 edit_metadata__gth_browser_file_list_key_press_cb (GthBrowser  *browser,
 						   GdkEventKey *event)
@@ -366,11 +227,15 @@ edit_metadata__gth_browser_file_list_key_press_cb (GthBrowser  *browser,
 
 	switch (gdk_keyval_to_lower (event->keyval)) {
 	case GDK_KEY_c:
-		gth_browser_activate_action_edit_metadata (NULL, browser);
+		gth_browser_activate_action_edit_comment (NULL, browser);
+		result = GINT_TO_POINTER (1);
+		break;
+
+	case GDK_KEY_t:
+		gth_browser_activate_action_edit_tags (NULL, browser);
 		result = GINT_TO_POINTER (1);
 		break;
 	}
 
 	return result;
 }
-
diff --git a/extensions/edit_metadata/callbacks.h b/extensions/edit_metadata/callbacks.h
index 8d4435e..258d1ce 100644
--- a/extensions/edit_metadata/callbacks.h
+++ b/extensions/edit_metadata/callbacks.h
@@ -27,8 +27,6 @@
 void      edit_metadata__gth_browser_construct_cb              (GthBrowser  *browser);
 void      edit_metadata__gth_browser_set_current_page_cb       (GthBrowser  *browser);
 void      edit_metadata__gth_browser_update_sensitivity_cb     (GthBrowser  *browser);
-void      edit_metadata__gth_browser_file_list_popup_before_cb (GthBrowser  *browser);
-void      edit_metadata__gth_browser_file_popup_before_cb      (GthBrowser  *browser);
 gpointer  edit_metadata__gth_browser_file_list_key_press_cb    (GthBrowser  *browser,
 						   	        GdkEventKey *event);
 
diff --git a/extensions/edit_metadata/data/ui/tag-chooser.ui b/extensions/edit_metadata/data/ui/tag-chooser.ui
index 8cb6897..7009c68 100644
--- a/extensions/edit_metadata/data/ui/tag-chooser.ui
+++ b/extensions/edit_metadata/data/ui/tag-chooser.ui
@@ -1,131 +1,46 @@
-<?xml version="1.0"?>
+<?xml version="1.0" encoding="UTF-8"?>
 <interface>
   <requires lib="gtk+" version="2.14"/>
-  <!-- interface-naming-policy project-wide -->
-  <object class="GtkListStore" id="tags_liststore">
-    <columns>
-      <!-- column-name name -->
-      <column type="gchararray"/>
-      <!-- column-name selected -->
-      <column type="gboolean"/>
-    </columns>
-  </object>
   <object class="GtkVBox" id="content">
     <property name="visible">True</property>
-    <property name="orientation">vertical</property>
+    <property name="can_focus">False</property>
     <property name="spacing">6</property>
     <child>
       <object class="GtkLabel" id="tag_label">
         <property name="visible">True</property>
+        <property name="can_focus">False</property>
         <property name="xalign">0</property>
         <property name="label" translatable="yes">T_ags:</property>
         <property name="use_underline">True</property>
-        <property name="mnemonic_widget">tags_treeview</property>
       </object>
       <packing>
         <property name="expand">False</property>
+        <property name="fill">True</property>
         <property name="position">0</property>
       </packing>
     </child>
     <child>
-      <object class="GtkHBox" id="hbox3">
+      <object class="GtkBox" id="tag_entry_box">
         <property name="visible">True</property>
-        <property name="spacing">12</property>
-        <child>
-          <object class="GtkScrolledWindow" id="tags_scrolledwindow">
-            <property name="width_request">250</property>
-            <property name="height_request">300</property>
-            <property name="visible">True</property>
-            <property name="can_focus">True</property>
-            <property name="hscrollbar_policy">automatic</property>
-            <property name="vscrollbar_policy">automatic</property>
-            <property name="shadow_type">etched-in</property>
-            <child>
-              <object class="GtkTreeView" id="tags_treeview">
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="model">tags_liststore</property>
-                <property name="headers_visible">False</property>
-                <child>
-                  <object class="GtkTreeViewColumn" id="treeviewcolumn1">
-                    <child>
-                      <object class="GtkCellRendererToggle" id="selected_cellrenderertoggle"/>
-                      <attributes>
-                        <attribute name="active">1</attribute>
-                      </attributes>
-                    </child>
-                  </object>
-                </child>
-                <child>
-                  <object class="GtkTreeViewColumn" id="treeviewcolumn2">
-                    <property name="title">column</property>
-                    <property name="sort_column_id">1</property>
-                    <child>
-                      <object class="GtkCellRendererPixbuf" id="cellrendererpixbuf2">
-                        <property name="icon_name">tag</property>
-                      </object>
-                    </child>
-                    <child>
-                      <object class="GtkCellRendererText" id="name_cellrenderertext">
-                        <property name="editable">True</property>
-                      </object>
-                      <attributes>
-                        <attribute name="text">0</attribute>
-                      </attributes>
-                    </child>
-                  </object>
-                </child>
-              </object>
-            </child>
-          </object>
-          <packing>
-            <property name="position">0</property>
-          </packing>
-        </child>
+        <property name="can_focus">False</property>
+        <property name="orientation">vertical</property>
         <child>
-          <object class="GtkVButtonBox" id="vbuttonbox3">
-            <property name="visible">True</property>
-            <property name="orientation">vertical</property>
-            <property name="spacing">6</property>
-            <property name="layout_style">start</property>
-            <child>
-              <object class="GtkButton" id="new_button">
-                <property name="label">gtk-new</property>
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="receives_default">True</property>
-                <property name="use_stock">True</property>
-              </object>
-              <packing>
-                <property name="expand">False</property>
-                <property name="fill">False</property>
-                <property name="position">0</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkButton" id="delete_button">
-                <property name="label">gtk-remove</property>
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="receives_default">True</property>
-                <property name="use_stock">True</property>
-              </object>
-              <packing>
-                <property name="expand">False</property>
-                <property name="fill">False</property>
-                <property name="position">1</property>
-              </packing>
-            </child>
-          </object>
-          <packing>
-            <property name="expand">False</property>
-            <property name="position">1</property>
-          </packing>
+          <placeholder/>
         </child>
       </object>
       <packing>
+        <property name="expand">True</property>
+        <property name="fill">True</property>
         <property name="position">1</property>
       </packing>
     </child>
   </object>
+  <object class="GtkListStore" id="tags_liststore">
+    <columns>
+      <!-- column-name name -->
+      <column type="gchararray"/>
+      <!-- column-name selected -->
+      <column type="gboolean"/>
+    </columns>
+  </object>
 </interface>
diff --git a/extensions/edit_metadata/dlg-edit-metadata.c b/extensions/edit_metadata/dlg-edit-metadata.c
index 42d1039..be378ca 100644
--- a/extensions/edit_metadata/dlg-edit-metadata.c
+++ b/extensions/edit_metadata/dlg-edit-metadata.c
@@ -26,14 +26,14 @@
 #include "gth-edit-metadata-dialog.h"
 
 
-#define UPDATE_SELECTION_DELAY    300
-#define EDIT_METADATA_DIALOG_NAME "dlg-edit-metadata"
+#define UPDATE_SELECTION_DELAY 300
 
 
 typedef struct {
 	int         ref;
 	GthBrowser *browser;
 	GtkWidget  *dialog;
+	char       *dialog_name;
 	GList      *file_list; /* GthFileData list */
 	GList      *parents;
 	gboolean    never_shown;
@@ -80,9 +80,10 @@ dialog_data_unref (DialogData *data)
 	}
 	cancel_file_list_loading (data);
 
-	gth_browser_set_dialog (data->browser, EDIT_METADATA_DIALOG_NAME, NULL);
+	gth_browser_set_dialog (data->browser, data->dialog_name, NULL);
 	gtk_widget_destroy (data->dialog);
 
+	g_free (data->dialog_name);
 	_g_object_list_unref (data->file_list);
 	_g_object_list_unref (data->parents);
 	g_free (data);
@@ -149,6 +150,11 @@ edit_metadata_dialog__response_cb (GtkDialog *dialog,
 	GList      *scan;
 	GthTask    *task;
 
+	if (response == GTK_RESPONSE_HELP) {
+		show_help_dialog (GTK_WINDOW (dialog), data->dialog_name);
+		return;
+	}
+
 	if ((response != GTK_RESPONSE_OK) && (response != GTK_RESPONSE_APPLY)) {
 		cancel_file_list_loading (data);
 		close_dialog (data);
@@ -234,6 +240,7 @@ loader_completed_cb (GthTask  *task,
 
 	_g_object_list_unref (data->file_list);
 	data->file_list = _g_object_list_ref (gth_load_file_data_task_get_result (GTH_LOAD_FILE_DATA_TASK (task)));
+
 	gth_edit_metadata_dialog_set_file_list (GTH_EDIT_METADATA_DIALOG (data->dialog), data->file_list);
 
 	gtk_window_set_transient_for (GTK_WINDOW (data->dialog), GTK_WINDOW (data->browser));
@@ -293,22 +300,25 @@ file_selection_changed_cb (GthFileSelection *self,
 
 
 void
-dlg_edit_metadata (GthBrowser *browser)
+dlg_edit_metadata (GthBrowser *browser,
+		   GType       dialog_type,
+		   const char *dialog_name)
 {
 	DialogData *data;
 
-	if (gth_browser_get_dialog (browser, EDIT_METADATA_DIALOG_NAME)) {
-		gtk_window_present (GTK_WINDOW (gth_browser_get_dialog (browser, EDIT_METADATA_DIALOG_NAME)));
+	if (gth_browser_get_dialog (browser, dialog_name)) {
+		gtk_window_present (GTK_WINDOW (gth_browser_get_dialog (browser, dialog_name)));
 		return;
 	}
 
 	data = g_new0 (DialogData, 1);
 	data->ref = 1;
 	data->browser = browser;
-	data->dialog = gth_edit_metadata_dialog_new ();
+	data->dialog = g_object_new (dialog_type, 0);
+	data->dialog_name = g_strdup (dialog_name);
 	data->never_shown = TRUE;
 
-	gth_browser_set_dialog (browser, EDIT_METADATA_DIALOG_NAME, data->dialog);
+	gth_browser_set_dialog (browser, data->dialog_name, data->dialog);
 
 	g_signal_connect (G_OBJECT (data->dialog),
 			  "delete-event",
diff --git a/extensions/edit_metadata/dlg-edit-metadata.h b/extensions/edit_metadata/dlg-edit-metadata.h
index fc5424b..697dd84 100644
--- a/extensions/edit_metadata/dlg-edit-metadata.h
+++ b/extensions/edit_metadata/dlg-edit-metadata.h
@@ -24,6 +24,8 @@
 
 #include <gthumb.h>
 
-void dlg_edit_metadata (GthBrowser *browser);
+void dlg_edit_metadata (GthBrowser *browser,
+			GType       dialog_type,
+			const char *dialog_name);
 
 #endif /* DLG_EDIT_METADATA_H */
diff --git a/extensions/edit_metadata/gth-edit-comment-dialog.c b/extensions/edit_metadata/gth-edit-comment-dialog.c
new file mode 100644
index 0000000..00b1dbc
--- /dev/null
+++ b/extensions/edit_metadata/gth-edit-comment-dialog.c
@@ -0,0 +1,217 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <glib/gi18n.h>
+#include "gth-edit-comment-dialog.h"
+#include "gth-edit-metadata-dialog.h"
+
+
+struct _GthEditCommentDialogPrivate {
+	GtkWidget *notebook;
+	GtkWidget *save_changed_checkbutton;
+};
+
+
+static void gth_edit_comment_dialog_gth_edit_metadata_dialog_interface_init (GthEditMetadataDialogInterface *iface);
+
+
+G_DEFINE_TYPE_WITH_CODE (GthEditCommentDialog,
+			 gth_edit_comment_dialog,
+			 GTK_TYPE_DIALOG,
+			 G_IMPLEMENT_INTERFACE (GTH_TYPE_EDIT_METADATA_DIALOG,
+					 	gth_edit_comment_dialog_gth_edit_metadata_dialog_interface_init))
+
+
+
+static void
+gth_edit_comment_dialog_set_file_list (GthEditMetadataDialog *base,
+				       GList                 *file_list)
+{
+	GthEditCommentDialog *self = GTH_EDIT_COMMENT_DIALOG (base);
+	int                   n_files;
+	char                 *title;
+	GList                *pages;
+	GList                *scan;
+
+	n_files = g_list_length (file_list);
+
+	/* update the title */
+
+	if (n_files == 1) {
+		GthFileData *file_data = file_list->data;
+
+		/* Translators: the %s symbol in the string is a file name */
+		title = g_strdup_printf (_("%s Metadata"), g_file_info_get_display_name (file_data->info));
+	}
+	else
+		title = g_strdup_printf (g_dngettext (NULL, "%d file", "%d files", n_files), n_files);
+	gtk_window_set_title (GTK_WINDOW (self), title);
+	g_free (title);
+
+	/* update the widgets */
+
+	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->priv->save_changed_checkbutton), n_files > 1);
+	gtk_widget_set_sensitive (self->priv->save_changed_checkbutton, n_files > 1);
+
+	pages = gtk_container_get_children (GTK_CONTAINER (self->priv->notebook));
+	for (scan = pages; scan; scan = scan->next)
+		gth_edit_comment_page_set_file_list (GTH_EDIT_COMMENT_PAGE (scan->data), file_list);
+
+	gtk_dialog_set_response_sensitive (GTK_DIALOG (self),
+					   GTK_RESPONSE_APPLY,
+					   n_files > 0);
+	gtk_dialog_set_response_sensitive (GTK_DIALOG (self),
+					   GTK_RESPONSE_OK,
+					   n_files > 0);
+
+	g_list_free (pages);
+}
+
+
+static void
+gth_edit_comment_dialog_update_info (GthEditMetadataDialog *base,
+				     GList                 *file_list /* GthFileData list */)
+{
+	GthEditCommentDialog *self = GTH_EDIT_COMMENT_DIALOG (base);
+	gboolean              only_modified_fields;
+	GList                *pages;
+	GList                *scan;
+
+	only_modified_fields = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self->priv->save_changed_checkbutton));
+	pages = gtk_container_get_children (GTK_CONTAINER (self->priv->notebook));
+	for (scan = pages; scan; scan = scan->next) {
+		GList *scan_file;
+
+		for (scan_file = file_list; scan_file; scan_file = scan_file->next) {
+			GthFileData *file_data = scan_file->data;
+			gth_edit_comment_page_update_info (GTH_EDIT_COMMENT_PAGE (scan->data), file_data->info, only_modified_fields);
+		}
+	}
+
+	g_list_free (pages);
+}
+
+
+static void
+gth_edit_comment_dialog_gth_edit_metadata_dialog_interface_init (GthEditMetadataDialogInterface *iface)
+{
+	iface->set_file_list = gth_edit_comment_dialog_set_file_list;
+	iface->update_info = gth_edit_comment_dialog_update_info;
+}
+
+
+static void
+gth_edit_comment_dialog_class_init (GthEditCommentDialogClass *klass)
+{
+	g_type_class_add_private (klass, sizeof (GthEditCommentDialogPrivate));
+}
+
+
+static void
+gth_edit_comment_dialog_init (GthEditCommentDialog *self)
+{
+	GtkWidget *vbox;
+	GArray    *pages;
+	int        i;
+
+	self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GTH_TYPE_EDIT_COMMENT_DIALOG, GthEditCommentDialogPrivate);
+
+	gtk_window_set_resizable (GTK_WINDOW (self), TRUE);
+	gtk_box_set_spacing (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (self))), 5);
+	gtk_container_set_border_width (GTK_CONTAINER (self), 5);
+
+	gtk_dialog_add_button (GTK_DIALOG (self), GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
+	gtk_dialog_add_button (GTK_DIALOG (self), GTK_STOCK_SAVE, GTK_RESPONSE_APPLY);
+	gtk_dialog_add_button (GTK_DIALOG (self), _("Sa_ve and Close"), GTK_RESPONSE_OK);
+	gtk_dialog_add_button (GTK_DIALOG (self), GTK_STOCK_HELP, GTK_RESPONSE_HELP);
+
+	vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
+	gtk_container_set_border_width (GTK_CONTAINER (vbox), 5);
+	gtk_widget_show (vbox);
+	gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (self))), vbox, TRUE, TRUE, 0);
+
+	self->priv->notebook = gtk_notebook_new ();
+	gtk_widget_show (self->priv->notebook);
+	gtk_box_pack_start (GTK_BOX (vbox), self->priv->notebook, TRUE, TRUE, 0);
+
+	self->priv->save_changed_checkbutton = gtk_check_button_new_with_mnemonic (_("Save only cha_nged fields"));
+	gtk_widget_show (self->priv->save_changed_checkbutton);
+	gtk_box_pack_start (GTK_BOX (vbox), self->priv->save_changed_checkbutton, FALSE, FALSE, 0);
+
+	pages = gth_main_get_type_set ("edit-comment-dialog-page");
+	if (pages == NULL)
+		return;
+
+	for (i = 0; i < pages->len; i++) {
+		GType      page_type;
+		GtkWidget *page;
+
+		page_type = g_array_index (pages, GType, i);
+		page = g_object_new (page_type, NULL);
+		if (! GTH_IS_EDIT_COMMENT_PAGE (page)) {
+			g_object_unref (page);
+			continue;
+		}
+
+		gtk_widget_show (page);
+		gtk_notebook_append_page (GTK_NOTEBOOK (self->priv->notebook),
+					  page,
+					  gtk_label_new (gth_edit_comment_page_get_name (GTH_EDIT_COMMENT_PAGE (page))));
+	}
+}
+
+
+/* -- gth_edit_comment_dialog_page -- */
+
+
+G_DEFINE_INTERFACE (GthEditCommentPage, gth_edit_comment_page, 0)
+
+
+static void
+gth_edit_comment_page_default_init (GthEditCommentPageInterface *iface)
+{
+	/* void */
+}
+
+
+void
+gth_edit_comment_page_set_file_list (GthEditCommentPage *self,
+				      GList               *file_list)
+{
+	GTH_EDIT_COMMENT_PAGE_GET_INTERFACE (self)->set_file_list (self, file_list);
+}
+
+
+void
+gth_edit_comment_page_update_info (GthEditCommentPage *self,
+				    GFileInfo           *info,
+				    gboolean             only_modified_fields)
+{
+	GTH_EDIT_COMMENT_PAGE_GET_INTERFACE (self)->update_info (self, info, only_modified_fields);
+}
+
+
+const char *
+gth_edit_comment_page_get_name (GthEditCommentPage *self)
+{
+	return GTH_EDIT_COMMENT_PAGE_GET_INTERFACE (self)->get_name (self);
+}
diff --git a/extensions/edit_metadata/gth-edit-comment-dialog.h b/extensions/edit_metadata/gth-edit-comment-dialog.h
new file mode 100644
index 0000000..ee40b28
--- /dev/null
+++ b/extensions/edit_metadata/gth-edit-comment-dialog.h
@@ -0,0 +1,84 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+ 
+#ifndef GTH_EDIT_COMMENT_DIALOG_H
+#define GTH_EDIT_COMMENT_DIALOG_H
+
+#include <gtk/gtk.h>
+#include <gthumb.h>
+
+G_BEGIN_DECLS
+
+#define GTH_TYPE_EDIT_COMMENT_DIALOG            (gth_edit_comment_dialog_get_type ())
+#define GTH_EDIT_COMMENT_DIALOG(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTH_TYPE_EDIT_COMMENT_DIALOG, GthEditCommentDialog))
+#define GTH_EDIT_COMMENT_DIALOG_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GTH_TYPE_EDIT_COMMENT_DIALOG, GthEditCommentDialogClass))
+#define GTH_IS_EDIT_COMMENT_DIALOG(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTH_TYPE_EDIT_COMMENT_DIALOG))
+#define GTH_IS_EDIT_COMMENT_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTH_TYPE_EDIT_COMMENT_DIALOG))
+#define GTH_EDIT_COMMENT_DIALOG_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), GTH_TYPE_EDIT_COMMENT_DIALOG, GthEditCommentDialogClass))
+
+#define GTH_TYPE_EDIT_COMMENT_PAGE               (gth_edit_comment_page_get_type ())
+#define GTH_EDIT_COMMENT_PAGE(obj)               (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTH_TYPE_EDIT_COMMENT_PAGE, GthEditCommentPage))
+#define GTH_IS_EDIT_COMMENT_PAGE(obj)            (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTH_TYPE_EDIT_COMMENT_PAGE))
+#define GTH_EDIT_COMMENT_PAGE_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), GTH_TYPE_EDIT_COMMENT_PAGE, GthEditCommentPageInterface))
+
+typedef struct _GthEditCommentDialog GthEditCommentDialog;
+typedef struct _GthEditCommentDialogClass GthEditCommentDialogClass;
+typedef struct _GthEditCommentDialogPrivate GthEditCommentDialogPrivate;
+
+struct _GthEditCommentDialog {
+	GtkDialog parent_instance;
+	GthEditCommentDialogPrivate *priv;
+};
+
+struct _GthEditCommentDialogClass {
+	GtkDialogClass parent_class;
+};
+
+typedef struct _GthEditCommentPage GthEditCommentPage;
+typedef struct _GthEditCommentPageInterface GthEditCommentPageInterface;
+
+struct _GthEditCommentPageInterface {
+	GTypeInterface parent_iface;
+	void         (*set_file_list) (GthEditCommentPage *self,
+				       GList               *file_list /* GthFileData list */);
+	void         (*update_info)   (GthEditCommentPage *self,
+				       GFileInfo           *info,
+				       gboolean             only_modified_fields);
+	const char * (*get_name)      (GthEditCommentPage *self);
+};
+
+/* GthEditCommentDialog */
+
+GType          gth_edit_comment_dialog_get_type       (void);
+
+/* GthEditCommentPage */
+
+GType          gth_edit_comment_page_get_type         (void);
+void           gth_edit_comment_page_set_file_list    (GthEditCommentPage   *self,
+						       GList                *file_list /* GthFileData list */);
+void           gth_edit_comment_page_update_info      (GthEditCommentPage   *self,
+						       GFileInfo            *info,
+						       gboolean              only_modified_fields);
+const char *   gth_edit_comment_page_get_name         (GthEditCommentPage   *self);
+
+G_END_DECLS
+
+#endif /* GTH_EDIT_COMMENT_DIALOG_H */
diff --git a/extensions/edit_metadata/gth-edit-comment-page.c b/extensions/edit_metadata/gth-edit-general-page.c
similarity index 85%
rename from extensions/edit_metadata/gth-edit-comment-page.c
rename to extensions/edit_metadata/gth-edit-general-page.c
index eaee343..2468e1e 100644
--- a/extensions/edit_metadata/gth-edit-comment-page.c
+++ b/extensions/edit_metadata/gth-edit-general-page.c
@@ -22,11 +22,10 @@
 #include <config.h>
 #include <glib/gi18n.h>
 #include <gio/gio.h>
-#include "gth-edit-comment-page.h"
-#include "gth-edit-metadata-dialog.h"
+#include "gth-edit-comment-dialog.h"
+#include "gth-edit-general-page.h"
 
 
-#define GTH_EDIT_COMMENT_PAGE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GTH_TYPE_EDIT_COMMENT_PAGE, GthEditCommentPagePrivate))
 #define GET_WIDGET(name) _gtk_builder_get_widget (self->priv->builder, (name))
 
 
@@ -41,7 +40,7 @@ typedef enum {
 } DateOption;
 
 
-struct _GthEditCommentPagePrivate {
+struct _GthEditGeneralPagePrivate {
 	GFileInfo  *info;
 	GtkBuilder *builder;
 	GtkWidget  *date_combobox;
@@ -51,21 +50,21 @@ struct _GthEditCommentPagePrivate {
 };
 
 
-static void gth_edit_comment_page_gth_edit_comment_page_interface_init (GthEditMetadataPageInterface *iface);
+static void gth_edit_general_page_gth_edit_general_page_interface_init (GthEditCommentPageInterface *iface);
 
 
-G_DEFINE_TYPE_WITH_CODE (GthEditCommentPage,
-			 gth_edit_comment_page,
+G_DEFINE_TYPE_WITH_CODE (GthEditGeneralPage,
+			 gth_edit_general_page,
 			 GTK_TYPE_VBOX,
-			 G_IMPLEMENT_INTERFACE (GTH_TYPE_EDIT_METADATA_PAGE,
-					 	gth_edit_comment_page_gth_edit_comment_page_interface_init))
+			 G_IMPLEMENT_INTERFACE (GTH_TYPE_EDIT_COMMENT_PAGE,
+					 	gth_edit_general_page_gth_edit_general_page_interface_init))
 
 
 void
-gth_edit_comment_page_real_set_file_list (GthEditMetadataPage *base,
-		 			  GList               *file_list)
+gth_edit_general_page_real_set_file_list (GthEditCommentPage *base,
+		 			  GList              *file_list)
 {
-	GthEditCommentPage  *self;
+	GthEditGeneralPage  *self;
 	GtkTextBuffer       *buffer;
 	GthMetadata         *metadata;
 	GthStringList       *tags;
@@ -74,7 +73,7 @@ gth_edit_comment_page_real_set_file_list (GthEditMetadataPage *base,
 	GthFileData         *file_data;
 	const char          *mime_type;
 
-	self = GTH_EDIT_COMMENT_PAGE (base);
+	self = GTH_EDIT_GENERAL_PAGE (base);
 
 	_g_object_unref (self->priv->info);
 	self->priv->info = gth_file_data_list_get_common_info (file_list, "general::description,general::title,general::location,general::datetime,general::tags,general::rating");
@@ -123,12 +122,12 @@ gth_edit_comment_page_real_set_file_list (GthEditMetadataPage *base,
 		char *value;
 
 		value = gth_string_list_join (tags, ",");
-		gth_tags_entry_set_text (GTH_TAGS_ENTRY (self->priv->tags_entry), value);
+		gth_tags_entry_set_tags_from_text (GTH_TAGS_ENTRY (self->priv->tags_entry), value);
 
 		g_free (value);
 	}
 	else
-		gth_tags_entry_set_text (GTH_TAGS_ENTRY (self->priv->tags_entry), NULL);
+		gth_tags_entry_set_tags_from_text (GTH_TAGS_ENTRY (self->priv->tags_entry), NULL);
 
 	metadata = (GthMetadata *) g_file_info_get_attribute_object (self->priv->info, "general::rating");
 	if (metadata != NULL) {
@@ -198,7 +197,7 @@ gth_edit_comment_page_real_set_file_list (GthEditMetadataPage *base,
 
 
 static char *
-get_date_from_option (GthEditCommentPage *self,
+get_date_from_option (GthEditGeneralPage *self,
 		      DateOption          option,
 		      GFileInfo          *info)
 {
@@ -259,24 +258,24 @@ get_date_from_option (GthEditCommentPage *self,
 
 
 void
-gth_edit_comment_page_real_update_info (GthEditMetadataPage *base,
-					GFileInfo           *info,
-					gboolean             only_modified_fields)
+gth_edit_general_page_real_update_info (GthEditCommentPage *base,
+					GFileInfo          *info,
+					gboolean            only_modified_fields)
 {
-	GthEditCommentPage  *self;
-	GthFileData         *file_data;
-	GtkTextBuffer       *buffer;
-	GtkTextIter          start;
-	GtkTextIter          end;
-	char                *text;
-	GthMetadata         *metadata;
-	int                  i;
-	char               **tagv;
-	GList               *tags;
-	GthStringList       *string_list;
-	char                *s;
-
-	self = GTH_EDIT_COMMENT_PAGE (base);
+	GthEditGeneralPage  *self;
+	GthFileData            *file_data;
+	GtkTextBuffer          *buffer;
+	GtkTextIter             start;
+	GtkTextIter             end;
+	char                   *text;
+	GthMetadata            *metadata;
+	int                     i;
+	char                  **tagv;
+	GList                  *tags;
+	GthStringList          *string_list;
+	char                   *s;
+
+	self = GTH_EDIT_GENERAL_PAGE (base);
 
 	file_data = gth_file_data_new (NULL, self->priv->info);
 
@@ -325,9 +324,11 @@ gth_edit_comment_page_real_update_info (GthEditMetadataPage *base,
 	switch (gtk_combo_box_get_active (GTK_COMBO_BOX (self->priv->date_combobox))) {
 	case NO_CHANGE:
 		break;
+
 	case NO_DATE:
 		g_file_info_remove_attribute (info, "general::datetime");
 		break;
+
 	default:
 		{
 			char *exif_date;
@@ -398,32 +399,32 @@ gth_edit_comment_page_real_update_info (GthEditMetadataPage *base,
 
 
 const char *
-gth_edit_comment_page_real_get_name (GthEditMetadataPage *self)
+gth_edit_general_page_real_get_name (GthEditCommentPage *self)
 {
 	return _("General");
 }
 
 
 static void
-gth_edit_comment_page_finalize (GObject *object)
+gth_edit_general_page_finalize (GObject *object)
 {
-	GthEditCommentPage *self;
+	GthEditGeneralPage *self;
 
-	self = GTH_EDIT_COMMENT_PAGE (object);
+	self = GTH_EDIT_GENERAL_PAGE (object);
 
 	_g_object_unref (self->priv->info);
 	g_object_unref (self->priv->builder);
 
-	G_OBJECT_CLASS (gth_edit_comment_page_parent_class)->finalize (object);
+	G_OBJECT_CLASS (gth_edit_general_page_parent_class)->finalize (object);
 }
 
 
 static void
-gth_edit_comment_page_class_init (GthEditCommentPageClass *klass)
+gth_edit_general_page_class_init (GthEditGeneralPageClass *klass)
 {
-	g_type_class_add_private (klass, sizeof (GthEditCommentPagePrivate));
+	g_type_class_add_private (klass, sizeof (GthEditGeneralPagePrivate));
 
-	G_OBJECT_CLASS (klass)->finalize = gth_edit_comment_page_finalize;
+	G_OBJECT_CLASS (klass)->finalize = gth_edit_general_page_finalize;
 }
 
 
@@ -431,7 +432,7 @@ static void
 date_combobox_changed_cb (GtkComboBox *widget,
 			  gpointer     user_data)
 {
-	GthEditCommentPage *self = user_data;
+	GthEditGeneralPage *self = user_data;
 	char               *value;
 
 	value = get_date_from_option (self, gtk_combo_box_get_active (widget), self->priv->info);
@@ -444,7 +445,7 @@ date_combobox_changed_cb (GtkComboBox *widget,
 
 static void
 tags_entry_list_collapsed_cb (GthTagsEntry *widget,
-			      gpointer     user_data)
+			      gpointer      user_data)
 {
 	GtkWidget *toplevel;
 	int        width;
@@ -461,9 +462,9 @@ tags_entry_list_collapsed_cb (GthTagsEntry *widget,
 
 
 static void
-gth_edit_comment_page_init (GthEditCommentPage *self)
+gth_edit_general_page_init (GthEditGeneralPage *self)
 {
-	self->priv = GTH_EDIT_COMMENT_PAGE_GET_PRIVATE (self);
+	self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GTH_TYPE_EDIT_GENERAL_PAGE, GthEditGeneralPagePrivate);
 	self->priv->info = NULL;
 
 	gtk_container_set_border_width (GTK_CONTAINER (self), 12);
@@ -507,9 +508,9 @@ gth_edit_comment_page_init (GthEditCommentPage *self)
 
 
 static void
-gth_edit_comment_page_gth_edit_comment_page_interface_init (GthEditMetadataPageInterface *iface)
+gth_edit_general_page_gth_edit_general_page_interface_init (GthEditCommentPageInterface *iface)
 {
-	iface->set_file_list = gth_edit_comment_page_real_set_file_list;
-	iface->update_info = gth_edit_comment_page_real_update_info;
-	iface->get_name = gth_edit_comment_page_real_get_name;
+	iface->set_file_list = gth_edit_general_page_real_set_file_list;
+	iface->update_info = gth_edit_general_page_real_update_info;
+	iface->get_name = gth_edit_general_page_real_get_name;
 }
diff --git a/extensions/edit_metadata/gth-edit-general-page.h b/extensions/edit_metadata/gth-edit-general-page.h
new file mode 100644
index 0000000..0398daa
--- /dev/null
+++ b/extensions/edit_metadata/gth-edit-general-page.h
@@ -0,0 +1,53 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GTH_EDIT_GENERAL_PAGE_H
+#define GTH_EDIT_GENERAL_PAGE_H
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gthumb.h>
+
+#define GTH_TYPE_EDIT_GENERAL_PAGE         (gth_edit_general_page_get_type ())
+#define GTH_EDIT_GENERAL_PAGE(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), GTH_TYPE_EDIT_GENERAL_PAGE, GthEditGeneralPage))
+#define GTH_EDIT_GENERAL_PAGE_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST ((k), GTH_TYPE_EDIT_GENERAL_PAGE, GthEditGeneralPageClass))
+#define GTH_IS_EDIT_GENERAL_PAGE(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), GTH_TYPE_EDIT_GENERAL_PAGE))
+#define GTH_IS_EDIT_GENERAL_PAGE_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), GTH_TYPE_EDIT_GENERAL_PAGE))
+#define GTH_EDIT_GENERAL_PAGE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS((o), GTH_TYPE_EDIT_GENERAL_PAGE, GthEditGeneralPageClass))
+
+typedef struct _GthEditGeneralPage         GthEditGeneralPage;
+typedef struct _GthEditGeneralPagePrivate  GthEditGeneralPagePrivate;
+typedef struct _GthEditGeneralPageClass    GthEditGeneralPageClass;
+
+struct _GthEditGeneralPage
+{
+	GtkVBox __parent;
+	GthEditGeneralPagePrivate *priv;
+};
+
+struct _GthEditGeneralPageClass
+{
+	GtkVBoxClass __parent_class;	
+};
+
+GType gth_edit_general_page_get_type (void) G_GNUC_CONST;
+
+#endif /* GTH_EDIT_GENERAL_PAGE_H */
diff --git a/extensions/edit_metadata/gth-edit-metadata-dialog.c b/extensions/edit_metadata/gth-edit-metadata-dialog.c
index fcc5912..6621ec5 100644
--- a/extensions/edit_metadata/gth-edit-metadata-dialog.c
+++ b/extensions/edit_metadata/gth-edit-metadata-dialog.c
@@ -3,7 +3,7 @@
 /*
  *  GThumb
  *
- *  Copyright (C) 2009 Free Software Foundation, Inc.
+ *  Copyright (C) 2011 Free Software Foundation, Inc.
  *
  *  This program is free software; you can redistribute it and/or modify
  *  it under the terms of the GNU General Public License as published by
@@ -20,182 +20,30 @@
  */
 
 #include <config.h>
-#include <glib/gi18n.h>
 #include "gth-edit-metadata-dialog.h"
 
 
-G_DEFINE_TYPE (GthEditMetadataDialog,  gth_edit_metadata_dialog, GTK_TYPE_DIALOG)
-
-
-struct _GthEditMetadataDialogPrivate {
-	GtkWidget *notebook;
-	GtkWidget *save_changed_checkbutton;
-};
-
-
-static void
-gth_edit_metadata_dialog_class_init (GthEditMetadataDialogClass *klass)
-{
-	g_type_class_add_private (klass, sizeof (GthEditMetadataDialogPrivate));
-}
+G_DEFINE_INTERFACE (GthEditMetadataDialog, gth_edit_metadata_dialog, 0)
 
 
 static void
-gth_edit_metadata_dialog_init (GthEditMetadataDialog *self)
-{
-	GtkWidget *vbox;
-	GArray    *pages;
-	int        i;
-
-	self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GTH_TYPE_EDIT_METADATA_DIALOG, GthEditMetadataDialogPrivate);
-
-	gtk_window_set_resizable (GTK_WINDOW (self), TRUE);
-	gtk_box_set_spacing (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (self))), 5);
-	gtk_container_set_border_width (GTK_CONTAINER (self), 5);
-
-	gtk_dialog_add_button (GTK_DIALOG (self), GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
-	gtk_dialog_add_button (GTK_DIALOG (self), GTK_STOCK_SAVE, GTK_RESPONSE_APPLY);
-	gtk_dialog_add_button (GTK_DIALOG (self), _("Sa_ve and Close"), GTK_RESPONSE_OK);
-
-	vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
-	gtk_container_set_border_width (GTK_CONTAINER (vbox), 5);
-	gtk_widget_show (vbox);
-	gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (self))), vbox, TRUE, TRUE, 0);
-
-	self->priv->notebook = gtk_notebook_new ();
-	gtk_widget_show (self->priv->notebook);
-	gtk_box_pack_start (GTK_BOX (vbox), self->priv->notebook, TRUE, TRUE, 0);
-
-	self->priv->save_changed_checkbutton = gtk_check_button_new_with_mnemonic (_("Save only cha_nged fields"));
-	gtk_widget_show (self->priv->save_changed_checkbutton);
-	gtk_box_pack_start (GTK_BOX (vbox), self->priv->save_changed_checkbutton, FALSE, FALSE, 0);
-
-	pages = gth_main_get_type_set ("edit-metadata-dialog-page");
-	if (pages == NULL)
-		return;
-
-	for (i = 0; i < pages->len; i++) {
-		GType      page_type;
-		GtkWidget *page;
-
-		page_type = g_array_index (pages, GType, i);
-		page = g_object_new (page_type, NULL);
-		if (! GTH_IS_EDIT_METADATA_PAGE (page)) {
-			g_object_unref (page);
-			continue;
-		}
-
-		gtk_widget_show (page);
-		gtk_notebook_append_page (GTK_NOTEBOOK (self->priv->notebook),
-					  page,
-					  gtk_label_new (gth_edit_metadata_page_get_name (GTH_EDIT_METADATA_PAGE (page))));
-	}
-}
-
-
-GtkWidget *
-gth_edit_metadata_dialog_new (void)
-{
-	return g_object_new (GTH_TYPE_EDIT_METADATA_DIALOG, NULL);
-}
-
-
-void
-gth_edit_metadata_dialog_set_file_list (GthEditMetadataDialog *dialog,
-				        GList                 *file_list)
-{
-	int    n_files;
-	char  *title;
-	GList *pages;
-	GList *scan;
-
-	n_files = g_list_length (file_list);
-	if (n_files == 1) {
-		GthFileData *file_data = file_list->data;
-
-		/* Translators: the %s symbol in the string is a file name */
-		title = g_strdup_printf (_("%s Metadata"), g_file_info_get_display_name (file_data->info));
-		gtk_window_set_title (GTK_WINDOW (dialog), title);
-	}
-	else {
-		title = g_strdup_printf (g_dngettext (NULL, "%d file", "%d files", n_files), n_files);
-		gtk_window_set_title (GTK_WINDOW (dialog), title);
-	}
-
-	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (dialog->priv->save_changed_checkbutton), n_files > 1);
-	gtk_widget_set_sensitive (dialog->priv->save_changed_checkbutton, n_files > 1);
-
-	pages = gtk_container_get_children (GTK_CONTAINER (dialog->priv->notebook));
-	for (scan = pages; scan; scan = scan->next)
-		gth_edit_metadata_page_set_file_list (GTH_EDIT_METADATA_PAGE (scan->data), file_list);
-
-	gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog),
-					   GTK_RESPONSE_APPLY,
-					   n_files > 0);
-	gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog),
-					   GTK_RESPONSE_OK,
-					   n_files > 0);
-
-	g_list_free (pages);
-	g_free (title);
-}
-
-
-void
-gth_edit_metadata_dialog_update_info (GthEditMetadataDialog *dialog,
-				      GList                 *file_list /* GthFileData list */)
-{
-	gboolean  only_modified_fields;
-	GList    *pages;
-	GList    *scan;
-
-	only_modified_fields = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (dialog->priv->save_changed_checkbutton));
-	pages = gtk_container_get_children (GTK_CONTAINER (dialog->priv->notebook));
-	for (scan = pages; scan; scan = scan->next) {
-		GList *scan_file;
-
-		for (scan_file = file_list; scan_file; scan_file = scan_file->next) {
-			GthFileData *file_data = scan_file->data;
-			gth_edit_metadata_page_update_info (GTH_EDIT_METADATA_PAGE (scan->data), file_data->info, only_modified_fields);
-		}
-	}
-
-	g_list_free (pages);
-}
-
-
-/* -- gth_edit_metadata_dialog_page -- */
-
-
-G_DEFINE_INTERFACE (GthEditMetadataPage, gth_edit_metadata_page, 0)
-
-
-static void
-gth_edit_metadata_page_default_init (GthEditMetadataPageInterface *iface)
+gth_edit_metadata_dialog_default_init (GthEditMetadataDialogInterface *iface)
 {
 	/* void */
 }
 
 
 void
-gth_edit_metadata_page_set_file_list (GthEditMetadataPage *self,
-				      GList               *file_list)
+gth_edit_metadata_dialog_set_file_list (GthEditMetadataDialog *self,
+					GList                 *file_list)
 {
-	GTH_EDIT_METADATA_PAGE_GET_INTERFACE (self)->set_file_list (self, file_list);
+	GTH_EDIT_METADATA_DIALOG_GET_INTERFACE (self)->set_file_list (self, file_list);
 }
 
 
 void
-gth_edit_metadata_page_update_info (GthEditMetadataPage *self,
-				    GFileInfo           *info,
-				    gboolean             only_modified_fields)
-{
-	GTH_EDIT_METADATA_PAGE_GET_INTERFACE (self)->update_info (self, info, only_modified_fields);
-}
-
-
-const char *
-gth_edit_metadata_page_get_name (GthEditMetadataPage *self)
+gth_edit_metadata_dialog_update_info (GthEditMetadataDialog *self,
+				      GList                 *file_list)
 {
-	return GTH_EDIT_METADATA_PAGE_GET_INTERFACE (self)->get_name (self);
+	GTH_EDIT_METADATA_DIALOG_GET_INTERFACE (self)->update_info (self, file_list);
 }
diff --git a/extensions/edit_metadata/gth-edit-metadata-dialog.h b/extensions/edit_metadata/gth-edit-metadata-dialog.h
index 9ae14eb..b7ed3c6 100644
--- a/extensions/edit_metadata/gth-edit-metadata-dialog.h
+++ b/extensions/edit_metadata/gth-edit-metadata-dialog.h
@@ -3,7 +3,7 @@
 /*
  *  GThumb
  *
- *  Copyright (C) 2009 Free Software Foundation, Inc.
+ *  Copyright (C) 2011 Free Software Foundation, Inc.
  *
  *  This program is free software; you can redistribute it and/or modify
  *  it under the terms of the GNU General Public License as published by
@@ -18,72 +18,40 @@
  *  You should have received a copy of the GNU General Public License
  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
- 
+
 #ifndef GTH_EDIT_METADATA_DIALOG_H
 #define GTH_EDIT_METADATA_DIALOG_H
 
 #include <gtk/gtk.h>
-#include <gthumb.h>
 
 G_BEGIN_DECLS
 
-#define GTH_TYPE_EDIT_METADATA_DIALOG            (gth_edit_metadata_dialog_get_type ())
-#define GTH_EDIT_METADATA_DIALOG(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTH_TYPE_EDIT_METADATA_DIALOG, GthEditMetadataDialog))
-#define GTH_EDIT_METADATA_DIALOG_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GTH_TYPE_EDIT_METADATA_DIALOG, GthEditMetadataDialogClass))
-#define GTH_IS_EDIT_METADATA_DIALOG(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTH_TYPE_EDIT_METADATA_DIALOG))
-#define GTH_IS_EDIT_METADATA_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTH_TYPE_EDIT_METADATA_DIALOG))
-#define GTH_EDIT_METADATA_DIALOG_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), GTH_TYPE_EDIT_METADATA_DIALOG, GthEditMetadataDialogClass))
-
-#define GTH_TYPE_EDIT_METADATA_PAGE               (gth_edit_metadata_page_get_type ())
-#define GTH_EDIT_METADATA_PAGE(obj)               (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTH_TYPE_EDIT_METADATA_PAGE, GthEditMetadataPage))
-#define GTH_IS_EDIT_METADATA_PAGE(obj)            (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTH_TYPE_EDIT_METADATA_PAGE))
-#define GTH_EDIT_METADATA_PAGE_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), GTH_TYPE_EDIT_METADATA_PAGE, GthEditMetadataPageInterface))
+#define GTH_TYPE_EDIT_METADATA_DIALOG               (gth_edit_metadata_dialog_get_type ())
+#define GTH_EDIT_METADATA_DIALOG(obj)               (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTH_TYPE_EDIT_METADATA_DIALOG, GthEditMetadataDialog))
+#define GTH_IS_EDIT_METADATA_DIALOG(obj)            (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTH_TYPE_EDIT_METADATA_DIALOG))
+#define GTH_EDIT_METADATA_DIALOG_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), GTH_TYPE_EDIT_METADATA_DIALOG, GthEditMetadataDialogInterface))
 
 typedef struct _GthEditMetadataDialog GthEditMetadataDialog;
-typedef struct _GthEditMetadataDialogClass GthEditMetadataDialogClass;
-typedef struct _GthEditMetadataDialogPrivate GthEditMetadataDialogPrivate;
-
-struct _GthEditMetadataDialog {
-	GtkDialog parent_instance;
-	GthEditMetadataDialogPrivate *priv;
-};
-
-struct _GthEditMetadataDialogClass {
-	GtkDialogClass parent_class;
-};
+typedef struct _GthEditMetadataDialogInterface GthEditMetadataDialogInterface;
 
-typedef struct _GthEditMetadataPage GthEditMetadataPage;
-typedef struct _GthEditMetadataPageInterface GthEditMetadataPageInterface;
-
-struct _GthEditMetadataPageInterface {
+struct _GthEditMetadataDialogInterface {
 	GTypeInterface parent_iface;
-	void         (*set_file_list) (GthEditMetadataPage *self,
-				       GList               *file_list /* GthFileData list */);
-	void         (*update_info)   (GthEditMetadataPage *self,
-				       GFileInfo           *info,
-				       gboolean             only_modified_fields);
-	const char * (*get_name)      (GthEditMetadataPage *self);
+
+	void  (*set_file_list)  (GthEditMetadataDialog *dialog,
+				 GList                 *file_list /* GthFileData list */);
+	void  (*update_info)    (GthEditMetadataDialog *dialog,
+				 GList                 *file_list /* GthFileData list */);
 };
 
 /* GthEditMetadataDialog */
 
 GType          gth_edit_metadata_dialog_get_type       (void);
-GtkWidget *    gth_edit_metadata_dialog_new            (void);
 void           gth_edit_metadata_dialog_set_file_list  (GthEditMetadataDialog *dialog,
 						        GList                 *file_list /* GthFileData list */);
 void           gth_edit_metadata_dialog_update_info    (GthEditMetadataDialog *dialog,
-							GList                 *file_list /* GthFileData list */);
-
-/* GthEditMetadataPage */
-
-GType          gth_edit_metadata_page_get_type         (void);
-void           gth_edit_metadata_page_set_file_list    (GthEditMetadataPage   *self,
-							GList                 *file_list /* GthFileData list */);
-void           gth_edit_metadata_page_update_info      (GthEditMetadataPage   *self,
-						        GFileInfo             *info,
-							gboolean               only_modified_fields);
-const char *   gth_edit_metadata_page_get_name         (GthEditMetadataPage   *self);
+						        GList                 *file_list /* GthFileData list */);
 
 G_END_DECLS
 
+
 #endif /* GTH_EDIT_METADATA_DIALOG_H */
diff --git a/extensions/edit_metadata/gth-edit-tags-dialog.c b/extensions/edit_metadata/gth-edit-tags-dialog.c
new file mode 100644
index 0000000..c5d559c
--- /dev/null
+++ b/extensions/edit_metadata/gth-edit-tags-dialog.c
@@ -0,0 +1,272 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2011 Free Software Foundation, Inc.
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <glib/gi18n.h>
+#include "gth-edit-metadata-dialog.h"
+#include "gth-edit-tags-dialog.h"
+
+
+#define GET_WIDGET(name) _gtk_builder_get_widget (self->priv->builder, (name))
+
+
+struct _GthEditTagsDialogPrivate {
+	GtkBuilder *builder;
+	GtkWidget  *tags_entry;
+};
+
+
+static void gth_edit_tags_dialog_gth_edit_metadata_dialog_interface_init (GthEditMetadataDialogInterface *iface);
+
+
+G_DEFINE_TYPE_WITH_CODE (GthEditTagsDialog,
+			 gth_edit_tags_dialog,
+			 GTK_TYPE_DIALOG,
+			 G_IMPLEMENT_INTERFACE (GTH_TYPE_EDIT_METADATA_DIALOG,
+					 	gth_edit_tags_dialog_gth_edit_metadata_dialog_interface_init))
+
+
+static void
+gth_edit_tags_dialog_finalize (GObject *object)
+{
+	GthEditTagsDialog *self;
+
+	self = GTH_EDIT_TAGS_DIALOG (object);
+	_g_object_unref (self->priv->builder);
+
+	G_OBJECT_CLASS (gth_edit_tags_dialog_parent_class)->finalize (object);
+}
+
+
+static gboolean
+remove_tag_if_not_present (gpointer key,
+                	   gpointer value,
+                	   gpointer user_data)
+{
+	GthStringList *file_tags = user_data;
+
+	return g_list_find_custom (gth_string_list_get_list (file_tags), key, (GCompareFunc) g_strcmp0) == NULL;
+}
+
+
+static void
+gth_edit_tags_dialog_set_file_list (GthEditMetadataDialog *base,
+				    GList                 *file_list)
+{
+	GthEditTagsDialog *self = GTH_EDIT_TAGS_DIALOG (base);
+	int                n_files;
+	char              *title;
+	GList             *scan;
+	GHashTable        *all_tags;
+	GHashTable        *common_tags;
+	GHashTable        *no_common_tags;
+	GList             *all_tags_list;
+	GList             *scan_tags;
+	GList             *common_tags_list;
+	GList             *no_common_tags_list;
+
+	n_files = g_list_length (file_list);
+
+	/* update the title */
+
+	if (n_files == 1) {
+		GthFileData *file_data = file_list->data;
+
+		/* Translators: the %s symbol in the string is a file name */
+		title = g_strdup_printf (_("%s Tags"), g_file_info_get_display_name (file_data->info));
+	}
+	else
+		title = g_strdup_printf (g_dngettext (NULL, "%d file", "%d files", n_files), n_files);
+	gtk_window_set_title (GTK_WINDOW (self), title);
+
+	g_free (title);
+
+	/* update the tag entry */
+
+	all_tags = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+	common_tags = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+	for (scan = file_list; scan; scan = scan->next) {
+		GthFileData   *file_data = scan->data;
+		GthStringList *file_tags;
+
+		file_tags = (GthStringList *) g_file_info_get_attribute_object (file_data->info, "general::tags");
+		if (file_tags != NULL) {
+			GList *scan_tags;
+
+			for (scan_tags = gth_string_list_get_list (file_tags);
+			     scan_tags != NULL;
+			     scan_tags = scan_tags->next)
+			{
+				char *tag = scan_tags->data;
+
+				/* update the all tags set */
+
+				if (g_hash_table_lookup (all_tags, tag) == NULL)
+					g_hash_table_insert (all_tags, g_strdup (tag), GINT_TO_POINTER (1));
+
+				/* update the common tags set */
+
+				if (scan == file_list)
+					g_hash_table_insert (common_tags, g_strdup (tag), GINT_TO_POINTER (1));
+				else
+					g_hash_table_foreach_remove (common_tags, remove_tag_if_not_present, file_tags);
+			}
+		}
+		else
+			g_hash_table_remove_all (common_tags);
+	}
+
+	no_common_tags = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+	all_tags_list = g_hash_table_get_keys (all_tags);
+	for (scan_tags = all_tags_list; scan_tags; scan_tags = scan_tags->next) {
+		char *tag = scan_tags->data;
+
+		if (g_hash_table_lookup (common_tags, tag) == NULL)
+			g_hash_table_insert (no_common_tags, g_strdup (tag), GINT_TO_POINTER (1));
+	}
+
+	common_tags_list = g_hash_table_get_keys (common_tags);
+	no_common_tags_list = g_hash_table_get_keys (no_common_tags);
+	gth_tags_entry_set_tag_list (GTH_TAGS_ENTRY (self->priv->tags_entry),
+				     common_tags_list,
+				     no_common_tags_list);
+
+	g_list_free (no_common_tags_list);
+	g_list_free (common_tags_list);
+	g_list_free (all_tags_list);
+	g_hash_table_unref (no_common_tags);
+	g_hash_table_unref (common_tags);
+	g_hash_table_unref (all_tags);
+}
+
+
+static GHashTable *
+_g_hash_table_from_string_list (GthStringList *list)
+{
+	GHashTable *h;
+	GList      *scan;
+
+	h = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+	for (scan = gth_string_list_get_list (list); scan; scan = scan->next)
+		g_hash_table_insert (h, g_strdup (scan->data), GINT_TO_POINTER (1));
+
+	return h;
+}
+
+
+static void
+gth_edit_tags_dialog_update_info (GthEditMetadataDialog *base,
+				  GList                 *file_list /* GthFileData list */)
+{
+	GthEditTagsDialog *self = GTH_EDIT_TAGS_DIALOG (base);
+	GList             *checked_tags;
+	GList             *inconsistent_tags;
+	GList             *scan;
+
+	gth_tags_entry_get_tag_list (GTH_TAGS_ENTRY (self->priv->tags_entry),
+				     TRUE,
+				     &checked_tags,
+				     &inconsistent_tags);
+
+	for (scan = file_list; scan; scan = scan->next) {
+		GthFileData *file_data = scan->data;
+		GList       *new_tags;
+		GHashTable  *old_tags;
+		GList       *scan_tags;
+
+		new_tags = _g_string_list_dup (checked_tags);
+
+		/* keep the inconsistent tags */
+
+		old_tags = _g_hash_table_from_string_list ((GthStringList *) g_file_info_get_attribute_object (file_data->info, "general::tags"));
+		for (scan_tags = inconsistent_tags; scan_tags; scan_tags = scan_tags->next) {
+			char *inconsistent_tag = scan_tags->data;
+
+			if (g_hash_table_lookup (old_tags, inconsistent_tag) != NULL)
+				new_tags = g_list_prepend (new_tags, g_strdup (inconsistent_tag));
+		}
+		g_hash_table_unref (old_tags);
+
+		/* update the general::tags attribute */
+
+		if (new_tags != NULL) {
+			GthStringList *file_tags;
+
+			new_tags = g_list_sort (new_tags, (GCompareFunc) g_strcmp0);
+			file_tags = gth_string_list_new (new_tags);
+			g_file_info_set_attribute_object (file_data->info, "general::tags", G_OBJECT (file_tags));
+
+			_g_object_unref (file_tags);
+			_g_string_list_free (new_tags);
+		}
+		else
+			g_file_info_remove_attribute (file_data->info, "general::tags");
+	}
+
+	g_list_free (inconsistent_tags);
+	_g_string_list_free (checked_tags);
+}
+
+
+static void
+gth_edit_tags_dialog_gth_edit_metadata_dialog_interface_init (GthEditMetadataDialogInterface *iface)
+{
+	iface->set_file_list = gth_edit_tags_dialog_set_file_list;
+	iface->update_info = gth_edit_tags_dialog_update_info;
+}
+
+
+static void
+gth_edit_tags_dialog_class_init (GthEditTagsDialogClass *klass)
+{
+	GObjectClass *object_class;
+
+	g_type_class_add_private (klass, sizeof (GthEditTagsDialogPrivate));
+
+	object_class = (GObjectClass*) klass;
+	object_class->finalize = gth_edit_tags_dialog_finalize;
+}
+
+
+static void
+gth_edit_tags_dialog_init (GthEditTagsDialog *self)
+{
+	self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GTH_TYPE_EDIT_TAGS_DIALOG, GthEditTagsDialogPrivate);
+	self->priv->builder = _gtk_builder_new_from_file ("tag-chooser.ui", "edit_metadata");
+
+	gtk_window_set_title (GTK_WINDOW (self), _("Assign Tags"));
+	gtk_window_set_resizable (GTK_WINDOW (self), TRUE);
+	gtk_window_set_default_size (GTK_WINDOW (self), -1, 500);
+	gtk_box_set_spacing (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (self))), 5);
+	gtk_container_set_border_width (GTK_CONTAINER (self), 5);
+
+	gtk_dialog_add_button (GTK_DIALOG (self), GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
+	gtk_dialog_add_button (GTK_DIALOG (self), GTK_STOCK_SAVE, GTK_RESPONSE_APPLY);
+	gtk_dialog_add_button (GTK_DIALOG (self), _("Sa_ve and Close"), GTK_RESPONSE_OK);
+
+	self->priv->tags_entry = gth_tags_entry_new ();
+	gth_tags_entry_set_expanded (GTH_TAGS_ENTRY (self->priv->tags_entry), TRUE);
+	gtk_widget_show (self->priv->tags_entry);
+	gtk_box_pack_start (GTK_BOX (GET_WIDGET ("tag_entry_box")), self->priv->tags_entry, TRUE, TRUE, 0);
+
+	gtk_container_set_border_width (GTK_CONTAINER (GET_WIDGET ("content")), 5);
+	gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (self))), GET_WIDGET ("content"), TRUE, TRUE, 0);
+}
diff --git a/extensions/edit_metadata/gth-edit-tags-dialog.h b/extensions/edit_metadata/gth-edit-tags-dialog.h
new file mode 100644
index 0000000..362ac0e
--- /dev/null
+++ b/extensions/edit_metadata/gth-edit-tags-dialog.h
@@ -0,0 +1,56 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2011 Free Software Foundation, Inc.
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+ 
+#ifndef GTH_EDIT_TAGS_DIALOG_H
+#define GTH_EDIT_TAGS_DIALOG_H
+
+#include <gtk/gtk.h>
+#include <gthumb.h>
+
+G_BEGIN_DECLS
+
+#define GTH_TYPE_EDIT_TAGS_DIALOG            (gth_edit_tags_dialog_get_type ())
+#define GTH_EDIT_TAGS_DIALOG(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTH_TYPE_EDIT_TAGS_DIALOG, GthEditTagsDialog))
+#define GTH_EDIT_TAGS_DIALOG_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GTH_TYPE_EDIT_TAGS_DIALOG, GthEditTagsDialogClass))
+#define GTH_IS_EDIT_TAGS_DIALOG(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTH_TYPE_EDIT_TAGS_DIALOG))
+#define GTH_IS_EDIT_TAGS_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTH_TYPE_EDIT_TAGS_DIALOG))
+#define GTH_EDIT_TAGS_DIALOG_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), GTH_TYPE_EDIT_TAGS_DIALOG, GthEditTagsDialogClass))
+
+typedef struct _GthEditTagsDialog GthEditTagsDialog;
+typedef struct _GthEditTagsDialogClass GthEditTagsDialogClass;
+typedef struct _GthEditTagsDialogPrivate GthEditTagsDialogPrivate;
+
+struct _GthEditTagsDialog {
+	GtkDialog parent_instance;
+	GthEditTagsDialogPrivate *priv;
+};
+
+struct _GthEditTagsDialogClass {
+	GtkDialogClass parent_class;
+};
+
+GType          gth_edit_tags_dialog_get_type  (void);
+GtkWidget *    gth_edit_tags_dialog_new       (void);
+
+
+G_END_DECLS
+
+#endif /* GTH_EDIT_TAGS_DIALOG_H */
diff --git a/extensions/edit_metadata/main.c b/extensions/edit_metadata/main.c
index 7d91719..85b7df4 100644
--- a/extensions/edit_metadata/main.c
+++ b/extensions/edit_metadata/main.c
@@ -24,7 +24,7 @@
 #include <gtk/gtk.h>
 #include <gthumb.h>
 #include "callbacks.h"
-#include "gth-edit-comment-page.h"
+#include "gth-edit-general-page.h"
 
 
 G_MODULE_EXPORT void
@@ -39,12 +39,10 @@ gthumb_extension_activate (void)
 	 **/
 	gth_hook_register ("delete-metadata", 3);
 
-	gth_main_register_type ("edit-metadata-dialog-page", GTH_TYPE_EDIT_COMMENT_PAGE);
+	gth_main_register_type ("edit-comment-dialog-page", GTH_TYPE_EDIT_GENERAL_PAGE);
 	gth_hook_add_callback ("gth-browser-construct", 7, G_CALLBACK (edit_metadata__gth_browser_construct_cb), NULL);
 	gth_hook_add_callback ("gth-browser-set-current-page", 5, G_CALLBACK (edit_metadata__gth_browser_set_current_page_cb), NULL);
 	gth_hook_add_callback ("gth-browser-update-sensitivity", 10, G_CALLBACK (edit_metadata__gth_browser_update_sensitivity_cb), NULL);
-	gth_hook_add_callback ("gth-browser-file-list-popup-before", 5, G_CALLBACK (edit_metadata__gth_browser_file_list_popup_before_cb), NULL);
-	gth_hook_add_callback ("gth-browser-file-popup-before", 5, G_CALLBACK (edit_metadata__gth_browser_file_popup_before_cb), NULL);
 	gth_hook_add_callback ("gth-browser-file-list-key-press", 10, G_CALLBACK (edit_metadata__gth_browser_file_list_key_press_cb), NULL);
 }
 
diff --git a/extensions/exiv2_tools/Makefile.am b/extensions/exiv2_tools/Makefile.am
index 26a127f..cb5f583 100644
--- a/extensions/exiv2_tools/Makefile.am
+++ b/extensions/exiv2_tools/Makefile.am
@@ -8,8 +8,8 @@ extension_LTLIBRARIES = libexiv2_tools.la
 libexiv2_tools_la_SOURCES = 		\
 	exiv2-utils.h			\
 	exiv2-utils.cpp			\
-	gth-edit-exiv2-page.c		\
-	gth-edit-exiv2-page.h		\
+	gth-edit-iptc-page.c		\
+	gth-edit-iptc-page.h		\
 	gth-metadata-provider-exiv2.c	\
 	gth-metadata-provider-exiv2.h	\
 	main.c
diff --git a/extensions/exiv2_tools/gth-edit-exiv2-page.c b/extensions/exiv2_tools/gth-edit-iptc-page.c
similarity index 80%
rename from extensions/exiv2_tools/gth-edit-exiv2-page.c
rename to extensions/exiv2_tools/gth-edit-iptc-page.c
index d66ec9b..bc4fe7a 100644
--- a/extensions/exiv2_tools/gth-edit-exiv2-page.c
+++ b/extensions/exiv2_tools/gth-edit-iptc-page.c
@@ -22,25 +22,25 @@
 #include <config.h>
 #include <glib/gi18n.h>
 #include <gio/gio.h>
-#include <extensions/edit_metadata/gth-edit-metadata-dialog.h>
+#include <extensions/edit_metadata/gth-edit-comment-dialog.h>
 #include "exiv2-utils.h"
-#include "gth-edit-exiv2-page.h"
+#include "gth-edit-iptc-page.h"
 
 
 #define GET_WIDGET(name) _gtk_builder_get_widget (self->priv->builder, (name))
 
 
-static void gth_edit_exiv2_page_gth_edit_exiv2_page_interface_init (GthEditMetadataPageInterface *iface);
+static void gth_edit_iptc_page_gth_edit_comment_page_interface_init (GthEditCommentPageInterface *iface);
 
 
-G_DEFINE_TYPE_WITH_CODE (GthEditExiv2Page,
-			 gth_edit_exiv2_page,
+G_DEFINE_TYPE_WITH_CODE (GthEditIptcPage,
+			 gth_edit_iptc_page,
 			 GTK_TYPE_VBOX,
-			 G_IMPLEMENT_INTERFACE (GTH_TYPE_EDIT_METADATA_PAGE,
-					        gth_edit_exiv2_page_gth_edit_exiv2_page_interface_init))
+			 G_IMPLEMENT_INTERFACE (GTH_TYPE_EDIT_COMMENT_PAGE,
+					        gth_edit_iptc_page_gth_edit_comment_page_interface_init))
 
 
-struct _GthEditExiv2PagePrivate {
+struct _GthEditIptcPagePrivate {
 	GtkBuilder *builder;
 	gboolean    supported;
 	GFileInfo  *info;
@@ -48,7 +48,7 @@ struct _GthEditExiv2PagePrivate {
 
 
 static void
-set_entry_value (GthEditExiv2Page *self,
+set_entry_value (GthEditIptcPage *self,
 		 GFileInfo        *info,
 		 const char       *attribute,
 		 const char       *widget_id)
@@ -64,14 +64,14 @@ set_entry_value (GthEditExiv2Page *self,
 
 
 void
-gth_edit_exiv2_page_real_set_file_list (GthEditMetadataPage *base,
-		 		        GList               *file_data_list)
+gth_edit_iptc_page_real_set_file_list (GthEditCommentPage *base,
+		 		        GList              *file_data_list)
 {
-	GthEditExiv2Page *self;
+	GthEditIptcPage *self;
 	GList            *scan;
 	GthMetadata      *metadata;
 
-	self = GTH_EDIT_EXIV2_PAGE (base);
+	self = GTH_EDIT_IPTC_PAGE (base);
 
 	self->priv->supported = TRUE;
 	for (scan = file_data_list; self->priv->supported && scan; scan = scan->next) {
@@ -117,7 +117,7 @@ gth_edit_exiv2_page_real_set_file_list (GthEditMetadataPage *base,
 
 
 static void
-set_attribute_from_entry (GthEditExiv2Page *self,
+set_attribute_from_entry (GthEditIptcPage *self,
 			  GFileInfo        *info,
 			  GthFileData      *file_data,
 			  gboolean          only_modified_fields,
@@ -142,16 +142,16 @@ set_attribute_from_entry (GthEditExiv2Page *self,
 
 
 void
-gth_edit_exiv2_page_real_update_info (GthEditMetadataPage *base,
-				      GFileInfo           *info,
-				      gboolean             only_modified_fields)
+gth_edit_iptc_page_real_update_info (GthEditCommentPage *base,
+				      GFileInfo          *info,
+				      gboolean            only_modified_fields)
 {
-	GthEditExiv2Page *self;
+	GthEditIptcPage *self;
 	GthFileData      *file_data;
 	double            v;
 	char             *s;
 
-	self = GTH_EDIT_EXIV2_PAGE (base);
+	self = GTH_EDIT_IPTC_PAGE (base);
 
 	if (! self->priv->supported)
 		return;
@@ -193,38 +193,38 @@ gth_edit_exiv2_page_real_update_info (GthEditMetadataPage *base,
 
 
 const char *
-gth_edit_exiv2_page_real_get_name (GthEditMetadataPage *self)
+gth_edit_iptc_page_real_get_name (GthEditCommentPage *self)
 {
 	return _("Other");
 }
 
 
 static void
-gth_edit_exiv2_page_finalize (GObject *object)
+gth_edit_iptc_page_finalize (GObject *object)
 {
-	GthEditExiv2Page *self;
+	GthEditIptcPage *self;
 
-	self = GTH_EDIT_EXIV2_PAGE (object);
+	self = GTH_EDIT_IPTC_PAGE (object);
 
 	_g_object_unref (self->priv->info);
 	g_object_unref (self->priv->builder);
 
-	G_OBJECT_CLASS (gth_edit_exiv2_page_parent_class)->finalize (object);
+	G_OBJECT_CLASS (gth_edit_iptc_page_parent_class)->finalize (object);
 }
 
 
 static void
-gth_edit_exiv2_page_class_init (GthEditExiv2PageClass *klass)
+gth_edit_iptc_page_class_init (GthEditIptcPageClass *klass)
 {
-	g_type_class_add_private (klass, sizeof (GthEditExiv2PagePrivate));
-	G_OBJECT_CLASS (klass)->finalize = gth_edit_exiv2_page_finalize;
+	g_type_class_add_private (klass, sizeof (GthEditIptcPagePrivate));
+	G_OBJECT_CLASS (klass)->finalize = gth_edit_iptc_page_finalize;
 }
 
 
 static void
-gth_edit_exiv2_page_init (GthEditExiv2Page *self)
+gth_edit_iptc_page_init (GthEditIptcPage *self)
 {
-	self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GTH_TYPE_EDIT_EXIV2_PAGE, GthEditExiv2PagePrivate);
+	self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GTH_TYPE_EDIT_IPTC_PAGE, GthEditIptcPagePrivate);
 	self->priv->info = NULL;
 
 	gtk_container_set_border_width (GTK_CONTAINER (self), 12);
@@ -235,9 +235,9 @@ gth_edit_exiv2_page_init (GthEditExiv2Page *self)
 
 
 static void
-gth_edit_exiv2_page_gth_edit_exiv2_page_interface_init (GthEditMetadataPageInterface *iface)
+gth_edit_iptc_page_gth_edit_comment_page_interface_init (GthEditCommentPageInterface *iface)
 {
-	iface->set_file_list = gth_edit_exiv2_page_real_set_file_list;
-	iface->update_info = gth_edit_exiv2_page_real_update_info;
-	iface->get_name = gth_edit_exiv2_page_real_get_name;
+	iface->set_file_list = gth_edit_iptc_page_real_set_file_list;
+	iface->update_info = gth_edit_iptc_page_real_update_info;
+	iface->get_name = gth_edit_iptc_page_real_get_name;
 }
diff --git a/extensions/exiv2_tools/gth-edit-iptc-page.h b/extensions/exiv2_tools/gth-edit-iptc-page.h
new file mode 100644
index 0000000..d99fad1
--- /dev/null
+++ b/extensions/exiv2_tools/gth-edit-iptc-page.h
@@ -0,0 +1,53 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GTH_EDIT_IPTC_PAGE_H
+#define GTH_EDIT_IPTC_PAGE_H
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gthumb.h>
+
+#define GTH_TYPE_EDIT_IPTC_PAGE         (gth_edit_iptc_page_get_type ())
+#define GTH_EDIT_IPTC_PAGE(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), GTH_TYPE_EDIT_IPTC_PAGE, GthEditIptcPage))
+#define GTH_EDIT_IPTC_PAGE_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST ((k), GTH_TYPE_EDIT_IPTC_PAGE, GthEditIptcPageClass))
+#define GTH_IS_EDIT_IPTC_PAGE(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), GTH_TYPE_EDIT_IPTC_PAGE))
+#define GTH_IS_EDIT_IPTC_PAGE_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), GTH_TYPE_EDIT_IPTC_PAGE))
+#define GTH_EDIT_IPTC_PAGE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS((o), GTH_TYPE_EDIT_IPTC_PAGE, GthEditIptcPageClass))
+
+typedef struct _GthEditIptcPage         GthEditIptcPage;
+typedef struct _GthEditIptcPagePrivate  GthEditIptcPagePrivate;
+typedef struct _GthEditIptcPageClass    GthEditIptcPageClass;
+
+struct _GthEditIptcPage
+{
+	GtkVBox __parent;
+	GthEditIptcPagePrivate *priv;
+};
+
+struct _GthEditIptcPageClass
+{
+	GtkVBoxClass __parent_class;	
+};
+
+GType gth_edit_iptc_page_get_type (void) G_GNUC_CONST;
+
+#endif /* GTH_EDIT_IPTC_PAGE_H */
diff --git a/extensions/exiv2_tools/main.c b/extensions/exiv2_tools/main.c
index 21df433..6c8f748 100644
--- a/extensions/exiv2_tools/main.c
+++ b/extensions/exiv2_tools/main.c
@@ -24,7 +24,7 @@
 #include <gtk/gtk.h>
 #include <gthumb.h>
 #include <extensions/jpeg_utils/jpegtran.h>
-#include "gth-edit-exiv2-page.h"
+#include "gth-edit-iptc-page.h"
 #include "gth-metadata-provider-exiv2.h"
 #include "exiv2-utils.h"
 
@@ -275,7 +275,7 @@ gthumb_extension_activate (void)
 	gth_main_register_metadata_info_v (exiv2_metadata_info);
 	gth_main_register_metadata_provider (GTH_TYPE_METADATA_PROVIDER_EXIV2);
 	if (gth_main_extension_is_active ("edit_metadata")) {
-		gth_main_register_type ("edit-metadata-dialog-page", GTH_TYPE_EDIT_EXIV2_PAGE);
+		gth_main_register_type ("edit-comment-dialog-page", GTH_TYPE_EDIT_IPTC_PAGE);
 		gth_hook_add_callback ("delete-metadata", 10, G_CALLBACK (exiv2_delete_metadata_cb), NULL);
 	}
 	gth_hook_add_callback ("save-pixbuf", 10, G_CALLBACK (exiv2_write_metadata), NULL);
diff --git a/gthumb/dlg-preferences-extensions.c b/gthumb/dlg-preferences-extensions.c
index 30739b0..c71498f 100644
--- a/gthumb/dlg-preferences-extensions.c
+++ b/gthumb/dlg-preferences-extensions.c
@@ -786,7 +786,7 @@ extensions__dlg_preferences_apply (GtkWidget  *dialog,
 	BrowserData         *data;
 	GList               *active_extensions;
 	GthExtensionManager *manager;
-	GList               *names;
+	GList               *extension_names;
 	GList               *scan;
 
 	data = g_object_get_data (G_OBJECT (dialog), BROWSER_DATA_KEY);
@@ -794,17 +794,17 @@ extensions__dlg_preferences_apply (GtkWidget  *dialog,
 
 	active_extensions = NULL;
 	manager = gth_main_get_default_extension_manager ();
-	names = gth_extension_manager_get_extensions (manager);
-	for (scan = names; scan; scan = scan->next) {
-		char                    *name = scan->data;
+	extension_names = gth_extension_manager_get_extensions (manager);
+	for (scan = extension_names; scan; scan = scan->next) {
+		char                    *extension_name = scan->data;
 		GthExtensionDescription *description;
 
-		description = gth_extension_manager_get_description (manager, name);
+		description = gth_extension_manager_get_description (manager, extension_name);
 		if ((description == NULL) || description->mandatory || description->hidden)
 			continue;
 
 		if (gth_extension_description_is_active (description))
-			active_extensions = g_list_prepend (active_extensions, g_strdup (name));
+			active_extensions = g_list_prepend (active_extensions, g_strdup (extension_name));
 	}
 	active_extensions = g_list_reverse (active_extensions);
 	_g_settings_set_string_list (data->settings, PREF_GENERAL_ACTIVE_EXTENSIONS, active_extensions);
@@ -830,4 +830,5 @@ extensions__dlg_preferences_apply (GtkWidget  *dialog,
 
 	g_list_foreach (active_extensions, (GFunc) g_free, NULL);
 	g_list_free (active_extensions);
+	g_list_free (extension_names);
 }
diff --git a/gthumb/gth-browser-actions-callbacks.h b/gthumb/gth-browser-actions-callbacks.h
index 5b61549..d2b7860 100644
--- a/gthumb/gth-browser-actions-callbacks.h
+++ b/gthumb/gth-browser-actions-callbacks.h
@@ -29,7 +29,7 @@
 DEFINE_ACTION(gth_browser_activate_action_bookmarks_add)
 DEFINE_ACTION(gth_browser_activate_action_bookmarks_edit)
 DEFINE_ACTION(gth_browser_activate_action_browser_mode)
-DEFINE_ACTION(gth_browser_activate_action_edit_metadata)
+DEFINE_ACTION(gth_browser_activate_action_edit_comment)
 DEFINE_ACTION(gth_browser_activate_action_edit_preferences)
 DEFINE_ACTION(gth_browser_activate_action_edit_select_all)
 DEFINE_ACTION(gth_browser_activate_action_file_open)
diff --git a/gthumb/gth-tags-entry.c b/gthumb/gth-tags-entry.c
index d571ec4..bb841b1 100644
--- a/gthumb/gth-tags-entry.c
+++ b/gthumb/gth-tags-entry.c
@@ -38,6 +38,7 @@ enum {
 
 enum {
 	EXPANDED_LIST_USED_COLUMN,
+	EXPANDED_LIST_INCONSISTENT_COLUMN,
 	EXPANDED_LIST_SEPARATOR_COLUMN,
 	EXPANDED_LIST_NAME_COLUMN,
 	EXPANDED_LIST_N_COLUMNS
@@ -55,6 +56,7 @@ typedef struct {
 	char     *name;
 	gboolean  used;
 	gboolean  suggested;
+	gboolean  inconsistent;
 } TagData;
 
 
@@ -70,12 +72,14 @@ struct _GthTagsEntryPrivate {
 	GtkWidget           *entry;
 	GtkWidget           *expand_button;
 	ExpandedList         expanded_list;
+	gboolean             expanded;
 	char               **tags;
 	GtkEntryCompletion  *completion;
 	GtkListStore        *completion_store;
 	char                *new_tag;
 	gboolean             action_create;
 	gulong               monitor_event;
+	GHashTable          *inconsistent;
 };
 
 
@@ -98,6 +102,7 @@ gth_tags_entry_finalize (GObject *obj)
 	g_object_unref (self->priv->completion);
 	g_strfreev (self->priv->tags);
 	g_strfreev (self->priv->expanded_list.last_used);
+	g_hash_table_unref (self->priv->inconsistent);
 
 	G_OBJECT_CLASS (gth_tags_entry_parent_class)->finalize (obj);
 }
@@ -240,9 +245,12 @@ update_expanded_list_from_entry (GthTagsEntry *self)
 		tag_data[i]->name = g_strdup (all_tags[i]);
 		tag_data[i]->suggested = FALSE;
 		tag_data[i]->used = FALSE;
+		tag_data[i]->inconsistent = (g_hash_table_lookup (self->priv->inconsistent, tag_data[i]->name) != NULL);
 		for (j = 0; ! tag_data[i]->used && (used_tags[j] != NULL); j++)
-			if (g_utf8_collate (tag_data[i]->name, used_tags[j]) == 0)
+			if (g_utf8_collate (tag_data[i]->name, used_tags[j]) == 0) {
 				tag_data[i]->used = TRUE;
+				tag_data[i]->inconsistent = FALSE;
+			}
 
 		if (! tag_data[i]->used)
 			for (j = 0; ! tag_data[i]->suggested && (self->priv->expanded_list.last_used[j] != NULL); j++)
@@ -273,6 +281,7 @@ update_expanded_list_from_entry (GthTagsEntry *self)
 		gtk_list_store_append (self->priv->expanded_list.store, &iter);
 		gtk_list_store_set (self->priv->expanded_list.store, &iter,
 				    EXPANDED_LIST_USED_COLUMN, TRUE,
+				    EXPANDED_LIST_INCONSISTENT_COLUMN, tag_data[i]->inconsistent,
 				    EXPANDED_LIST_SEPARATOR_COLUMN, FALSE,
 				    EXPANDED_LIST_NAME_COLUMN, tag_data[i]->name,
 				    -1);
@@ -282,6 +291,7 @@ update_expanded_list_from_entry (GthTagsEntry *self)
 		gtk_list_store_append (self->priv->expanded_list.store, &iter);
 		gtk_list_store_set (self->priv->expanded_list.store, &iter,
 				    EXPANDED_LIST_USED_COLUMN, FALSE,
+				    EXPANDED_LIST_INCONSISTENT_COLUMN, FALSE,
 				    EXPANDED_LIST_SEPARATOR_COLUMN, TRUE,
 				    EXPANDED_LIST_NAME_COLUMN, "",
 				    -1);
@@ -301,6 +311,7 @@ update_expanded_list_from_entry (GthTagsEntry *self)
 		gtk_list_store_append (self->priv->expanded_list.store, &iter);
 		gtk_list_store_set (self->priv->expanded_list.store, &iter,
 				    EXPANDED_LIST_USED_COLUMN, FALSE,
+				    EXPANDED_LIST_INCONSISTENT_COLUMN, tag_data[i]->inconsistent,
 				    EXPANDED_LIST_SEPARATOR_COLUMN, FALSE,
 				    EXPANDED_LIST_NAME_COLUMN, tag_data[i]->name,
 				    -1);
@@ -310,6 +321,7 @@ update_expanded_list_from_entry (GthTagsEntry *self)
 		gtk_list_store_append (self->priv->expanded_list.store, &iter);
 		gtk_list_store_set (self->priv->expanded_list.store, &iter,
 				    EXPANDED_LIST_USED_COLUMN, FALSE,
+				    EXPANDED_LIST_INCONSISTENT_COLUMN, FALSE,
 				    EXPANDED_LIST_SEPARATOR_COLUMN, TRUE,
 				    EXPANDED_LIST_NAME_COLUMN, "",
 				    -1);
@@ -326,6 +338,7 @@ update_expanded_list_from_entry (GthTagsEntry *self)
 		gtk_list_store_append (self->priv->expanded_list.store, &iter);
 		gtk_list_store_set (self->priv->expanded_list.store, &iter,
 				    EXPANDED_LIST_USED_COLUMN, FALSE,
+				    EXPANDED_LIST_INCONSISTENT_COLUMN, tag_data[i]->inconsistent,
 				    EXPANDED_LIST_SEPARATOR_COLUMN, FALSE,
 				    EXPANDED_LIST_NAME_COLUMN, tag_data[i]->name,
 				    -1);
@@ -618,14 +631,21 @@ cell_renderer_toggle_toggled_cb (GtkCellRendererToggle *cell_renderer,
 
 	tree_model = gtk_tree_view_get_model (GTK_TREE_VIEW (self->priv->expanded_list.tree_view));
 	if (gtk_tree_model_get_iter (tree_model, &iter, tpath)) {
-		gboolean used;
+		char     *tag;
+		gboolean  used;
 
 		gtk_tree_model_get (tree_model, &iter,
+				    EXPANDED_LIST_NAME_COLUMN, &tag,
 				    EXPANDED_LIST_USED_COLUMN, &used,
 				    -1);
+
+		g_hash_table_remove (self->priv->inconsistent, tag);
 		gtk_list_store_set (GTK_LIST_STORE (tree_model), &iter,
 				    EXPANDED_LIST_USED_COLUMN, ! used,
+				    EXPANDED_LIST_INCONSISTENT_COLUMN, FALSE,
 				    -1);
+
+		g_free (tag);
 	}
 
 	update_entry_from_expanded_list (self);
@@ -679,6 +699,8 @@ gth_tags_entry_init (GthTagsEntry *self)
 
 	self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GTH_TYPE_TAGS_ENTRY, GthTagsEntryPrivate);
 	self->priv->expanded_list.last_used = g_new0 (char *, 1);
+	self->priv->expanded = FALSE;
+	self->priv->inconsistent = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
 
 	gtk_box_set_spacing (GTK_BOX (self), 3);
 
@@ -729,7 +751,11 @@ gth_tags_entry_init (GthTagsEntry *self)
 
 	/* expanded list, the treeview */
 
-	self->priv->expanded_list.store = gtk_list_store_new (EXPANDED_LIST_N_COLUMNS, G_TYPE_BOOLEAN, G_TYPE_BOOLEAN, G_TYPE_STRING);
+	self->priv->expanded_list.store = gtk_list_store_new (EXPANDED_LIST_N_COLUMNS,
+							      G_TYPE_BOOLEAN,
+							      G_TYPE_BOOLEAN,
+							      G_TYPE_BOOLEAN,
+							      G_TYPE_STRING);
 	self->priv->expanded_list.tree_view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (self->priv->expanded_list.store));
 	gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (self->priv->expanded_list.tree_view), FALSE);
 	gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (self->priv->expanded_list.tree_view),
@@ -749,6 +775,7 @@ gth_tags_entry_init (GthTagsEntry *self)
 	gtk_tree_view_column_pack_start (column, renderer, FALSE);
 	gtk_tree_view_column_set_attributes (column, renderer,
 					     "active", EXPANDED_LIST_USED_COLUMN,
+					     "inconsistent", EXPANDED_LIST_INCONSISTENT_COLUMN,
 					     NULL);
 
 	/* the name column. */
@@ -804,38 +831,24 @@ gth_tags_entry_new (void)
 }
 
 
-char **
-gth_tags_entry_get_tags (GthTagsEntry *self,
-			 gboolean      update_globals)
+void
+gth_tags_entry_set_expanded (GthTagsEntry *self,
+			     gboolean      expanded)
 {
-	GthTagsFile  *tags_file;
-	char        **all_tags;
-	char        **tags;
-	int           i;
-	int           j;
-
-	tags_file = gth_main_get_default_tag_file ();
+	g_return_if_fail (GTH_IS_TAGS_ENTRY (self));
 
-	all_tags = g_strsplit (gtk_entry_get_text (GTK_ENTRY (self->priv->entry)), ",", -1);
-	tags = g_new0 (char *, g_strv_length (all_tags) + 1);
-	for (i = 0, j = 0; all_tags[i] != NULL; i++) {
-		all_tags[i] = g_strstrip (all_tags[i]);
-		if (all_tags[i][0] != '\0') {
-			tags[j] = g_strdup (g_strstrip (all_tags[i]));
-			if (update_globals)
-				gth_tags_file_add (tags_file, tags[j]);
-			j++;
-		}
-	}
-	g_strfreev (all_tags);
+	self->priv->expanded = expanded;
+	gtk_widget_set_size_request (self->priv->expanded_list.container, -1, self->priv->expanded ? -1 : EXPANDED_LIST_HEIGHT);
+	gtk_widget_set_visible (self->priv->expand_button, ! expanded);
+	gtk_widget_set_visible (self->priv->expanded_list.container, expanded || gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self->priv->expand_button)));
+}
 
-	if (update_globals) {
-		for (i = 0; self->priv->tags[i] != NULL; i++)
-			gth_tags_file_add (tags_file, self->priv->tags[i]);
-		gth_main_tags_changed ();
-	}
 
-	return tags;
+gboolean
+gth_tags_entry_get_expanded (GthTagsEntry *self)
+{
+	g_return_val_if_fail (GTH_IS_TAGS_ENTRY (self), FALSE);
+	return self->priv->expanded;
 }
 
 
@@ -867,8 +880,8 @@ gth_tags_entry_set_tags (GthTagsEntry  *self,
 
 
 void
-gth_tags_entry_set_text (GthTagsEntry *self,
-			 const char   *text)
+gth_tags_entry_set_tags_from_text (GthTagsEntry *self,
+				   const char   *text)
 {
 	char **tags;
 
@@ -882,3 +895,87 @@ gth_tags_entry_set_text (GthTagsEntry *self,
 
 	g_strfreev (tags);
 }
+
+
+char **
+gth_tags_entry_get_tags (GthTagsEntry *self,
+			 gboolean      update_globals)
+{
+	GthTagsFile  *tags_file;
+	char        **all_tags;
+	char        **tags;
+	int           i;
+	int           j;
+
+	tags_file = gth_main_get_default_tag_file ();
+
+	all_tags = g_strsplit (gtk_entry_get_text (GTK_ENTRY (self->priv->entry)), ",", -1);
+	tags = g_new0 (char *, g_strv_length (all_tags) + 1);
+	for (i = 0, j = 0; all_tags[i] != NULL; i++) {
+		all_tags[i] = g_strstrip (all_tags[i]);
+		if (all_tags[i][0] != '\0') {
+			tags[j] = g_strdup (g_strstrip (all_tags[i]));
+			if (update_globals)
+				gth_tags_file_add (tags_file, tags[j]);
+			j++;
+		}
+	}
+	g_strfreev (all_tags);
+
+	if (update_globals) {
+		for (i = 0; self->priv->tags[i] != NULL; i++)
+			gth_tags_file_add (tags_file, self->priv->tags[i]);
+		gth_main_tags_changed ();
+	}
+
+	return tags;
+}
+
+
+void
+gth_tags_entry_set_tag_list (GthTagsEntry *self,
+			     GList        *checked,
+			     GList        *inconsistent)
+{
+	GString *str;
+	GList   *scan;
+
+	g_hash_table_remove_all (self->priv->inconsistent);
+	for (scan = inconsistent; scan; scan = scan->next)
+		g_hash_table_insert (self->priv->inconsistent, g_strdup (scan->data), GINT_TO_POINTER (1));
+
+	str = g_string_new ("");
+	for (scan = checked; scan; scan = scan->next) {
+		if (scan != checked)
+			g_string_append (str, ", ");
+		g_string_append (str, (char *) scan->data);
+	}
+	gth_tags_entry_set_tags_from_text (self, str->str);
+
+	if (checked == NULL)
+		update_expanded_list_from_entry (self);
+
+	g_string_free (str, TRUE);
+}
+
+
+void
+gth_tags_entry_get_tag_list (GthTagsEntry  *self,
+		             gboolean       update_globals,
+			     GList        **checked,
+			     GList        **inconsistent)
+{
+	if (checked != NULL) {
+		char **tags_v;
+		int    i;
+
+		tags_v = gth_tags_entry_get_tags (self, update_globals);
+		*checked = NULL;
+		for (i = 0; tags_v[i] != NULL; i++)
+			*checked = g_list_prepend (*checked, g_strdup (tags_v[i]));
+		*checked = g_list_reverse (*checked);
+	}
+
+	if (inconsistent != NULL)
+		*inconsistent = g_hash_table_get_keys (self->priv->inconsistent);
+}
diff --git a/gthumb/gth-tags-entry.h b/gthumb/gth-tags-entry.h
index 9f0e538..5773119 100644
--- a/gthumb/gth-tags-entry.h
+++ b/gthumb/gth-tags-entry.h
@@ -50,14 +50,24 @@ struct _GthTagsEntryClass {
 	void (*list_collapsed) (GthTagsEntry *self);
 };
 
-GType        gth_tags_entry_get_type  (void);
-GtkWidget *  gth_tags_entry_new       (void);
-char **      gth_tags_entry_get_tags  (GthTagsEntry  *self,
-				       gboolean       update_globals);
-void         gth_tags_entry_set_tags  (GthTagsEntry  *self,
-				       char         **tags);
-void         gth_tags_entry_set_text  (GthTagsEntry  *self,
-				       const char    *text);
+GType        gth_tags_entry_get_type             (void);
+GtkWidget *  gth_tags_entry_new                  (void);
+void         gth_tags_entry_set_expanded         (GthTagsEntry  *self,
+					          gboolean       expanded);
+gboolean     gth_tags_entry_get_expanded         (GthTagsEntry  *self);
+void         gth_tags_entry_set_tags             (GthTagsEntry  *self,
+				                  char         **tags);
+void         gth_tags_entry_set_tags_from_text   (GthTagsEntry  *self,
+				                  const char    *text);
+char **      gth_tags_entry_get_tags             (GthTagsEntry  *self,
+				                  gboolean       update_globals);
+void         gth_tags_entry_set_tag_list         (GthTagsEntry  *self,
+						  GList         *checked,
+						  GList         *inconsistent);
+void         gth_tags_entry_get_tag_list         (GthTagsEntry  *self,
+						  gboolean       update_globals,
+						  GList        **checked,
+						  GList        **inconsistent);
 
 G_END_DECLS
 



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]