[gthumb/ext: 36/79] started work on scripts support



commit c70aeb59cdeab78707aeb8625a73ba6909119469
Author: Paolo Bacchilega <paobac src gnome org>
Date:   Sun Jul 5 22:43:43 2009 +0200

    started work on scripts support

 configure.ac                                       |    2 +
 extensions/list_tools/Makefile.am                  |   10 +
 extensions/list_tools/actions.c                    |    5 +-
 extensions/list_tools/callbacks.c                  |  102 ++++-
 extensions/list_tools/data/Makefile.am             |    2 +
 extensions/list_tools/data/ui/Makefile.am          |    5 +
 .../list_tools/data/ui/personalize-scripts.ui      |  173 ++++++
 extensions/list_tools/data/ui/script-editor.ui     |  341 ++++++++++++
 extensions/list_tools/dlg-personalize-scripts.c    |  563 ++++++++++++++++++++
 extensions/list_tools/dlg-personalize-scripts.h    |   30 +
 extensions/list_tools/gth-script-editor-dialog.c   |  249 +++++++++
 extensions/list_tools/gth-script-editor-dialog.h   |   61 +++
 extensions/list_tools/gth-script-file.c            |  409 ++++++++++++++
 extensions/list_tools/gth-script-file.h            |   72 +++
 extensions/list_tools/gth-script.c                 |  389 ++++++++++++++
 extensions/list_tools/gth-script.h                 |   67 +++
 gthumb/gth-browser-ui.h                            |    4 +-
 gthumb/gth-filter-file.c                           |    4 +-
 18 files changed, 2478 insertions(+), 10 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index d216eb2..41941c8 100644
--- a/configure.ac
+++ b/configure.ac
@@ -236,6 +236,8 @@ extensions/image_viewer/Makefile
 extensions/image_viewer/data/Makefile
 extensions/image_viewer/data/ui/Makefile
 extensions/list_tools/Makefile
+extensions/list_tools/data/Makefile
+extensions/list_tools/data/ui/Makefile
 extensions/search/Makefile
 extensions/search/data/Makefile
 extensions/search/data/ui/Makefile
diff --git a/extensions/list_tools/Makefile.am b/extensions/list_tools/Makefile.am
index f3520a2..e987e49 100644
--- a/extensions/list_tools/Makefile.am
+++ b/extensions/list_tools/Makefile.am
@@ -1,3 +1,5 @@
+SUBDIRS = data
+
 extensiondir = $(libdir)/gthumb-2.0/extensions
 extension_LTLIBRARIES = liblist_tools.la
 
@@ -6,6 +8,14 @@ liblist_tools_la_SOURCES = 		\
 	actions.h			\
 	callbacks.c			\
 	callbacks.h			\
+	dlg-personalize-scripts.c	\
+	dlg-personalize-scripts.h	\
+	gth-script.c			\
+	gth-script.h			\
+	gth-script-editor-dialog.c	\
+	gth-script-editor-dialog.h	\
+	gth-script-file.c		\
+	gth-script-file.h		\
 	main.c
 
 liblist_tools_la_CFLAGS = $(GTHUMB_CFLAGS) $(DISABLE_DEPRECATED) $(WARNINGS) -I$(top_srcdir) -I$(top_builddir)/gthumb 
diff --git a/extensions/list_tools/actions.c b/extensions/list_tools/actions.c
index 3973897..f967f3a 100644
--- a/extensions/list_tools/actions.c
+++ b/extensions/list_tools/actions.c
@@ -24,11 +24,12 @@
 #include <config.h>
 #include <glib/gi18n.h>
 #include <gthumb.h>
+#include "dlg-personalize-scripts.h"
 
 
 void
 gth_browser_action_list_tools_edit_scripts (GtkAction  *action,
-			       GthBrowser *browser)
+					    GthBrowser *browser)
 {
-	/* FIXME */
+	dlg_personalize_scripts (browser);
 }
diff --git a/extensions/list_tools/callbacks.c b/extensions/list_tools/callbacks.c
index 1593d4e..8b7686d 100644
--- a/extensions/list_tools/callbacks.c
+++ b/extensions/list_tools/callbacks.c
@@ -26,6 +26,7 @@
 #include <glib-object.h>
 #include <gthumb.h>
 #include "actions.h"
+#include "gth-script-file.h"
 
 
 #define BROWSER_DATA_KEY "list-tools-browser-data"
@@ -35,10 +36,9 @@ static const char *fixed_ui_info =
 "<ui>"
 "  <popup name='ListToolsPopup'>"
 "    <placeholder name='Tools'/>"
-"    <separator/>"
 "    <placeholder name='Scripts'/>"
-"    <separator/>"
-"    <menuitem action='ListTools_EditScripts'/>"
+"    <separator name='ScriptsListSeparator'/>"
+"    <menuitem name='EditScripts' action='ListTools_EditScripts'/>"
 "  </popup>"
 "</ui>";
 
@@ -52,18 +52,106 @@ static GtkActionEntry action_entries[] = {
 
 
 typedef struct {
+	GthBrowser     *browser;
 	GtkToolItem    *tool_item;
 	GtkActionGroup *action_group;
+	gulong          scripts_changed_id;
 } BrowserData;
 
 
 static void
 browser_data_free (BrowserData *data)
 {
+	g_signal_handler_disconnect (gth_script_file_get (), data->scripts_changed_id);
 	g_free (data);
 }
 
 
+static void
+activate_script_menu_item (GtkMenuItem *menuitem,
+			   gpointer     user_data)
+{
+	BrowserData *data = user_data;
+	GthScript   *script;
+
+	script = gth_script_file_get_script (gth_script_file_get (), g_object_get_data (G_OBJECT (menuitem), "script_id"));
+	if (script != NULL) {
+		GList   *items;
+		GList   *file_list;
+		GthTask *task;
+
+		items = gth_file_selection_get_selected (GTH_FILE_SELECTION (gth_browser_get_file_list_view (data->browser)));
+		file_list = gth_file_list_get_files (GTH_FILE_LIST (gth_browser_get_file_list (data->browser)), items);
+		task = gth_script_get_task (script, file_list);
+		gth_browser_exec_task (data->browser, task);
+
+		g_object_unref (task);
+		_g_object_list_unref (file_list);
+		_gtk_tree_path_list_free (items);
+	}
+}
+
+
+static void
+update_scripts_menu (BrowserData *data)
+{
+	GtkWidget *separator1;
+	GtkWidget *separator2;
+	GtkWidget *menu;
+	GList     *script_list;
+	GList     *scan;
+	int        pos;
+	gboolean   script_present = FALSE;
+
+	separator1 = gtk_ui_manager_get_widget (gth_browser_get_ui_manager (data->browser), "/ListToolsPopup/Tools");
+	separator2 = gtk_ui_manager_get_widget (gth_browser_get_ui_manager (data->browser), "/ListToolsPopup/Scripts");
+	menu = gtk_widget_get_parent (separator1);
+	_gtk_container_remove_children (GTK_CONTAINER (menu), separator1, separator2);
+
+	script_list = gth_script_file_get_scripts (gth_script_file_get ());
+	pos = _gtk_container_get_pos (GTK_CONTAINER (menu), separator2);
+	for (scan = script_list; scan; scan = scan->next) {
+		GthScript *script = scan->data;
+		GtkWidget *menu_item;
+
+		if (! gth_script_is_visible (script))
+			continue;
+
+		menu_item = gtk_image_menu_item_new_with_label (gth_script_get_display_name (script));
+		gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (menu_item), gtk_image_new_from_stock (GTK_STOCK_EXECUTE, GTK_ICON_SIZE_MENU));
+		gtk_widget_show (menu_item);
+		gtk_menu_shell_insert (GTK_MENU_SHELL (menu), menu_item, pos++);
+
+		g_object_set_data_full (G_OBJECT (menu_item),
+					"script_id",
+					g_strdup (gth_script_get_id (script)),
+					(GDestroyNotify) g_free);
+		g_signal_connect (menu_item,
+				  "activate",
+				  G_CALLBACK (activate_script_menu_item),
+			  	  data);
+
+		script_present = TRUE;
+	}
+
+	separator1 = gtk_ui_manager_get_widget (gth_browser_get_ui_manager (data->browser), "/ListToolsPopup/ScriptsListSeparator");
+	if (script_present)
+		gtk_widget_show (separator1);
+	else
+		gtk_widget_hide (separator1);
+
+	_g_object_list_unref (script_list);
+}
+
+
+static void
+scripts_changed_cb (GthScriptFile *script_file,
+		     BrowserData   *data)
+{
+	update_scripts_menu (data);
+}
+
+
 void
 list_tools__gth_browser_construct_cb (GthBrowser *browser)
 {
@@ -73,7 +161,7 @@ list_tools__gth_browser_construct_cb (GthBrowser *browser)
 	g_return_if_fail (GTH_IS_BROWSER (browser));
 
 	data = g_new0 (BrowserData, 1);
-
+	data->browser = browser;
 	data->action_group = gtk_action_group_new ("List Tools Manager Actions");
 	gtk_action_group_set_translation_domain (data->action_group, NULL);
 	gtk_action_group_add_actions (data->action_group,
@@ -97,6 +185,12 @@ list_tools__gth_browser_construct_cb (GthBrowser *browser)
 	gtk_toolbar_insert (GTK_TOOLBAR (gth_browser_get_browser_toolbar (browser)), data->tool_item, -1);
 
 	g_object_set_data_full (G_OBJECT (browser), BROWSER_DATA_KEY, data, (GDestroyNotify) browser_data_free);
+
+	update_scripts_menu (data);
+	data->scripts_changed_id = g_signal_connect (gth_script_file_get (),
+				                     "changed",
+				                     G_CALLBACK (scripts_changed_cb),
+				                     data);
 }
 
 
diff --git a/extensions/list_tools/data/Makefile.am b/extensions/list_tools/data/Makefile.am
new file mode 100644
index 0000000..4d5385d
--- /dev/null
+++ b/extensions/list_tools/data/Makefile.am
@@ -0,0 +1,2 @@
+SUBDIRS = ui
+-include $(top_srcdir)/git.mk
diff --git a/extensions/list_tools/data/ui/Makefile.am b/extensions/list_tools/data/ui/Makefile.am
new file mode 100644
index 0000000..b220c3f
--- /dev/null
+++ b/extensions/list_tools/data/ui/Makefile.am
@@ -0,0 +1,5 @@
+uidir = $(datadir)/gthumb/ui
+ui_DATA = personalize-scripts.ui script-editor.ui
+EXTRA_DIST = $(ui_DATA)
+
+-include $(top_srcdir)/git.mk
diff --git a/extensions/list_tools/data/ui/personalize-scripts.ui b/extensions/list_tools/data/ui/personalize-scripts.ui
new file mode 100644
index 0000000..3b16fc2
--- /dev/null
+++ b/extensions/list_tools/data/ui/personalize-scripts.ui
@@ -0,0 +1,173 @@
+<?xml version="1.0"?>
+<interface>
+  <requires lib="gtk+" version="2.14"/>
+  <!-- interface-naming-policy project-wide -->
+  <object class="GtkDialog" id="personalize_scripts_dialog">
+    <property name="border_width">5</property>
+    <property name="title" translatable="yes">Scripts</property>
+    <property name="type_hint">normal</property>
+    <property name="has_separator">False</property>
+    <child internal-child="vbox">
+      <object class="GtkVBox" id="dialog-vbox">
+        <property name="visible">True</property>
+        <property name="orientation">vertical</property>
+        <property name="spacing">2</property>
+        <child>
+          <object class="GtkVBox" id="vbox4">
+            <property name="visible">True</property>
+            <property name="border_width">6</property>
+            <property name="orientation">vertical</property>
+            <property name="spacing">12</property>
+            <child>
+              <object class="GtkVBox" id="vbox6">
+                <property name="visible">True</property>
+                <property name="orientation">vertical</property>
+                <property name="spacing">6</property>
+                <child>
+                  <object class="GtkLabel" id="scripts_label">
+                    <property name="visible">True</property>
+                    <property name="xalign">0</property>
+                    <property name="label" translatable="yes">_Scripts:</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkHBox" id="hbox3">
+                    <property name="visible">True</property>
+                    <property name="spacing">12</property>
+                    <child>
+                      <object class="GtkScrolledWindow" id="scripts_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>
+                          <placeholder/>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="position">0</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkVButtonBox" id="vbuttonbox3">
+                        <property name="visible">True</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="edit_button">
+                            <property name="label">gtk-edit</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>
+                        <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">2</property>
+                          </packing>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="position">1</property>
+                      </packing>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="position">0</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="position">1</property>
+          </packing>
+        </child>
+        <child internal-child="action_area">
+          <object class="GtkHButtonBox" id="dialog-action_area">
+            <property name="visible">True</property>
+            <property name="layout_style">end</property>
+            <child>
+              <object class="GtkButton" id="close_button">
+                <property name="label">gtk-close</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="help_button">
+                <property name="label">gtk-help</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>
+                <property name="secondary">True</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="pack_type">end</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+    <action-widgets>
+      <action-widget response="0">close_button</action-widget>
+      <action-widget response="0">help_button</action-widget>
+    </action-widgets>
+  </object>
+</interface>
diff --git a/extensions/list_tools/data/ui/script-editor.ui b/extensions/list_tools/data/ui/script-editor.ui
new file mode 100644
index 0000000..6d47730
--- /dev/null
+++ b/extensions/list_tools/data/ui/script-editor.ui
@@ -0,0 +1,341 @@
+<?xml version="1.0"?>
+<interface>
+  <requires lib="gtk+" version="2.16"/>
+  <!-- interface-naming-policy project-wide -->
+  <object class="GtkVBox" id="script_editor">
+    <property name="visible">True</property>
+    <property name="border_width">6</property>
+    <property name="orientation">vertical</property>
+    <property name="spacing">12</property>
+    <child>
+      <object class="GtkTable" id="table1">
+        <property name="visible">True</property>
+        <property name="n_rows">5</property>
+        <property name="n_columns">2</property>
+        <property name="column_spacing">12</property>
+        <property name="row_spacing">6</property>
+        <child>
+          <object class="GtkLabel" id="label2">
+            <property name="visible">True</property>
+            <property name="xalign">1</property>
+            <property name="label" translatable="yes">_Name:</property>
+            <property name="use_underline">True</property>
+            <property name="mnemonic_widget">name_entry</property>
+          </object>
+          <packing>
+            <property name="x_options">GTK_FILL</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkLabel" id="label4">
+            <property name="visible">True</property>
+            <property name="xalign">1</property>
+            <property name="label" translatable="yes">_Command:</property>
+            <property name="use_underline">True</property>
+            <property name="mnemonic_widget">name_entry</property>
+          </object>
+          <packing>
+            <property name="top_attach">1</property>
+            <property name="bottom_attach">2</property>
+            <property name="x_options">GTK_FILL</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkEntry" id="name_entry">
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="invisible_char">&#x25CF;</property>
+          </object>
+          <packing>
+            <property name="left_attach">1</property>
+            <property name="right_attach">2</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkEntry" id="command_entry">
+            <property name="width_request">400</property>
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="invisible_char">&#x25CF;</property>
+            <property name="secondary_icon_stock">gtk-help</property>
+            <property name="secondary_icon_activatable">True</property>
+            <property name="secondary_icon_sensitive">True</property>
+            <property name="secondary_icon_tooltip_text">Help</property>
+          </object>
+          <packing>
+            <property name="left_attach">1</property>
+            <property name="right_attach">2</property>
+            <property name="top_attach">1</property>
+            <property name="bottom_attach">2</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkCheckButton" id="wait_command_checkbutton">
+            <property name="label" translatable="yes">_Wait for the command to finish</property>
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="receives_default">False</property>
+            <property name="use_underline">True</property>
+            <property name="draw_indicator">True</property>
+          </object>
+          <packing>
+            <property name="right_attach">2</property>
+            <property name="top_attach">4</property>
+            <property name="bottom_attach">5</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkCheckButton" id="for_each_file_checkbutton">
+            <property name="label" translatable="yes">E_xecute the command a file at a time</property>
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="receives_default">False</property>
+            <property name="use_underline">True</property>
+            <property name="draw_indicator">True</property>
+          </object>
+          <packing>
+            <property name="right_attach">2</property>
+            <property name="top_attach">3</property>
+            <property name="bottom_attach">4</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkHBox" id="command_help_box">
+            <child>
+              <object class="GtkTable" id="table2">
+                <property name="visible">True</property>
+                <property name="n_rows">7</property>
+                <property name="n_columns">2</property>
+                <property name="column_spacing">12</property>
+                <property name="row_spacing">6</property>
+                <child>
+                  <object class="GtkLabel" id="label3">
+                    <property name="visible">True</property>
+                    <property name="xalign">0</property>
+                    <property name="label" translatable="yes">%U</property>
+                    <attributes>
+                      <attribute name="size" value="8000"/>
+                    </attributes>
+                  </object>
+                  <packing>
+                    <property name="top_attach">1</property>
+                    <property name="bottom_attach">2</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="label5">
+                    <property name="visible">True</property>
+                    <property name="xalign">0</property>
+                    <property name="label" translatable="yes">%F</property>
+                    <attributes>
+                      <attribute name="size" value="8000"/>
+                    </attributes>
+                  </object>
+                  <packing>
+                    <property name="top_attach">2</property>
+                    <property name="bottom_attach">3</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="label6">
+                    <property name="visible">True</property>
+                    <property name="xalign">0</property>
+                    <property name="label" translatable="yes">%B</property>
+                    <attributes>
+                      <attribute name="size" value="8000"/>
+                    </attributes>
+                  </object>
+                  <packing>
+                    <property name="top_attach">3</property>
+                    <property name="bottom_attach">4</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="label7">
+                    <property name="visible">True</property>
+                    <property name="xalign">0</property>
+                    <property name="label" translatable="yes">%P</property>
+                    <attributes>
+                      <attribute name="size" value="8000"/>
+                    </attributes>
+                  </object>
+                  <packing>
+                    <property name="top_attach">4</property>
+                    <property name="bottom_attach">5</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="label9">
+                    <property name="visible">True</property>
+                    <property name="xalign">0</property>
+                    <property name="label" translatable="yes">The file uri</property>
+                    <attributes>
+                      <attribute name="size" value="8000"/>
+                    </attributes>
+                  </object>
+                  <packing>
+                    <property name="left_attach">1</property>
+                    <property name="right_attach">2</property>
+                    <property name="top_attach">1</property>
+                    <property name="bottom_attach">2</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="label10">
+                    <property name="visible">True</property>
+                    <property name="xalign">0</property>
+                    <property name="label" translatable="yes">The file path</property>
+                    <attributes>
+                      <attribute name="size" value="8000"/>
+                    </attributes>
+                  </object>
+                  <packing>
+                    <property name="left_attach">1</property>
+                    <property name="right_attach">2</property>
+                    <property name="top_attach">2</property>
+                    <property name="bottom_attach">3</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="label11">
+                    <property name="visible">True</property>
+                    <property name="xalign">0</property>
+                    <property name="label" translatable="yes">The file basename</property>
+                    <attributes>
+                      <attribute name="size" value="8000"/>
+                    </attributes>
+                  </object>
+                  <packing>
+                    <property name="left_attach">1</property>
+                    <property name="right_attach">2</property>
+                    <property name="top_attach">3</property>
+                    <property name="bottom_attach">4</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="label12">
+                    <property name="visible">True</property>
+                    <property name="xalign">0</property>
+                    <property name="label" translatable="yes">The parent folder path</property>
+                    <attributes>
+                      <attribute name="size" value="8000"/>
+                    </attributes>
+                  </object>
+                  <packing>
+                    <property name="left_attach">1</property>
+                    <property name="right_attach">2</property>
+                    <property name="top_attach">4</property>
+                    <property name="bottom_attach">5</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="label14">
+                    <property name="visible">True</property>
+                    <property name="xalign">0</property>
+                    <property name="label" translatable="yes">Special code</property>
+                    <attributes>
+                      <attribute name="weight" value="bold"/>
+                      <attribute name="size" value="8000"/>
+                    </attributes>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="label15">
+                    <property name="visible">True</property>
+                    <property name="xalign">0</property>
+                    <property name="label" translatable="yes">Description</property>
+                    <attributes>
+                      <attribute name="weight" value="bold"/>
+                      <attribute name="size" value="8000"/>
+                    </attributes>
+                  </object>
+                  <packing>
+                    <property name="left_attach">1</property>
+                    <property name="right_attach">2</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="label8">
+                    <property name="visible">True</property>
+                    <property name="xalign">0</property>
+                    <property name="label" translatable="yes" comments="Translate only 'message' and 'default_value', keeping the underscore if possible.">%ask{ message }{ default_value }</property>
+                    <attributes>
+                      <attribute name="size" value="8000"/>
+                    </attributes>
+                  </object>
+                  <packing>
+                    <property name="top_attach">5</property>
+                    <property name="bottom_attach">6</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="label13">
+                    <property name="visible">True</property>
+                    <property name="xalign">0</property>
+                    <property name="label" translatable="yes">Ask an input value</property>
+                    <attributes>
+                      <attribute name="size" value="8000"/>
+                    </attributes>
+                  </object>
+                  <packing>
+                    <property name="left_attach">1</property>
+                    <property name="right_attach">2</property>
+                    <property name="top_attach">5</property>
+                    <property name="bottom_attach">6</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="label1">
+                    <property name="visible">True</property>
+                    <property name="xalign">0</property>
+                    <property name="label" translatable="yes" comments="Translate only 'property_name', keeping the underscore if possible.">%prop{ property_name }</property>
+                    <attributes>
+                      <attribute name="size" value="8000"/>
+                    </attributes>
+                  </object>
+                  <packing>
+                    <property name="top_attach">6</property>
+                    <property name="bottom_attach">7</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="label16">
+                    <property name="visible">True</property>
+                    <property name="xalign">0</property>
+                    <property name="label" translatable="yes">A file property</property>
+                    <attributes>
+                      <attribute name="size" value="8000"/>
+                    </attributes>
+                  </object>
+                  <packing>
+                    <property name="left_attach">1</property>
+                    <property name="right_attach">2</property>
+                    <property name="top_attach">6</property>
+                    <property name="bottom_attach">7</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="left_attach">1</property>
+            <property name="right_attach">2</property>
+            <property name="top_attach">2</property>
+            <property name="bottom_attach">3</property>
+          </packing>
+        </child>
+        <child>
+          <placeholder/>
+        </child>
+      </object>
+      <packing>
+        <property name="expand">False</property>
+        <property name="position">0</property>
+      </packing>
+    </child>
+  </object>
+</interface>
diff --git a/extensions/list_tools/dlg-personalize-scripts.c b/extensions/list_tools/dlg-personalize-scripts.c
new file mode 100644
index 0000000..192b5ea
--- /dev/null
+++ b/extensions/list_tools/dlg-personalize-scripts.c
@@ -0,0 +1,563 @@
+/* -*- 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, write to the Free Software
+ *  Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <gtk/gtk.h>
+#include <gthumb.h>
+#include "dlg-personalize-scripts.h"
+#include "gth-script.h"
+#include "gth-script-editor-dialog.h"
+#include "gth-script-file.h"
+
+
+#define GET_WIDGET(name) _gtk_builder_get_widget (data->builder, (name))
+#define ORDER_CHANGED_DELAY 250
+
+
+enum {
+	COLUMN_SCRIPT,
+	COLUMN_NAME,
+	COLUMN_VISIBLE,
+	NUM_COLUMNS
+};
+
+
+typedef struct {
+	GthBrowser   *browser;
+	GtkBuilder   *builder;
+	GtkWidget    *dialog;
+	GtkWidget    *list_view;
+	GtkListStore *list_store;
+	gulong        scripts_changed_id;
+	guint         list_changed_id;
+} DialogData;
+
+
+static void
+destroy_cb (GtkWidget  *widget,
+	    DialogData *data)
+{
+	if (data->list_changed_id != 0)
+		g_source_remove (data->list_changed_id);
+	data->list_changed_id = 0;
+
+	gth_browser_set_dialog (data->browser, "personalize_scripts", NULL);
+	g_signal_handler_disconnect (gth_script_file_get (), data->scripts_changed_id);
+
+	g_object_unref (data->builder);
+	g_free (data);
+}
+
+
+static gboolean
+list_view_row_order_changed_cb (gpointer user_data)
+{
+	DialogData   *data = user_data;
+	GtkTreeModel *model = GTK_TREE_MODEL (data->list_store);
+	GtkTreeIter   iter;
+
+	if (data->list_changed_id != 0)
+		g_source_remove (data->list_changed_id);
+	data->list_changed_id = 0;
+
+	if (gtk_tree_model_get_iter_first (model, &iter)) {
+		GthScriptFile *script_file;
+
+		script_file = gth_script_file_get ();
+		gth_script_file_clear (script_file);
+
+		do {
+			GthScript *script;
+
+			gtk_tree_model_get (model, &iter, COLUMN_SCRIPT, &script, -1);
+			gth_script_file_add (script_file, script);
+
+			g_object_unref (script);
+		}
+		while (gtk_tree_model_iter_next (model, &iter));
+
+		gth_script_file_save (script_file, NULL);
+	}
+
+	return FALSE;
+}
+
+
+static void
+row_deleted_cb (GtkTreeModel *tree_model,
+		GtkTreePath  *path,
+		gpointer      user_data)
+{
+	DialogData *data = user_data;
+
+	if (data->list_changed_id != 0)
+		g_source_remove (data->list_changed_id);
+	data->list_changed_id = g_timeout_add (ORDER_CHANGED_DELAY, list_view_row_order_changed_cb, data);
+}
+
+
+static void
+row_inserted_cb (GtkTreeModel *tree_model,
+		 GtkTreePath  *path,
+		 GtkTreeIter  *iter,
+		 gpointer      user_data)
+{
+	DialogData *data = user_data;
+
+	if (data->list_changed_id != 0)
+		g_source_remove (data->list_changed_id);
+	data->list_changed_id = g_timeout_add (ORDER_CHANGED_DELAY, list_view_row_order_changed_cb, data);
+}
+
+
+static void
+set_script_list (DialogData *data,
+		 GList      *script_list)
+{
+	GList *scan;
+
+	g_signal_handlers_block_by_func (data->list_store, row_inserted_cb, data);
+
+	for (scan = script_list; scan; scan = scan->next) {
+		GthScript   *script = scan->data;
+		GtkTreeIter  iter;
+
+		gtk_list_store_append (data->list_store, &iter);
+		gtk_list_store_set (data->list_store, &iter,
+				    COLUMN_SCRIPT, script,
+				    COLUMN_NAME, gth_script_get_display_name (script),
+				    COLUMN_VISIBLE, gth_script_is_visible (script),
+				   -1);
+	}
+
+	g_signal_handlers_unblock_by_func (data->list_store, row_inserted_cb, data);
+}
+
+
+static void
+update_script_list (DialogData *data)
+{
+	GthScriptFile *script_file;
+	GList         *script_list;
+
+	g_signal_handlers_block_by_func (data->list_store, row_deleted_cb, data);
+	gtk_list_store_clear (data->list_store);
+	g_signal_handlers_unblock_by_func (data->list_store, row_deleted_cb, data);
+
+	script_file = gth_script_file_get ();
+	script_list = gth_script_file_get_scripts (script_file);
+	set_script_list (data, script_list);
+
+	_g_object_list_unref (script_list);
+}
+
+
+static void
+scripts_changed_cb (GthScriptFile *script_file,
+		    DialogData    *data)
+{
+	update_script_list (data);
+}
+
+
+static void
+cell_renderer_toggle_toggled_cb (GtkCellRendererToggle *cell_renderer,
+				 char                  *path,
+                                 gpointer               user_data)
+{
+	DialogData  *data = user_data;
+	GtkTreePath *tpath;
+	GtkTreeIter  iter;
+	gboolean     visible;
+
+	tpath = gtk_tree_path_new_from_string (path);
+	if (tpath == NULL)
+		return;
+
+	if (gtk_tree_model_get_iter (GTK_TREE_MODEL (data->list_store), &iter, tpath)) {
+		GthScript     *script;
+		GthScriptFile *script_file;
+
+		gtk_tree_model_get (GTK_TREE_MODEL (data->list_store), &iter,
+				    COLUMN_SCRIPT, &script,
+				    COLUMN_VISIBLE, &visible,
+				    -1);
+		visible = ! visible;
+		g_object_set (script, "visible", visible, NULL);
+
+		script_file = gth_script_file_get ();
+		g_signal_handlers_block_by_func (script_file, scripts_changed_cb, data);
+		gth_script_file_add (script_file, script);
+		gth_script_file_save (script_file, NULL);
+		g_signal_handlers_unblock_by_func (script_file, scripts_changed_cb, data);
+
+		gtk_list_store_set (data->list_store, &iter,
+				    COLUMN_VISIBLE, visible,
+				    -1);
+
+		g_object_unref (script);
+	}
+
+	gtk_tree_path_free (tpath);
+}
+
+
+static void
+add_columns (GtkTreeView *treeview,
+	     DialogData  *data)
+{
+	GtkCellRenderer   *renderer;
+	GtkTreeViewColumn *column;
+
+	/* the name column. */
+
+	column = gtk_tree_view_column_new ();
+	gtk_tree_view_column_set_title (column, _("Script"));
+
+	renderer = gtk_cell_renderer_text_new ();
+        gtk_tree_view_column_pack_start (column, renderer, TRUE);
+        gtk_tree_view_column_set_attributes (column, renderer,
+                                             "text", COLUMN_NAME,
+                                             NULL);
+
+        gtk_tree_view_column_set_expand (column, TRUE);
+        gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column);
+
+	/* the checkbox column */
+
+	column = gtk_tree_view_column_new ();
+	gtk_tree_view_column_set_title (column, _("Show"));
+
+	renderer = gtk_cell_renderer_toggle_new ();
+	g_signal_connect (renderer,
+			  "toggled",
+			  G_CALLBACK (cell_renderer_toggle_toggled_cb),
+			  data);
+
+	gtk_tree_view_column_pack_start (column, renderer, TRUE);
+	gtk_tree_view_column_set_attributes (column, renderer,
+					     "active", COLUMN_VISIBLE,
+					     NULL);
+
+	gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column);
+}
+
+
+static gboolean
+get_script_iter (DialogData  *data,
+	         GthScript   *script,
+	         GtkTreeIter *iter)
+{
+	GtkTreeModel *model = GTK_TREE_MODEL (data->list_store);
+	gboolean      found = FALSE;
+	const char   *script_id;
+
+	script_id = gth_script_get_id (script);
+	if (! gtk_tree_model_get_iter_first (model, iter))
+		return FALSE;
+
+	do {
+		GthScript *list_script;
+
+		gtk_tree_model_get (model, iter, COLUMN_SCRIPT, &list_script, -1);
+		found = g_strcmp0 (script_id, gth_script_get_id (list_script)) == 0;
+
+		g_object_unref (list_script);
+	}
+	while (! found && gtk_tree_model_iter_next (model, iter));
+
+	return found;
+}
+
+
+static void
+script_editor_dialog__response_cb (GtkDialog *dialog,
+			           int        response,
+			           gpointer   user_data)
+{
+	DialogData    *data = user_data;
+	GthScript     *script;
+	GError        *error = NULL;
+	GthScriptFile *script_file;
+	gboolean       new_script;
+	GtkTreeIter    iter;
+	gboolean       change_list = TRUE;
+
+
+	if (response == GTK_RESPONSE_HELP) {
+		/* FIXME */
+		return;
+	}
+
+	if (response != GTK_RESPONSE_OK) {
+		gtk_widget_destroy (GTK_WIDGET (dialog));
+		return;
+	}
+
+	script = gth_script_editor_dialog_get_script (GTH_SCRIPT_EDITOR_DIALOG (dialog), &error);
+	if (script == NULL) {
+		_gtk_error_dialog_from_gerror_show (GTK_WINDOW (dialog), _("Could not save the script"), &error);
+		return;
+	}
+
+	/* update the script file */
+
+	script_file = gth_script_file_get ();
+	new_script = ! gth_script_file_has_script (script_file, script);
+
+	g_signal_handlers_block_by_func (script_file, scripts_changed_cb, data);
+	gth_script_file_add (script_file, script);
+	gth_script_file_save (script_file, NULL); /* FIXME: handle errors */
+	g_signal_handlers_unblock_by_func (script_file, scripts_changed_cb, data);
+
+	/* update the script list */
+
+	if (new_script) {
+		g_signal_handlers_block_by_func (data->list_store, row_inserted_cb, data);
+		gtk_list_store_append (data->list_store, &iter);
+		g_signal_handlers_unblock_by_func (data->list_store, row_inserted_cb, data);
+	}
+	else
+		change_list = get_script_iter (data, script, &iter);
+
+	if (change_list)
+		gtk_list_store_set (data->list_store, &iter,
+				    COLUMN_SCRIPT, script,
+				    COLUMN_NAME, gth_script_get_display_name (script),
+				    COLUMN_VISIBLE, gth_script_is_visible (script),
+				    -1);
+
+	gtk_widget_destroy (GTK_WIDGET (dialog));
+
+	g_object_unref (script);
+}
+
+
+static void
+new_script_cb (GtkButton  *button,
+	       DialogData *data)
+{
+	GtkWidget *dialog;
+
+	dialog = gth_script_editor_dialog_new (_("New Script"), GTK_WINDOW (data->dialog));
+	g_signal_connect (dialog, "response",
+			  G_CALLBACK (script_editor_dialog__response_cb),
+			  data);
+	gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
+	gtk_window_present (GTK_WINDOW (dialog));
+}
+
+
+static void
+edit_script_cb (GtkButton  *button,
+	        DialogData *data)
+{
+	GtkTreeSelection *selection;
+	GtkTreeModel     *model = GTK_TREE_MODEL (data->list_store);
+	GtkTreeIter       iter;
+	GthScript        *script;
+	GtkWidget        *dialog;
+
+	selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (data->list_view));
+	if (! gtk_tree_selection_get_selected (selection, &model, &iter))
+		return;
+
+	gtk_tree_model_get (model, &iter, COLUMN_SCRIPT, &script, -1);
+	if (script == NULL)
+		return;
+
+	dialog = gth_script_editor_dialog_new (_("Edit Script"), GTK_WINDOW (data->dialog));
+	gth_script_editor_dialog_set_script (GTH_SCRIPT_EDITOR_DIALOG (dialog), script);
+	g_signal_connect (dialog,
+			  "response",
+			  G_CALLBACK (script_editor_dialog__response_cb),
+			  data);
+	gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
+	gtk_window_present (GTK_WINDOW (dialog));
+
+	g_object_unref (script);
+}
+
+
+static void
+delete_script_cb (GtkButton  *button,
+	          DialogData *data)
+{
+	GtkTreeSelection *selection;
+	GtkTreeModel     *model = GTK_TREE_MODEL (data->list_store);
+	GtkTreeIter       iter;
+	GthScript        *script;
+	GthScriptFile    *script_file;
+
+	selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (data->list_view));
+	if (! gtk_tree_selection_get_selected (selection, &model, &iter))
+		return;
+
+	gtk_tree_model_get (model, &iter, COLUMN_SCRIPT, &script, -1);
+	if (script == NULL)
+		return;
+
+	/* update the script file */
+
+	script_file = gth_script_file_get ();
+	g_signal_handlers_block_by_func (script_file, scripts_changed_cb, data);
+	gth_script_file_remove (script_file, script);
+	gth_script_file_save (script_file, NULL);
+	g_signal_handlers_unblock_by_func (script_file, scripts_changed_cb, data);
+
+	/* update the script list */
+
+	g_signal_handlers_block_by_func (data->list_store, row_deleted_cb, data);
+	gtk_list_store_remove (data->list_store, &iter);
+	g_signal_handlers_unblock_by_func (data->list_store, row_deleted_cb, data);
+
+	g_object_unref (script);
+}
+
+
+static void
+update_sensitivity (DialogData *data)
+{
+	GtkTreeModel     *model;
+	GtkTreeSelection *selection;
+	GtkTreeIter       iter;
+	gboolean          script_selected;
+
+	model = GTK_TREE_MODEL (data->list_store);
+	selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (data->list_view));
+	script_selected =  gtk_tree_selection_get_selected (selection, &model, &iter);
+
+	gtk_widget_set_sensitive (GET_WIDGET ("edit_button"), script_selected);
+	gtk_widget_set_sensitive (GET_WIDGET ("delete_button"), script_selected);
+}
+
+
+static void
+list_view_selection_changed_cb (GtkTreeSelection *treeselection,
+				gpointer          user_data)
+{
+	update_sensitivity ((DialogData *) user_data);
+}
+
+
+static void
+list_view_row_activated_cb (GtkTreeView       *tree_view,
+                            GtkTreePath       *path,
+                            GtkTreeViewColumn *column,
+                            gpointer           user_data)
+{
+	edit_script_cb (NULL, user_data);
+}
+
+
+void
+dlg_personalize_scripts (GthBrowser *browser)
+{
+	DialogData *data;
+
+	if (gth_browser_get_dialog (browser, "personalize_scripts") != NULL) {
+		gtk_window_present (GTK_WINDOW (gth_browser_get_dialog (browser, "personalize_scripts")));
+		return;
+	}
+
+	data = g_new0 (DialogData, 1);
+	data->browser = browser;
+	data->builder = _gtk_builder_new_from_file ("personalize-scripts.ui", "list_tools");
+
+	/* Get the widgets. */
+
+	data->dialog = _gtk_builder_get_widget (data->builder, "personalize_scripts_dialog");
+	gth_browser_set_dialog (browser, "personalize_scripts", data->dialog);
+	g_object_set_data (G_OBJECT (data->dialog), "dialog_data", data);
+
+	/**/
+
+	data->list_store = gtk_list_store_new (NUM_COLUMNS,
+					       G_TYPE_OBJECT,
+					       G_TYPE_STRING,
+					       G_TYPE_BOOLEAN);
+	data->list_view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (data->list_store));
+	g_object_unref (data->list_store);
+        gtk_tree_view_set_reorderable (GTK_TREE_VIEW (data->list_view), TRUE);
+        gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (data->list_view), TRUE);
+
+	add_columns (GTK_TREE_VIEW (data->list_view), data);
+
+	gtk_widget_show (data->list_view);
+	gtk_container_add (GTK_CONTAINER (GET_WIDGET ("scripts_scrolledwindow")), data->list_view);
+
+	gtk_label_set_mnemonic_widget (GTK_LABEL (GET_WIDGET ("scripts_label")), data->list_view);
+	gtk_label_set_use_underline (GTK_LABEL (GET_WIDGET ("scripts_label")), TRUE);
+
+	update_script_list (data);
+	update_sensitivity (data);
+
+	/* Set the signals handlers. */
+
+	g_signal_connect (G_OBJECT (data->dialog),
+			  "destroy",
+			  G_CALLBACK (destroy_cb),
+			  data);
+	g_signal_connect_swapped (G_OBJECT (GET_WIDGET ("close_button")),
+				  "clicked",
+				  G_CALLBACK (gtk_widget_destroy),
+				  G_OBJECT (data->dialog));
+	g_signal_connect (G_OBJECT (GET_WIDGET ("new_button")),
+			  "clicked",
+			  G_CALLBACK (new_script_cb),
+			  data);
+	g_signal_connect (G_OBJECT (GET_WIDGET ("edit_button")),
+			  "clicked",
+			  G_CALLBACK (edit_script_cb),
+			  data);
+	g_signal_connect (G_OBJECT (GET_WIDGET ("delete_button")),
+			  "clicked",
+			  G_CALLBACK (delete_script_cb),
+			  data);
+	g_signal_connect (gtk_tree_view_get_selection (GTK_TREE_VIEW (data->list_view)),
+			  "changed",
+			  G_CALLBACK (list_view_selection_changed_cb),
+			  data);
+	g_signal_connect (GTK_TREE_VIEW (data->list_view),
+			  "row-activated",
+			  G_CALLBACK (list_view_row_activated_cb),
+			  data);
+	g_signal_connect (data->list_store,
+			  "row-deleted",
+			  G_CALLBACK (row_deleted_cb),
+			  data);
+	g_signal_connect (data->list_store,
+			  "row-inserted",
+			  G_CALLBACK (row_inserted_cb),
+			  data);
+
+	data->scripts_changed_id = g_signal_connect (gth_script_file_get (),
+				                     "changed",
+				                     G_CALLBACK (scripts_changed_cb),
+				                     data);
+
+	/* run dialog. */
+
+	gtk_window_set_transient_for (GTK_WINDOW (data->dialog), GTK_WINDOW (browser));
+	gtk_window_set_modal (GTK_WINDOW (data->dialog), FALSE);
+	gtk_widget_show (data->dialog);
+}
diff --git a/extensions/list_tools/dlg-personalize-scripts.h b/extensions/list_tools/dlg-personalize-scripts.h
new file mode 100644
index 0000000..e2fa889
--- /dev/null
+++ b/extensions/list_tools/dlg-personalize-scripts.h
@@ -0,0 +1,30 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2009 The 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, write to the Free Software
+ *  Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef DLG_PERSONALIZE_SCRIPTS_H
+#define DLG_PERSONALIZE_SCRIPTS_H
+
+#include <gthumb.h>
+
+void dlg_personalize_scripts (GthBrowser *browser);
+
+#endif /* DLG_PERSONALIZE_SCRIPTS_H */
diff --git a/extensions/list_tools/gth-script-editor-dialog.c b/extensions/list_tools/gth-script-editor-dialog.c
new file mode 100644
index 0000000..ca80738
--- /dev/null
+++ b/extensions/list_tools/gth-script-editor-dialog.c
@@ -0,0 +1,249 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2009 The 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, write to the Free Software
+ *  Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <gtk/gtk.h>
+#include <gthumb.h>
+#include "gth-script-editor-dialog.h"
+
+
+#define GET_WIDGET(name) _gtk_builder_get_widget (self->priv->builder, (name))
+
+
+static gpointer parent_class = NULL;
+
+
+struct _GthScriptEditorDialogPrivate {
+	GtkBuilder *builder;
+  	char       *script_id;
+  	gboolean    script_visible;
+  	gboolean    wait_command;
+  	gboolean    for_each_file;
+  	gboolean    help_visible;
+};
+
+
+static void
+gth_script_editor_dialog_finalize (GObject *object)
+{
+	GthScriptEditorDialog *dialog;
+
+	dialog = GTH_SCRIPT_EDITOR_DIALOG (object);
+
+	if (dialog->priv != NULL) {
+		g_object_unref (dialog->priv->builder);
+		g_free (dialog->priv->script_id);
+		g_free (dialog->priv);
+		dialog->priv = NULL;
+	}
+
+	G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+
+static void
+gth_script_editor_dialog_class_init (GthScriptEditorDialogClass *class)
+{
+	GObjectClass *object_class;
+
+	parent_class = g_type_class_peek_parent (class);
+	object_class = (GObjectClass*) class;
+
+	object_class->finalize = gth_script_editor_dialog_finalize;
+}
+
+
+static void
+gth_script_editor_dialog_init (GthScriptEditorDialog *dialog)
+{
+	dialog->priv = g_new0 (GthScriptEditorDialogPrivate, 1);
+	dialog->priv->help_visible = FALSE;
+}
+
+
+GType
+gth_script_editor_dialog_get_type (void)
+{
+        static GType type = 0;
+
+        if (! type) {
+                GTypeInfo type_info = {
+			sizeof (GthScriptEditorDialogClass),
+			NULL,
+			NULL,
+			(GClassInitFunc) gth_script_editor_dialog_class_init,
+			NULL,
+			NULL,
+			sizeof (GthScriptEditorDialog),
+			0,
+			(GInstanceInitFunc) gth_script_editor_dialog_init
+		};
+
+		type = g_type_register_static (GTK_TYPE_DIALOG,
+					       "GthScriptEditorDialog",
+					       &type_info,
+					       0);
+	}
+
+        return type;
+}
+
+
+static void
+update_sensitivity (GthScriptEditorDialog *self)
+{
+	/* FIXME */
+}
+
+
+static void
+command_entry_icon_press_cb (GtkEntry             *entry,
+                             GtkEntryIconPosition  icon_pos,
+                             GdkEvent             *event,
+                             gpointer              user_data)
+{
+	GthScriptEditorDialog *self = user_data;
+
+	self->priv->help_visible = ! self->priv->help_visible;
+
+	if (self->priv->help_visible)
+		gtk_widget_show (GET_WIDGET("command_help_box"));
+	else
+		gtk_widget_hide (GET_WIDGET("command_help_box"));
+}
+
+
+static void
+gth_script_editor_dialog_construct (GthScriptEditorDialog *self,
+				    const char            *title,
+			            GtkWindow             *parent)
+{
+	GtkWidget *content;
+
+	if (title != NULL)
+    		gtk_window_set_title (GTK_WINDOW (self), title);
+  	if (parent != NULL)
+    		gtk_window_set_transient_for (GTK_WINDOW (self), parent);
+    	gtk_window_set_resizable (GTK_WINDOW (self), FALSE);
+	gtk_dialog_set_has_separator (GTK_DIALOG (self), FALSE);
+	gtk_box_set_spacing (GTK_BOX (GTK_DIALOG (self)->vbox), 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_OK);
+	gtk_dialog_add_button (GTK_DIALOG (self), GTK_STOCK_HELP, GTK_RESPONSE_HELP);
+
+    	self->priv->builder = _gtk_builder_new_from_file ("script-editor.ui", "list_tools");
+
+    	content = _gtk_builder_get_widget (self->priv->builder, "script_editor");
+    	gtk_container_set_border_width (GTK_CONTAINER (content), 5);
+  	gtk_box_pack_start (GTK_BOX (GTK_DIALOG (self)->vbox), content, TRUE, TRUE, 0);
+
+  	g_signal_connect (GET_WIDGET ("command_entry"),
+  			  "icon-press",
+  			  G_CALLBACK (command_entry_icon_press_cb),
+  			  self);
+
+  	/**/
+
+  	gth_script_editor_dialog_set_script (self, NULL);
+}
+
+
+GtkWidget *
+gth_script_editor_dialog_new (const char *title,
+			      GtkWindow  *parent)
+{
+	GthScriptEditorDialog *self;
+
+	self = g_object_new (GTH_TYPE_SCRIPT_EDITOR_DIALOG, NULL);
+	gth_script_editor_dialog_construct (self, title, parent);
+
+	return (GtkWidget *) self;
+}
+
+
+static void
+_gth_script_editor_dialog_set_new_script (GthScriptEditorDialog *self)
+{
+	gtk_entry_set_text (GTK_ENTRY (GET_WIDGET ("name_entry")), "");
+	gtk_entry_set_text (GTK_ENTRY (GET_WIDGET ("command_entry")), "");
+	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (GET_WIDGET ("for_each_file_checkbutton")), FALSE);
+	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (GET_WIDGET ("wait_command_checkbutton")), FALSE);
+}
+
+
+void
+gth_script_editor_dialog_set_script (GthScriptEditorDialog *self,
+				     GthScript             *script)
+{
+	g_free (self->priv->script_id);
+	self->priv->script_id = NULL;
+	self->priv->script_visible = TRUE;
+
+	_gth_script_editor_dialog_set_new_script (self);
+
+	if (script != NULL) {
+		self->priv->script_id = g_strdup (gth_script_get_id (script));
+		self->priv->script_visible = gth_script_is_visible (script);
+
+		gtk_entry_set_text (GTK_ENTRY (GET_WIDGET ("name_entry")), gth_script_get_display_name (script));
+		gtk_entry_set_text (GTK_ENTRY (GET_WIDGET ("command_entry")), gth_script_get_command (script));
+		gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (GET_WIDGET ("for_each_file_checkbutton")), gth_script_for_each_file (script));
+		gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (GET_WIDGET ("wait_command_checkbutton")), gth_script_wait_command (script));
+	}
+
+	update_sensitivity (self);
+}
+
+
+GthScript *
+gth_script_editor_dialog_get_script (GthScriptEditorDialog  *self,
+				     GError                **error)
+{
+	GthScript *script;
+
+	script = gth_script_new ();
+	if (self->priv->script_id != NULL)
+		g_object_set (script, "id", self->priv->script_id, NULL);
+	g_object_set (script,
+		      "display-name", gtk_entry_get_text (GTK_ENTRY (GET_WIDGET ("name_entry"))),
+		      "command", gtk_entry_get_text (GTK_ENTRY (GET_WIDGET ("command_entry"))),
+		      "visible", self->priv->script_visible,
+		      "for-each-file", gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (GET_WIDGET ("for_each_file_checkbutton"))),
+		      "wait-command", gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (GET_WIDGET ("wait_command_checkbutton"))),
+		      NULL);
+
+	if (g_strcmp0 (gth_script_get_display_name (script), "") == 0) {
+		*error = g_error_new (GTHUMB_ERROR, 0, _("No name specified"));
+		g_object_unref (script);
+		return NULL;
+	}
+
+	if (g_strcmp0 (gth_script_get_command (script), "") == 0) {
+		*error = g_error_new (GTHUMB_ERROR, 0, _("No command specified"));
+		g_object_unref (script);
+		return NULL;
+	}
+
+	return script;
+}
diff --git a/extensions/list_tools/gth-script-editor-dialog.h b/extensions/list_tools/gth-script-editor-dialog.h
new file mode 100644
index 0000000..86929d3
--- /dev/null
+++ b/extensions/list_tools/gth-script-editor-dialog.h
@@ -0,0 +1,61 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2009 The 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, write to the Free Software
+ *  Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_SCRIPT_EDITOR_DIALOG_H
+#define GTH_SCRIPT_EDITOR_DIALOG_H
+
+#include <gtk/gtk.h>
+#include "gth-script.h"
+
+G_BEGIN_DECLS
+
+#define GTH_TYPE_SCRIPT_EDITOR_DIALOG            (gth_script_editor_dialog_get_type ())
+#define GTH_SCRIPT_EDITOR_DIALOG(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTH_TYPE_SCRIPT_EDITOR_DIALOG, GthScriptEditorDialog))
+#define GTH_SCRIPT_EDITOR_DIALOG_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GTH_TYPE_SCRIPT_EDITOR_DIALOG, GthScriptEditorDialogClass))
+#define GTH_IS_SCRIPT_EDITOR_DIALOG(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTH_TYPE_SCRIPT_EDITOR_DIALOG))
+#define GTH_IS_SCRIPT_EDITOR_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTH_TYPE_SCRIPT_EDITOR_DIALOG))
+#define GTH_SCRIPT_EDITOR_DIALOG_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), GTH_TYPE_SCRIPT_EDITOR_DIALOG, GthScriptEditorDialogClass))
+
+typedef struct _GthScriptEditorDialog        GthScriptEditorDialog;
+typedef struct _GthScriptEditorDialogClass   GthScriptEditorDialogClass;
+typedef struct _GthScriptEditorDialogPrivate GthScriptEditorDialogPrivate;
+
+struct _GthScriptEditorDialog {
+	GtkDialog parent_instance;
+	GthScriptEditorDialogPrivate *priv;
+};
+
+struct _GthScriptEditorDialogClass {
+	GtkDialogClass parent_class;
+};
+
+GType       gth_script_editor_dialog_get_type   (void);
+GtkWidget * gth_script_editor_dialog_new        (const char             *title,
+						 GtkWindow              *parent);
+void        gth_script_editor_dialog_set_script (GthScriptEditorDialog  *self,
+						 GthScript              *script);
+GthScript * gth_script_editor_dialog_get_script (GthScriptEditorDialog  *self,
+						 GError                **error);
+
+G_END_DECLS
+
+#endif /* GTH_SCRIPT_EDITOR_DIALOG_H */
diff --git a/extensions/list_tools/gth-script-file.c b/extensions/list_tools/gth-script-file.c
new file mode 100644
index 0000000..c4f7534
--- /dev/null
+++ b/extensions/list_tools/gth-script-file.c
@@ -0,0 +1,409 @@
+/* -*- 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, write to the Free Software
+ *  Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <string.h>
+#include <gthumb.h>
+#include "gth-script.h"
+#include "gth-script-file.h"
+
+
+#define SCRIPT_FORMAT "1.0"
+
+
+enum {
+        CHANGED,
+        LAST_SIGNAL
+};
+
+
+struct _GthScriptFilePrivate
+{
+	gboolean  loaded;
+	GList    *items;
+};
+
+
+static gpointer parent_class = NULL;
+static guint gth_script_file_signals[LAST_SIGNAL] = { 0 };
+
+static GType gth_script_file_get_type (void);
+
+
+static void
+gth_script_file_finalize (GObject *object)
+{
+	GthScriptFile *self;
+
+	self = GTH_SCRIPT_FILE (object);
+
+	_g_object_list_unref (self->priv->items);
+
+	G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+
+static void
+gth_script_file_class_init (GthScriptFileClass *klass)
+{
+	GObjectClass *object_class;
+
+	parent_class = g_type_class_peek_parent (klass);
+	g_type_class_add_private (klass, sizeof (GthScriptFilePrivate));
+
+	object_class = (GObjectClass*) klass;
+	object_class->finalize = gth_script_file_finalize;
+
+	gth_script_file_signals[CHANGED] =
+		g_signal_new ("changed",
+			      G_TYPE_FROM_CLASS (klass),
+			      G_SIGNAL_RUN_LAST,
+			      G_STRUCT_OFFSET (GthScriptFileClass, changed),
+			      NULL, NULL,
+			      g_cclosure_marshal_VOID__VOID,
+			      G_TYPE_NONE,
+			      0);
+}
+
+
+static void
+gth_script_file_init (GthScriptFile *self)
+{
+	self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GTH_TYPE_SCRIPT_FILE, GthScriptFilePrivate);
+	self->priv->items = NULL;
+	self->priv->loaded = FALSE;
+}
+
+
+static GType
+gth_script_file_get_type (void)
+{
+        static GType type = 0;
+
+        if (! type) {
+                GTypeInfo type_info = {
+			sizeof (GthScriptFileClass),
+			NULL,
+			NULL,
+			(GClassInitFunc) gth_script_file_class_init,
+			NULL,
+			NULL,
+			sizeof (GthScriptFile),
+			0,
+			(GInstanceInitFunc) gth_script_file_init
+		};
+
+		type = g_type_register_static (G_TYPE_OBJECT,
+					       "GthScriptFile",
+					       &type_info,
+					       0);
+	}
+
+        return type;
+}
+
+
+GthScriptFile *
+gth_script_file_get (void)
+{
+	static GthScriptFile *script_file = NULL;
+
+	if (script_file == NULL)
+		script_file = g_object_new (GTH_TYPE_SCRIPT_FILE, NULL);
+
+  	return script_file;
+}
+
+
+static gboolean
+gth_script_file_load_from_data (GthScriptFile  *self,
+                                const char     *data,
+                                gsize           length,
+                                GError        **error)
+{
+	DomDocument *doc;
+	gboolean     success;
+
+	doc = dom_document_new ();
+	success = dom_document_load (doc, data, length, error);
+	if (success) {
+		DomElement *scripts_node;
+		DomElement *child;
+		GList      *new_items = NULL;
+
+		scripts_node = DOM_ELEMENT (doc)->first_child;
+		if ((scripts_node != NULL) && (g_strcmp0 (scripts_node->tag_name, "scripts") == 0)) {
+			for (child = scripts_node->first_child;
+			     child != NULL;
+			     child = child->next_sibling)
+			{
+				GthScript *script = NULL;
+
+				if (strcmp (child->tag_name, "script") == 0) {
+					script = gth_script_new ();
+					dom_domizable_load_from_element (DOM_DOMIZABLE (script), child);
+				}
+
+				if (script == NULL)
+					continue;
+
+				new_items = g_list_prepend (new_items, script);
+			}
+
+			new_items = g_list_reverse (new_items);
+			self->priv->items = g_list_concat (self->priv->items, new_items);
+		}
+	}
+	g_object_unref (doc);
+
+	return success;
+}
+
+
+static gboolean
+gth_script_file_load_from_file (GthScriptFile  *self,
+                                const char     *filename,
+                                GError        **error)
+{
+	char     *buffer;
+	gsize     len;
+	GError   *read_error;
+	gboolean  retval;
+
+	g_return_val_if_fail (self != NULL, FALSE);
+	g_return_val_if_fail (filename != NULL, FALSE);
+
+	read_error = NULL;
+	g_file_get_contents (filename, &buffer, &len, &read_error);
+	if (read_error != NULL) {
+		g_propagate_error (error, read_error);
+		return FALSE;
+	}
+
+	read_error = NULL;
+	retval = gth_script_file_load_from_data (self,
+						 buffer,
+                                           	 len,
+                                           	 &read_error);
+  	if (read_error != NULL) {
+		g_propagate_error (error, read_error);
+		g_free (buffer);
+		return FALSE;
+	}
+
+  	g_free (buffer);
+
+	return retval;
+}
+
+
+static void
+_gth_script_file_load_if_needed (GthScriptFile *self)
+{
+	char *default_script_file;
+
+	if (self->priv->loaded)
+		return;
+
+	default_script_file = gth_user_dir_get_file (GTH_DIR_CONFIG, GTHUMB_DIR, "scripts.xml", NULL);
+	gth_script_file_load_from_file (self, default_script_file, NULL);
+	self->priv->loaded = TRUE;
+
+	g_free (default_script_file);
+}
+
+
+static char *
+gth_script_file_to_data (GthScriptFile  *self,
+			 gsize          *len,
+			 GError        **data_error)
+{
+	DomDocument *doc;
+	DomElement  *root;
+	char        *data;
+	GList       *scan;
+
+	doc = dom_document_new ();
+	root = dom_document_create_element (doc, "scripts",
+					    "version", SCRIPT_FORMAT,
+					    NULL);
+	dom_element_append_child (DOM_ELEMENT (doc), root);
+	for (scan = self->priv->items; scan; scan = scan->next)
+		dom_element_append_child (root, dom_domizable_create_element (DOM_DOMIZABLE (scan->data), doc));
+	data = dom_document_dump (doc, len);
+
+	g_object_unref (doc);
+
+	return data;
+}
+
+
+static gboolean
+gth_script_file_to_file (GthScriptFile  *self,
+                         const char     *filename,
+                         GError        **error)
+{
+	char     *data;
+	GError   *data_error, *write_error;
+	gsize     len;
+	gboolean  retval;
+
+	g_return_val_if_fail (self != NULL, FALSE);
+	g_return_val_if_fail (filename != NULL, FALSE);
+
+	data_error = NULL;
+	data = gth_script_file_to_data (self, &len, &data_error);
+	if (data_error) {
+		g_propagate_error (error, data_error);
+		return FALSE;
+	}
+
+	write_error = NULL;
+	g_file_set_contents (filename, data, len, &write_error);
+	if (write_error) {
+		g_propagate_error (error, write_error);
+		retval = FALSE;
+	}
+	else
+		retval = TRUE;
+
+	g_free (data);
+
+	return retval;
+}
+
+
+GthScript *
+gth_script_file_get_script (GthScriptFile *self,
+			    const char    *id)
+{
+	GList *scan;
+
+	for (scan = self->priv->items; scan; scan = scan->next) {
+		GthScript *script = scan->data;
+
+		if (g_strcmp0 (gth_script_get_id (script), id) == 0)
+			return script;
+	}
+
+	return NULL;
+}
+
+
+gboolean
+gth_script_file_save (GthScriptFile  *self,
+		      GError        **error)
+{
+	char     *default_script_file;
+	gboolean  result;
+
+	_gth_script_file_load_if_needed (self);
+
+	default_script_file = gth_user_dir_get_file (GTH_DIR_CONFIG, GTHUMB_DIR, "scripts.xml", NULL);
+	result = gth_script_file_to_file (self, default_script_file, error);
+	if (result)
+		g_signal_emit (G_OBJECT (self), gth_script_file_signals[CHANGED], 0);
+
+	g_free (default_script_file);
+
+	return result;
+}
+
+
+GList *
+gth_script_file_get_scripts (GthScriptFile *self)
+{
+	GList *list;
+	GList *scan;
+
+	_gth_script_file_load_if_needed (self);
+
+	list = NULL;
+	for (scan = self->priv->items; scan; scan = scan->next)
+		list = g_list_prepend (list, gth_duplicable_duplicate (GTH_DUPLICABLE (scan->data)));
+
+	return g_list_reverse (list);
+}
+
+
+static int
+find_by_id (gconstpointer a,
+            gconstpointer b)
+{
+	GthScript *script = (GthScript *) a;
+	GthScript *script_to_find = (GthScript *) b;
+
+	return g_strcmp0 (gth_script_get_id (script), gth_script_get_id (script_to_find));
+}
+
+
+gboolean
+gth_script_file_has_script (GthScriptFile *self,
+			    GthScript     *script)
+{
+	return g_list_find_custom (self->priv->items, script, find_by_id) != NULL;
+}
+
+
+void
+gth_script_file_add (GthScriptFile *self,
+		     GthScript     *script)
+{
+	GList *link;
+
+	_gth_script_file_load_if_needed (self);
+
+	g_object_ref (script);
+
+	link = g_list_find_custom (self->priv->items, script, find_by_id);
+	if (link != NULL) {
+		g_object_unref (link->data);
+		link->data = script;
+	}
+	else
+		self->priv->items = g_list_append (self->priv->items, script);
+}
+
+
+void
+gth_script_file_remove (GthScriptFile *self,
+			GthScript     *script)
+{
+	GList *link;
+
+	_gth_script_file_load_if_needed (self);
+
+	link = g_list_find_custom (self->priv->items, script, find_by_id);
+	if (link == NULL)
+		return;
+	self->priv->items = g_list_remove_link (self->priv->items, link);
+
+	_g_object_list_unref (link);
+}
+
+
+void
+gth_script_file_clear (GthScriptFile *self)
+{
+	_g_object_list_unref (self->priv->items);
+	self->priv->items = NULL;
+}
diff --git a/extensions/list_tools/gth-script-file.h b/extensions/list_tools/gth-script-file.h
new file mode 100644
index 0000000..f4790c7
--- /dev/null
+++ b/extensions/list_tools/gth-script-file.h
@@ -0,0 +1,72 @@
+/* -*- 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, write to the Free Software
+ *  Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_SCRIPT_FILE_H
+#define GTH_SCRIPT_FILE_H
+
+#include <glib.h>
+#include "gth-script.h"
+
+G_BEGIN_DECLS
+
+#define GTH_TYPE_SCRIPT_FILE         (gth_script_file_get_type ())
+#define GTH_SCRIPT_FILE(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), GTH_TYPE_SCRIPT_FILE, GthScriptFile))
+#define GTH_SCRIPT_FILE_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST ((k), GTH_TYPE_SCRIPT_FILE, GthScriptFileClass))
+#define GTH_IS_SCRIPT_FILE(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), GTH_TYPE_SCRIPT_FILE))
+#define GTH_IS_SCRIPT_FILE_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), GTH_TYPE_SCRIPT_FILE))
+#define GTH_SCRIPT_FILE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS((o), GTH_TYPE_SCRIPT_FILE, GthScriptFileClass))
+
+typedef struct _GthScriptFile         GthScriptFile;
+typedef struct _GthScriptFilePrivate  GthScriptFilePrivate;
+typedef struct _GthScriptFileClass    GthScriptFileClass;
+
+
+struct _GthScriptFile
+{
+	GObject __parent;
+	GthScriptFilePrivate *priv;
+};
+
+struct _GthScriptFileClass
+{
+	GObjectClass __parent_class;
+
+	void  (*changed) (GthScriptFile *self);
+};
+
+GthScriptFile *  gth_script_file_get                  (void);
+GthScript *      gth_script_file_get_script           (GthScriptFile  *self,
+						       const char     *id);
+gboolean         gth_script_file_save                 (GthScriptFile  *self,
+			 			       GError        **error);
+GList *          gth_script_file_get_scripts          (GthScriptFile  *self);
+gboolean         gth_script_file_has_script           (GthScriptFile  *self,
+						       GthScript      *script);
+void             gth_script_file_add                  (GthScriptFile  *self,
+						       GthScript      *script);
+void             gth_script_file_remove               (GthScriptFile  *self,
+						       GthScript      *script);
+void             gth_script_file_clear                (GthScriptFile  *self);
+
+G_END_DECLS
+
+#endif /* GTH_SCRIPT_FILE_H */
diff --git a/extensions/list_tools/gth-script.c b/extensions/list_tools/gth-script.c
new file mode 100644
index 0000000..5585727
--- /dev/null
+++ b/extensions/list_tools/gth-script.c
@@ -0,0 +1,389 @@
+/* -*- 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, write to the Free Software
+ *  Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <gthumb.h>
+#include "gth-script.h"
+
+
+enum {
+        PROP_0,
+        PROP_ID,
+        PROP_DISPLAY_NAME,
+        PROP_COMMAND,
+        PROP_VISIBLE,
+        PROP_FOR_EACH_FILE,
+        PROP_WAIT_COMMAND
+};
+
+
+struct _GthScriptPrivate {
+	char     *id;
+	char     *display_name;
+	char     *command;
+	gboolean  visible;
+	gboolean  for_each_file;
+	gboolean  wait_command;
+};
+
+
+static gpointer *parent_class = NULL;
+
+
+static DomElement*
+gth_script_real_create_element (DomDomizable *base,
+				DomDocument  *doc)
+{
+	GthScript  *self;
+	DomElement *element;
+
+	g_return_val_if_fail (DOM_IS_DOCUMENT (doc), NULL);
+
+	self = GTH_SCRIPT (base);
+	element = dom_document_create_element (doc, "script",
+					       "id", self->priv->id,
+					       "display-name", self->priv->display_name,
+					       "command", self->priv->command,
+					       "for-each-file", self->priv->for_each_file ? "true" : "false",
+					       "wait-command", self->priv->wait_command ? "true" : "false",
+					       NULL);
+	if (! self->priv->visible)
+		dom_element_set_attribute (element, "display", "none");
+
+	return element;
+}
+
+
+static void
+gth_script_real_load_from_element (DomDomizable *base,
+				   DomElement   *element)
+{
+	GthScript *self;
+
+	g_return_if_fail (DOM_IS_ELEMENT (element));
+
+	self = GTH_SCRIPT (base);
+	g_object_set (self,
+		      "id", dom_element_get_attribute (element, "id"),
+		      "display-name", dom_element_get_attribute (element, "display-name"),
+		      "command", dom_element_get_attribute (element, "command"),
+		      "visible", (g_strcmp0 (dom_element_get_attribute (element, "display"), "none") != 0),
+		      "for-each-file", (g_strcmp0 (dom_element_get_attribute (element, "for-each-file"), "true") == 0),
+		      "wait-command", (g_strcmp0 (dom_element_get_attribute (element, "wait-command"), "true") == 0),
+		      NULL);
+}
+
+
+GObject *
+gth_script_real_duplicate (GthDuplicable *duplicable)
+{
+	GthScript *script = GTH_SCRIPT (duplicable);
+	GthScript *new_script;
+
+	new_script = gth_script_new ();
+	g_object_set (new_script,
+		      "id", script->priv->id,
+		      "display-name", script->priv->display_name,
+		      "command", script->priv->command,
+		      "visible", script->priv->visible,
+		      "for-each-file", script->priv->for_each_file,
+		      "wait-command", script->priv->wait_command,
+		      NULL);
+
+	return (GObject *) new_script;
+}
+
+
+static void
+gth_script_finalize (GObject *base)
+{
+	GthScript *self;
+
+	self = GTH_SCRIPT (base);
+
+	g_free (self->priv->id);
+	g_free (self->priv->display_name);
+	g_free (self->priv->command);
+
+	G_OBJECT_CLASS (parent_class)->finalize (base);
+}
+
+
+static void
+gth_script_set_property (GObject      *object,
+		         guint         property_id,
+		         const GValue *value,
+		         GParamSpec   *pspec)
+{
+	GthScript *self;
+
+        self = GTH_SCRIPT (object);
+
+	switch (property_id) {
+	case PROP_ID:
+		g_free (self->priv->id);
+		self->priv->id = g_value_dup_string (value);
+		if (self->priv->id == NULL)
+			self->priv->id = g_strdup ("");
+		break;
+	case PROP_DISPLAY_NAME:
+		g_free (self->priv->display_name);
+		self->priv->display_name = g_value_dup_string (value);
+		if (self->priv->display_name == NULL)
+			self->priv->display_name = g_strdup ("");
+		break;
+	case PROP_COMMAND:
+		g_free (self->priv->command);
+		self->priv->command = g_value_dup_string (value);
+		if (self->priv->command == NULL)
+			self->priv->command = g_strdup ("");
+		break;
+	case PROP_VISIBLE:
+		self->priv->visible = g_value_get_boolean (value);
+		break;
+	case PROP_FOR_EACH_FILE:
+		self->priv->for_each_file = g_value_get_boolean (value);
+		break;
+	case PROP_WAIT_COMMAND:
+		self->priv->wait_command = g_value_get_boolean (value);
+		break;
+	default:
+		break;
+	}
+}
+
+
+static void
+gth_script_get_property (GObject    *object,
+		         guint       property_id,
+		         GValue     *value,
+		         GParamSpec *pspec)
+{
+	GthScript *self;
+
+        self = GTH_SCRIPT (object);
+
+	switch (property_id) {
+	case PROP_ID:
+		g_value_set_string (value, self->priv->id);
+		break;
+	case PROP_DISPLAY_NAME:
+		g_value_set_string (value, self->priv->display_name);
+		break;
+	case PROP_COMMAND:
+		g_value_set_string (value, self->priv->command);
+		break;
+	case PROP_VISIBLE:
+		g_value_set_boolean (value, self->priv->visible);
+		break;
+	case PROP_FOR_EACH_FILE:
+		g_value_set_boolean (value, self->priv->for_each_file);
+		break;
+	case PROP_WAIT_COMMAND:
+		g_value_set_boolean (value, self->priv->wait_command);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+		break;
+	}
+}
+
+
+static void
+gth_script_class_init (GthScriptClass *klass)
+{
+	GObjectClass *object_class;
+
+	parent_class = g_type_class_peek_parent (klass);
+	g_type_class_add_private (klass, sizeof (GthScriptPrivate));
+
+	object_class = G_OBJECT_CLASS (klass);
+	object_class->set_property = gth_script_set_property;
+	object_class->get_property = gth_script_get_property;
+	object_class->finalize = gth_script_finalize;
+
+	/* properties */
+
+	g_object_class_install_property (object_class,
+					 PROP_ID,
+					 g_param_spec_string ("id",
+                                                              "ID",
+                                                              "The object id",
+                                                              NULL,
+                                                              G_PARAM_READWRITE));
+	g_object_class_install_property (object_class,
+					 PROP_DISPLAY_NAME,
+					 g_param_spec_string ("display-name",
+                                                              "Display name",
+                                                              "The user visible name",
+                                                              NULL,
+                                                              G_PARAM_READWRITE));
+	g_object_class_install_property (object_class,
+					 PROP_COMMAND,
+					 g_param_spec_string ("command",
+                                                              "Command",
+                                                              "The command to execute",
+                                                              NULL,
+                                                              G_PARAM_READWRITE));
+	g_object_class_install_property (object_class,
+					 PROP_VISIBLE,
+					 g_param_spec_boolean ("visible",
+							       "Visible",
+							       "Whether this script should be visible in the script list",
+							       FALSE,
+							       G_PARAM_READWRITE));
+	g_object_class_install_property (object_class,
+					 PROP_FOR_EACH_FILE,
+					 g_param_spec_boolean ("for-each-file",
+							       "Each File",
+							       "Whether to execute the command on file at a time",
+							       FALSE,
+							       G_PARAM_READWRITE));
+	g_object_class_install_property (object_class,
+					 PROP_WAIT_COMMAND,
+					 g_param_spec_boolean ("wait-command",
+							       "Wait command",
+							       "Whether to wait command to finish",
+							       FALSE,
+							       G_PARAM_READWRITE));
+}
+
+
+static void
+gth_script_dom_domizable_interface_init (DomDomizableIface *iface)
+{
+	iface->create_element = gth_script_real_create_element;
+	iface->load_from_element = gth_script_real_load_from_element;
+}
+
+
+static void
+gth_script_gth_duplicable_interface_init (GthDuplicableIface *iface)
+{
+	iface->duplicate = gth_script_real_duplicate;
+}
+
+
+static void
+gth_script_init (GthScript *self)
+{
+	self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GTH_TYPE_SCRIPT, GthScriptPrivate);
+	self->priv->id = NULL;
+	self->priv->display_name = NULL;
+	self->priv->command = NULL;
+}
+
+
+GType
+gth_script_get_type (void)
+{
+        static GType type = 0;
+
+        if (! type) {
+                GTypeInfo type_info = {
+			sizeof (GthScriptClass),
+			NULL,
+			NULL,
+			(GClassInitFunc) gth_script_class_init,
+			NULL,
+			NULL,
+			sizeof (GthScript),
+			0,
+			(GInstanceInitFunc) gth_script_init
+		};
+		static const GInterfaceInfo dom_domizable_info = {
+			(GInterfaceInitFunc) gth_script_dom_domizable_interface_init,
+			(GInterfaceFinalizeFunc) NULL,
+			NULL
+		};
+		static const GInterfaceInfo gth_duplicable_info = {
+			(GInterfaceInitFunc) gth_script_gth_duplicable_interface_init,
+			(GInterfaceFinalizeFunc) NULL,
+			NULL
+		};
+
+		type = g_type_register_static (G_TYPE_OBJECT,
+					       "GthScript",
+					       &type_info,
+					       0);
+		g_type_add_interface_static (type, DOM_TYPE_DOMIZABLE, &dom_domizable_info);
+		g_type_add_interface_static (type, GTH_TYPE_DUPLICABLE, &gth_duplicable_info);
+	}
+
+        return type;
+}
+
+
+GthScript*
+gth_script_new (void)
+{
+	GthScript *script;
+	char      *id;
+
+	id = _g_rand_string (ID_LENGTH);
+	script = (GthScript *) g_object_new (GTH_TYPE_SCRIPT, "id", id, NULL);
+	g_free (id);
+
+	return script;
+}
+
+
+const char *
+gth_script_get_id (GthScript *script)
+{
+	return script->priv->id;
+}
+
+
+const char *
+gth_script_get_display_name (GthScript *script)
+{
+	return script->priv->display_name;
+}
+
+
+const char *
+gth_script_get_command (GthScript *script)
+{
+	return script->priv->command;
+}
+
+
+gboolean
+gth_script_is_visible (GthScript *script)
+{
+	return script->priv->visible;
+}
+
+
+gboolean
+gth_script_for_each_file (GthScript *script)
+{
+	return script->priv->for_each_file;
+}
+
+
+gboolean
+gth_script_wait_command (GthScript *script)
+{
+	return script->priv->wait_command;
+}
diff --git a/extensions/list_tools/gth-script.h b/extensions/list_tools/gth-script.h
new file mode 100644
index 0000000..b812207
--- /dev/null
+++ b/extensions/list_tools/gth-script.h
@@ -0,0 +1,67 @@
+/* -*- 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, write to the Free Software
+ *  Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_SCRIPT_H
+#define GTH_SCRIPT_H
+
+#include <glib-object.h>
+#include <gthumb.h>
+
+G_BEGIN_DECLS
+
+#define GTH_TYPE_SCRIPT         (gth_script_get_type ())
+#define GTH_SCRIPT(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), GTH_TYPE_SCRIPT, GthScript))
+#define GTH_SCRIPT_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST ((k), GTH_TYPE_SCRIPT, GthScriptClass))
+#define GTH_IS_SCRIPT(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), GTH_TYPE_SCRIPT))
+#define GTH_IS_SCRIPT_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), GTH_TYPE_SCRIPT))
+#define GTH_SCRIPT_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS((o), GTH_TYPE_SCRIPT, GthScriptClass))
+
+typedef struct _GthScript         GthScript;
+typedef struct _GthScriptPrivate  GthScriptPrivate;
+typedef struct _GthScriptClass    GthScriptClass;
+
+
+struct _GthScript
+{
+	GObject __parent;
+	GthScriptPrivate *priv;
+};
+
+struct _GthScriptClass
+{
+	GObjectClass __parent_class;
+};
+
+GType             gth_script_get_type          (void) G_GNUC_CONST;
+GthScript *       gth_script_new               (void);
+const char *      gth_script_get_id            (GthScript *script);
+const char *      gth_script_get_display_name  (GthScript *script);
+const char *      gth_script_get_command       (GthScript *script);
+gboolean          gth_script_is_visible        (GthScript *script);
+gboolean          gth_script_for_each_file     (GthScript *script);
+gboolean          gth_script_wait_command      (GthScript *script);
+GthTask *         gth_script_get_task          (GthScript *script,
+						GList     *file_list /* GthFileData */);
+
+G_END_DECLS
+
+#endif /* GTH_SCRIPT_H */
diff --git a/gthumb/gth-browser-ui.h b/gthumb/gth-browser-ui.h
index fdbfe51..06a095d 100644
--- a/gthumb/gth-browser-ui.h
+++ b/gthumb/gth-browser-ui.h
@@ -114,10 +114,10 @@ static const char *fixed_ui_info =
 "    <separator/>"
 "    <toolitem action='View_Prev'/>"
 "    <toolitem action='View_Next'/>"
-"    <separator/>"
+/*"    <separator/>"
 "    <toolitem action='Edit_Metadata'/>"
 "    <separator/>"
-"    <toolitem action='View_Fullscreen'/>"
+"    <toolitem action='View_Fullscreen'/>"*/
 "    <separator/>"
 "    <placeholder name='ViewerCommands'/>"
 "    <separator expand='true'/>"
diff --git a/gthumb/gth-filter-file.c b/gthumb/gth-filter-file.c
index 799dd33..e761c2d 100644
--- a/gthumb/gth-filter-file.c
+++ b/gthumb/gth-filter-file.c
@@ -211,10 +211,10 @@ gth_filter_file_get_tests (GthFilterFile *filters)
 {
 	GList *list = NULL;
 	GList *scan;
-	
+
 	for (scan = filters->items; scan; scan = scan->next)
 		list = g_list_prepend (list, gth_duplicable_duplicate (GTH_DUPLICABLE (scan->data)));
-	
+
 	return g_list_reverse (list);
 }
 



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