[gthumb/ext: 38/79] continue work on scripts



commit 21f6e9e0443c6dab49eabea7a19ce38f75213fb8
Author: Paolo Bacchilega <paobac src gnome org>
Date:   Fri Jul 10 23:14:18 2009 +0200

    continue work on scripts

 .../comments/gth-metadata-provider-comment.c       |    8 +-
 extensions/exiv2/gth-metadata-provider-exiv2.c     |    8 +-
 .../image_viewer/gth-metadata-provider-image.c     |    8 +-
 extensions/list_tools/Makefile.am                  |    2 +
 extensions/list_tools/callbacks.c                  |   58 +++-
 extensions/list_tools/data/ui/Makefile.am          |    2 +-
 extensions/list_tools/data/ui/ask-value.ui         |  118 ++++++
 extensions/list_tools/data/ui/script-editor.ui     |  120 ++++++-
 extensions/list_tools/gth-script-editor-dialog.c   |    2 +-
 extensions/list_tools/gth-script-task.c            |  296 +++++++++++++++
 extensions/list_tools/gth-script-task.h            |   59 +++
 extensions/list_tools/gth-script.c                 |  384 ++++++++++++++++++++
 extensions/list_tools/gth-script.h                 |   23 +-
 gthumb/glib-utils.c                                |   22 ++
 gthumb/glib-utils.h                                |    3 +-
 gthumb/gnome-desktop-thumbnail.c                   |   12 +-
 gthumb/gth-file-data.c                             |   56 +++
 gthumb/gth-file-data.h                             |   71 ++--
 gthumb/gth-file-properties.c                       |   33 +--
 gthumb/gth-metadata-provider.c                     |   14 +-
 gthumb/gth-thumb-loader.h                          |    5 +-
 gthumb/gthumb-error.h                              |    1 +
 tests/glib-utils-test.c                            |   39 ++-
 23 files changed, 1196 insertions(+), 148 deletions(-)
---
diff --git a/extensions/comments/gth-metadata-provider-comment.c b/extensions/comments/gth-metadata-provider-comment.c
index 28f1069..e6f9bfb 100644
--- a/extensions/comments/gth-metadata-provider-comment.c
+++ b/extensions/comments/gth-metadata-provider-comment.c
@@ -124,11 +124,7 @@ gth_metadata_provider_comment_write (GthMetadataProvider *self,
 				     GthFileData         *file_data,
 				     const char          *attributes)
 {
-	GFileAttributeMatcher *matcher;
-
-	matcher = g_file_attribute_matcher_new (attributes);
-
-	if (g_file_attribute_matcher_matches (matcher, "comment::*")) {
+	if (_g_file_attributes_matches (attributes, "comment::*")) {
 		GthComment    *comment;
 		char          *data;
 		gsize          length;
@@ -163,8 +159,6 @@ gth_metadata_provider_comment_write (GthMetadataProvider *self,
 		g_free (data);
 		g_object_unref (comment);
 	}
-
-	g_file_attribute_matcher_unref (matcher);
 }
 
 
diff --git a/extensions/exiv2/gth-metadata-provider-exiv2.c b/extensions/exiv2/gth-metadata-provider-exiv2.c
index 8270979..2d3cb40 100644
--- a/extensions/exiv2/gth-metadata-provider-exiv2.c
+++ b/extensions/exiv2/gth-metadata-provider-exiv2.c
@@ -45,11 +45,7 @@ gth_metadata_provider_exiv2_read (GthMetadataProvider *self,
 				  GthFileData         *file_data,
 				  const char          *attributes)
 {
-	GFileAttributeMatcher *matcher;
-
-	matcher = g_file_attribute_matcher_new (attributes);
-
-	if (g_file_attribute_matcher_matches (matcher, "Exif::*,Iptc::*,Xmp::*")) {
+	if (_g_file_attributes_matches (attributes, "Exif::*,Iptc::*,Xmp::*")) {
 		char        *uri;
 		char        *uri_wo_ext;
 		char        *sidecar_uri;
@@ -78,8 +74,6 @@ gth_metadata_provider_exiv2_read (GthMetadataProvider *self,
 		g_free (uri_wo_ext);
 		g_free (uri);
 	}
-
-	g_file_attribute_matcher_unref (matcher);
 }
 
 
diff --git a/extensions/image_viewer/gth-metadata-provider-image.c b/extensions/image_viewer/gth-metadata-provider-image.c
index f233729..2e70bda 100644
--- a/extensions/image_viewer/gth-metadata-provider-image.c
+++ b/extensions/image_viewer/gth-metadata-provider-image.c
@@ -45,11 +45,7 @@ gth_metadata_provider_image_read (GthMetadataProvider *self,
 				  GthFileData         *file_data,
 				  const char          *attributes)
 {
-	GFileAttributeMatcher *matcher;
-
-	matcher = g_file_attribute_matcher_new (attributes);
-
-	if (g_file_attribute_matcher_matches (matcher, "image::*")) {
+	if (_g_file_attributes_matches (attributes, "image::*")) {
 		GdkPixbufFormat *format;
 		GFile           *cache_file;
 		char            *filename;
@@ -76,8 +72,6 @@ gth_metadata_provider_image_read (GthMetadataProvider *self,
 		g_free (filename);
 		g_object_unref (cache_file);
 	}
-
-	g_file_attribute_matcher_unref (matcher);
 }
 
 
diff --git a/extensions/list_tools/Makefile.am b/extensions/list_tools/Makefile.am
index e987e49..e09660d 100644
--- a/extensions/list_tools/Makefile.am
+++ b/extensions/list_tools/Makefile.am
@@ -16,6 +16,8 @@ liblist_tools_la_SOURCES = 		\
 	gth-script-editor-dialog.h	\
 	gth-script-file.c		\
 	gth-script-file.h		\
+	gth-script-task.h		\
+	gth-script-task.c		\
 	main.c
 
 liblist_tools_la_CFLAGS = $(GTHUMB_CFLAGS) $(DISABLE_DEPRECATED) $(WARNINGS) -I$(top_srcdir) -I$(top_builddir)/gthumb 
diff --git a/extensions/list_tools/callbacks.c b/extensions/list_tools/callbacks.c
index 8b7686d..69811e1 100644
--- a/extensions/list_tools/callbacks.c
+++ b/extensions/list_tools/callbacks.c
@@ -26,7 +26,9 @@
 #include <glib-object.h>
 #include <gthumb.h>
 #include "actions.h"
+#include "callbacks.h"
 #include "gth-script-file.h"
+#include "gth-script-task.h"
 
 
 #define BROWSER_DATA_KEY "list-tools-browser-data"
@@ -76,16 +78,20 @@ activate_script_menu_item (GtkMenuItem *menuitem,
 
 	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;
+		GList *items;
+		GList *file_list;
 
 		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);
+		if (file_list != NULL) {
+			GthTask *task;
+
+			task = gth_script_task_new (GTK_WINDOW (data->browser), script, file_list);
+			gth_browser_exec_task (data->browser, task);
+
+			g_object_unref (task);
+		}
 
-		g_object_unref (task);
 		_g_object_list_unref (file_list);
 		_gtk_tree_path_list_free (items);
 	}
@@ -140,6 +146,8 @@ update_scripts_menu (BrowserData *data)
 	else
 		gtk_widget_hide (separator1);
 
+	list_tools__gth_browser_update_sensitivity_cb (data->browser);
+
 	_g_object_list_unref (script_list);
 }
 
@@ -197,19 +205,39 @@ list_tools__gth_browser_construct_cb (GthBrowser *browser)
 void
 list_tools__gth_browser_update_sensitivity_cb (GthBrowser *browser)
 {
-	/*
-	BrowserData   *data;
-	GthFileSource *file_source;
-	int            n_selected;
-	gboolean       sensitive;
+	BrowserData *data;
+	int          n_selected;
+	gboolean     sensitive;
+	GtkWidget   *separator1;
+	GtkWidget   *separator2;
+	GtkWidget   *menu;
 
 	data = g_object_get_data (G_OBJECT (browser), BROWSER_DATA_KEY);
 	g_return_if_fail (data != NULL);
 
-	file_source = gth_browser_get_location_source (browser);
 	n_selected = gth_file_selection_get_n_selected (GTH_FILE_SELECTION (gth_browser_get_file_list_view (browser)));
+	sensitive = (n_selected > 0);
 
-	sensitive = (n_selected > 0) && (file_source != NULL) && gth_file_source_can_cut (file_source);
-	gtk_widget_set_sensitive (GTK_WIDGET (data->tool_item), sensitive);
-	*/
+	separator1 = gtk_ui_manager_get_widget (gth_browser_get_ui_manager (browser), "/ListToolsPopup/Tools");
+	separator2 = gtk_ui_manager_get_widget (gth_browser_get_ui_manager (browser), "/ListToolsPopup/Scripts");
+	menu = gtk_widget_get_parent (separator1);
+	{
+		GList *children;
+		GList *scan;
+
+		children = gtk_container_get_children (GTK_CONTAINER (menu));
+
+		if (separator1 != NULL) {
+			for (scan = children; scan; scan = scan->next)
+				if (scan->data == separator1) {
+					scan = scan->next;
+					break;
+				}
+		}
+		else
+			scan = children;
+
+		for (/* void */; scan && (scan->data != separator2); scan = scan->next)
+			gtk_widget_set_sensitive (scan->data, sensitive);
+	}
 }
diff --git a/extensions/list_tools/data/ui/Makefile.am b/extensions/list_tools/data/ui/Makefile.am
index b220c3f..169e14f 100644
--- a/extensions/list_tools/data/ui/Makefile.am
+++ b/extensions/list_tools/data/ui/Makefile.am
@@ -1,5 +1,5 @@
 uidir = $(datadir)/gthumb/ui
-ui_DATA = personalize-scripts.ui script-editor.ui
+ui_DATA = ask-value.ui personalize-scripts.ui script-editor.ui
 EXTRA_DIST = $(ui_DATA)
 
 -include $(top_srcdir)/git.mk
diff --git a/extensions/list_tools/data/ui/ask-value.ui b/extensions/list_tools/data/ui/ask-value.ui
new file mode 100644
index 0000000..e69533e
--- /dev/null
+++ b/extensions/list_tools/data/ui/ask-value.ui
@@ -0,0 +1,118 @@
+<?xml version="1.0"?>
+<interface>
+  <requires lib="gtk+" version="2.16"/>
+  <!-- interface-naming-policy project-wide -->
+  <object class="GtkDialog" id="ask_value_dialog">
+    <property name="width_request">500</property>
+    <property name="border_width">5</property>
+    <property name="type_hint">dialog</property>
+    <property name="has_separator">False</property>
+    <child internal-child="vbox">
+      <object class="GtkVBox" id="dialog-vbox1">
+        <property name="visible">True</property>
+        <property name="orientation">vertical</property>
+        <property name="spacing">2</property>
+        <child>
+          <object class="GtkHBox" id="hbox1">
+            <property name="visible">True</property>
+            <property name="border_width">5</property>
+            <property name="spacing">12</property>
+            <child>
+              <object class="GtkImage" id="request_image">
+                <property name="width_request">128</property>
+                <property name="height_request">128</property>
+                <property name="visible">True</property>
+                <property name="stock">gtk-missing-image</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkVBox" id="vbox1">
+                <property name="visible">True</property>
+                <property name="orientation">vertical</property>
+                <property name="spacing">6</property>
+                <child>
+                  <object class="GtkLabel" id="request_label">
+                    <property name="visible">True</property>
+                    <property name="xalign">0</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkEntry" id="request_entry">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="invisible_char">&#x25CF;</property>
+                    <property name="activates_default">True</property>
+                  </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="expand">False</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+        <child internal-child="action_area">
+          <object class="GtkHButtonBox" id="dialog-action_area1">
+            <property name="visible">True</property>
+            <property name="layout_style">end</property>
+            <child>
+              <object class="GtkButton" id="cancel_button">
+                <property name="label">gtk-cancel</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="ok_button">
+                <property name="label">gtk-execute</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="can_default">True</property>
+                <property name="has_default">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="pack_type">end</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+    <action-widgets>
+      <action-widget response="1">cancel_button</action-widget>
+      <action-widget response="2">ok_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
index 6d47730..ccafed5 100644
--- a/extensions/list_tools/data/ui/script-editor.ui
+++ b/extensions/list_tools/data/ui/script-editor.ui
@@ -104,7 +104,7 @@
             <child>
               <object class="GtkTable" id="table2">
                 <property name="visible">True</property>
-                <property name="n_rows">7</property>
+                <property name="n_rows">10</property>
                 <property name="n_columns">2</property>
                 <property name="column_spacing">12</property>
                 <property name="row_spacing">6</property>
@@ -160,8 +160,8 @@
                     </attributes>
                   </object>
                   <packing>
-                    <property name="top_attach">4</property>
-                    <property name="bottom_attach">5</property>
+                    <property name="top_attach">6</property>
+                    <property name="bottom_attach">7</property>
                   </packing>
                 </child>
                 <child>
@@ -224,8 +224,8 @@
                   <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>
+                    <property name="top_attach">6</property>
+                    <property name="bottom_attach">7</property>
                   </packing>
                 </child>
                 <child>
@@ -264,8 +264,8 @@
                     </attributes>
                   </object>
                   <packing>
-                    <property name="top_attach">5</property>
-                    <property name="bottom_attach">6</property>
+                    <property name="top_attach">7</property>
+                    <property name="bottom_attach">8</property>
                   </packing>
                 </child>
                 <child>
@@ -280,29 +280,29 @@
                   <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>
+                    <property name="top_attach">7</property>
+                    <property name="bottom_attach">8</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>
+                    <property name="label" translatable="yes" comments="Translate only 'property_name', keeping the underscore if possible.">%attr{ property_name }</property>
                     <attributes>
                       <attribute name="size" value="8000"/>
                     </attributes>
                   </object>
                   <packing>
-                    <property name="top_attach">6</property>
-                    <property name="bottom_attach">7</property>
+                    <property name="top_attach">8</property>
+                    <property name="bottom_attach">9</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>
+                    <property name="label" translatable="yes">A file attribute</property>
                     <attributes>
                       <attribute name="size" value="8000"/>
                     </attributes>
@@ -310,8 +310,98 @@
                   <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>
+                    <property name="top_attach">8</property>
+                    <property name="bottom_attach">9</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="label17">
+                    <property name="visible">True</property>
+                    <property name="xalign">0</property>
+                    <property name="label" translatable="yes">%N</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="label18">
+                    <property name="visible">True</property>
+                    <property name="xalign">0</property>
+                    <property name="label" translatable="yes">The file basename without extension</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="label19">
+                    <property name="visible">True</property>
+                    <property name="xalign">0</property>
+                    <property name="label" translatable="yes">%E</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="label20">
+                    <property name="visible">True</property>
+                    <property name="xalign">0</property>
+                    <property name="label" translatable="yes">The file extension</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="label21">
+                    <property name="visible">True</property>
+                    <property name="xalign">0</property>
+                    <property name="label" translatable="yes" comments="Translate only 'text'.">%quote{ text }</property>
+                    <attributes>
+                      <attribute name="size" value="8000"/>
+                    </attributes>
+                  </object>
+                  <packing>
+                    <property name="top_attach">9</property>
+                    <property name="bottom_attach">10</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="label22">
+                    <property name="visible">True</property>
+                    <property name="xalign">0</property>
+                    <property name="label" translatable="yes">Quote the text </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">9</property>
+                    <property name="bottom_attach">10</property>
                   </packing>
                 </child>
               </object>
diff --git a/extensions/list_tools/gth-script-editor-dialog.c b/extensions/list_tools/gth-script-editor-dialog.c
index ca80738..361739c 100644
--- a/extensions/list_tools/gth-script-editor-dialog.c
+++ b/extensions/list_tools/gth-script-editor-dialog.c
@@ -143,7 +143,7 @@ gth_script_editor_dialog_construct (GthScriptEditorDialog *self,
     		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_window_set_resizable (GTK_WINDOW (self), TRUE);
 	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);
diff --git a/extensions/list_tools/gth-script-task.c b/extensions/list_tools/gth-script-task.c
new file mode 100644
index 0000000..39caf90
--- /dev/null
+++ b/extensions/list_tools/gth-script-task.c
@@ -0,0 +1,296 @@
+/* -*- 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 "gth-script-task.h"
+
+
+struct _GthScriptTaskPrivate {
+	GthScript    *script;
+	GtkWindow    *parent;
+	GList        *file_list;
+	GList        *current;
+	GPid          pid;
+	guint         script_watch;
+	GCancellable *cancellable;
+};
+
+
+static gpointer parent_class = NULL;
+
+
+static void
+gth_script_task_finalize (GObject *object)
+{
+	GthScriptTask *self;
+
+	self = GTH_SCRIPT_TASK (object);
+
+	g_object_unref (self->priv->script);
+	g_object_unref (self->priv->cancellable);
+	_g_object_list_unref (self->priv->file_list);
+
+	G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+
+static void _gth_script_task_exec (GthScriptTask *self);
+
+
+static void
+_gth_script_task_exec_next_file (GthScriptTask *self)
+{
+	self->priv->current = self->priv->current->next;
+	if (self->priv->current == NULL)
+		gth_task_completed (GTH_TASK (self), NULL);
+	else
+		_gth_script_task_exec (self);
+}
+
+
+static void
+watch_script_cb (GPid     pid,
+                 int      status,
+                 gpointer data)
+{
+	GthScriptTask *self = data;
+	GError        *error;
+
+	g_spawn_close_pid (self->priv->pid);
+	self->priv->pid = 0;
+	self->priv->script_watch = 0;
+
+	if (status != 0) {
+		error = g_error_new (GTH_TASK_ERROR, GTH_TASK_ERROR_FAILED, _("Command exited abnormally with status %d"), status);
+		gth_task_completed (GTH_TASK (self), error);
+		return;
+	}
+
+	if (gth_script_for_each_file (self->priv->script))
+		_gth_script_task_exec_next_file (self);
+	else
+		gth_task_completed (GTH_TASK (self), NULL);
+}
+
+
+static void
+_gth_script_task_exec (GthScriptTask *self)
+{
+	char      *command_line;
+	GError    *error = NULL;
+	gboolean   retval = FALSE;
+
+	if (gth_script_for_each_file (self->priv->script)) {
+		GList *list;
+
+		list = g_list_prepend (NULL, self->priv->current->data);
+		command_line = gth_script_get_command_line (self->priv->script,
+							    self->priv->parent,
+							    list,
+							    &error);
+
+		g_list_free (list);
+	}
+	else
+		command_line = gth_script_get_command_line (self->priv->script,
+							    self->priv->parent,
+							    self->priv->file_list,
+							    &error);
+
+	if (error == NULL) {
+		char **argv;
+
+		argv = g_new (char *, 4);
+		argv[0] = "sh";
+		argv[1] = "-c";
+		argv[2] = command_line;
+		argv[3] = NULL;
+
+		if (gth_script_wait_command (self->priv->script)) {
+			if (g_spawn_async (NULL,
+					   argv,
+					   NULL,
+					   G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_SEARCH_PATH,
+					   NULL,
+					   NULL,
+					   &self->priv->pid,
+					   &error))
+			{
+				self->priv->script_watch = g_child_watch_add (self->priv->pid,
+									      watch_script_cb,
+									      self);
+				retval = TRUE;
+			}
+		}
+		else {
+			if (g_spawn_async (NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, &error))
+				retval = TRUE;
+		}
+
+		g_free (argv);
+	}
+
+	g_free (command_line);
+
+	if (! retval) {
+		gth_task_completed (GTH_TASK (self), error);
+		return;
+	}
+
+	if (gth_script_wait_command (self->priv->script))
+		return;
+
+	if (gth_script_for_each_file (self->priv->script)) {
+		_gth_script_task_exec_next_file (self);
+		return;
+	}
+
+	gth_task_completed (GTH_TASK (self), NULL);
+}
+
+
+static void
+file_info_ready_cb (GList    *files,
+		    GError   *error,
+		    gpointer  user_data)
+{
+	GthScriptTask *self = user_data;
+
+	if (error != NULL) {
+		gth_task_completed (GTH_TASK (self), error);
+		return;
+	}
+
+	_gth_script_task_exec (self);
+}
+
+
+static void
+gth_script_task_exec (GthTask *task)
+{
+	GthScriptTask *self;
+	char          *attributes;
+
+	g_return_if_fail (GTH_IS_SCRIPT_TASK (task));
+
+	self = GTH_SCRIPT_TASK (task);
+
+	attributes = gth_script_get_requested_attributes (self->priv->script);
+	if (attributes != NULL) {
+		_g_query_metadata_async (self->priv->file_list,
+					 attributes,
+					 self->priv->cancellable,
+					 file_info_ready_cb,
+					 self);
+		g_free (attributes);
+	}
+	else
+		_gth_script_task_exec (self);
+}
+
+
+static void
+gth_script_task_cancel (GthTask *task)
+{
+	GthScriptTask *self;
+
+	g_return_if_fail (GTH_IS_SCRIPT_TASK (task));
+
+	self = GTH_SCRIPT_TASK (task);
+
+	if (self->priv->pid != 0)
+		kill (self->priv->pid, SIGTERM);
+	else
+		g_cancellable_cancel (self->priv->cancellable);
+}
+
+
+static void
+gth_script_task_class_init (GthScriptTaskClass *klass)
+{
+	GObjectClass *object_class;
+	GthTaskClass *task_class;
+
+	parent_class = g_type_class_peek_parent (klass);
+	g_type_class_add_private (klass, sizeof (GthScriptTaskPrivate));
+
+	object_class = G_OBJECT_CLASS (klass);
+	object_class->finalize = gth_script_task_finalize;
+
+	task_class = GTH_TASK_CLASS (klass);
+	task_class->exec = gth_script_task_exec;
+	task_class->cancel = gth_script_task_cancel;
+}
+
+
+static void
+gth_script_task_init (GthScriptTask *self)
+{
+	self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GTH_TYPE_SCRIPT_TASK, GthScriptTaskPrivate);
+	self->priv->cancellable = g_cancellable_new ();
+	self->priv->pid = 0;
+}
+
+
+GType
+gth_script_task_get_type (void)
+{
+	static GType type = 0;
+
+	if (! type) {
+		GTypeInfo type_info = {
+			sizeof (GthScriptTaskClass),
+			NULL,
+			NULL,
+			(GClassInitFunc) gth_script_task_class_init,
+			NULL,
+			NULL,
+			sizeof (GthScriptTask),
+			0,
+			(GInstanceInitFunc) gth_script_task_init
+		};
+
+		type = g_type_register_static (GTH_TYPE_TASK,
+					       "GthScriptTask",
+					       &type_info,
+					       0);
+	}
+
+	return type;
+}
+
+
+GthTask *
+gth_script_task_new (GtkWindow *parent,
+		     GthScript *script,
+		     GList     *file_list)
+{
+	GthScriptTask *self;
+
+	self = GTH_SCRIPT_TASK (g_object_new (GTH_TYPE_SCRIPT_TASK, NULL));
+	self->priv->parent = parent;
+	self->priv->script = g_object_ref (script);
+	self->priv->file_list = _g_object_list_ref (file_list);
+	self->priv->current = self->priv->file_list;
+
+	return (GthTask *) self;
+}
diff --git a/extensions/list_tools/gth-script-task.h b/extensions/list_tools/gth-script-task.h
new file mode 100644
index 0000000..0ae0a52
--- /dev/null
+++ b/extensions/list_tools/gth-script-task.h
@@ -0,0 +1,59 @@
+/* -*- 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_TASK_H
+#define GTH_SCRIPT_TASK_H
+
+#include <glib.h>
+#include <gthumb.h>
+#include "gth-script.h"
+
+G_BEGIN_DECLS
+
+#define GTH_TYPE_SCRIPT_TASK            (gth_script_task_get_type ())
+#define GTH_SCRIPT_TASK(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTH_TYPE_SCRIPT_TASK, GthScriptTask))
+#define GTH_SCRIPT_TASK_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GTH_TYPE_SCRIPT_TASK, GthScriptTaskClass))
+#define GTH_IS_SCRIPT_TASK(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTH_TYPE_SCRIPT_TASK))
+#define GTH_IS_SCRIPT_TASK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTH_TYPE_SCRIPT_TASK))
+#define GTH_SCRIPT_TASK_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), GTH_TYPE_SCRIPT_TASK, GthScriptTaskClass))
+
+typedef struct _GthScriptTask        GthScriptTask;
+typedef struct _GthScriptTaskClass   GthScriptTaskClass;
+typedef struct _GthScriptTaskPrivate GthScriptTaskPrivate;
+
+struct _GthScriptTask {
+	GthTask __parent;
+	GthScriptTaskPrivate *priv;
+};
+
+struct _GthScriptTaskClass {
+	GthTaskClass __parent;
+};
+
+GType         gth_script_task_get_type     (void);
+GthTask *     gth_script_task_new          (GtkWindow *parent,
+					    GthScript *script,
+					    GList     *file_list /* GthFileData */);
+
+G_END_DECLS
+
+#endif /* GTH_SCRIPT_TASK_H */
diff --git a/extensions/list_tools/gth-script.c b/extensions/list_tools/gth-script.c
index 5585727..7a213e6 100644
--- a/extensions/list_tools/gth-script.c
+++ b/extensions/list_tools/gth-script.c
@@ -387,3 +387,387 @@ gth_script_wait_command (GthScript *script)
 {
 	return script->priv->wait_command;
 }
+
+
+char *
+gth_script_get_requested_attributes (GthScript *script)
+{
+	GRegex   *re;
+	char    **a;
+	int       i, n, j;
+	char    **b;
+	char     *attributes;
+
+	re = g_regex_new ("%prop\\{([^}]+)\\}", 0, 0, NULL);
+	a = g_regex_split (re, script->priv->command, 0);
+	for (i = 0, n = 0; a[i] != NULL; i++)
+		if ((i > 0) && (i % 2 == 0))
+			n++;
+	if (n == 0)
+		return NULL;
+
+	b = g_new (char *, n + 1);
+	for (i = 1, j = 0; a[i] != NULL; i += 2, j++) {
+		b[j] = g_strstrip (a[i]);
+	}
+	b[j] = NULL;
+	attributes = g_strjoinv (",", b);
+
+	g_free (b);
+	g_strfreev (a);
+	g_regex_unref (re);
+
+	return attributes;
+}
+
+
+/* -- gth_script_get_command_line -- */
+
+
+typedef struct {
+	GtkWindow  *parent;
+	GthScript  *script;
+	GList      *file_list;
+	GError    **error;
+	gboolean    quote_values;
+} ReplaceData;
+
+
+typedef char * (*GetFileDataValueFunc) (GthFileData *file_data);
+
+
+static char *
+create_file_list (GList                *file_list,
+		  GetFileDataValueFunc  func,
+		  gboolean              quote_value)
+{
+	GString *s;
+	GList   *scan;
+
+	s = g_string_new ("");
+	for (scan = file_list; scan; scan = scan->next) {
+		GthFileData *file_data = scan->data;
+		char        *value;
+		char        *quoted;
+
+		value = func (file_data);
+		if (quote_value)
+			quoted = g_shell_quote (value);
+		else
+			quoted = g_strdup (value);
+
+		g_string_append (s, quoted);
+		if (scan->next != NULL)
+			g_string_append (s, " ");
+
+		g_free (quoted);
+		g_free (value);
+	}
+
+	return g_string_free (s, FALSE);
+}
+
+
+static char *
+get_uri_func (GthFileData *file_data)
+{
+	return g_file_get_uri (file_data->file);
+}
+
+
+static char *
+get_filename_func (GthFileData *file_data)
+{
+	return g_file_get_path (file_data->file);
+}
+
+
+static char *
+get_basename_func (GthFileData *file_data)
+{
+	return g_file_get_basename (file_data->file);
+}
+
+
+static char *
+get_name_wo_ext_func (GthFileData *file_data)
+{
+	char *path;
+	char *name;
+
+	path = g_file_get_path (file_data->file);
+	name = _g_uri_remove_extension (path);
+
+	g_free (path);
+
+	return name;
+}
+
+
+static char *
+get_ext_func (GthFileData *file_data)
+{
+	char *path;
+	char *ext;
+
+	path = g_file_get_path (file_data->file);
+	ext = g_strdup (_g_uri_get_file_extension (path));
+
+	g_free (path);
+
+	return ext;
+}
+
+
+static char *
+get_parent_func (GthFileData *file_data)
+{
+	GFile *parent;
+	char  *path;
+
+	parent = g_file_get_parent (file_data->file);
+	path = g_file_get_path (parent);
+
+	g_object_unref (parent);
+
+	return path;
+}
+
+
+static void
+thumb_loader_ready_cb (GthThumbLoader *thumb_loader,
+		       GError         *error,
+		       gpointer        user_data)
+{
+	GtkBuilder *builder = user_data;
+	GdkPixbuf  *pixbuf;
+
+	pixbuf = gth_thumb_loader_get_pixbuf (thumb_loader);
+	if (pixbuf != NULL) {
+		GtkWidget *image;
+
+		image = _gtk_builder_get_widget (builder, "request_image");
+		if (image != NULL)
+			gtk_image_set_from_pixbuf (GTK_IMAGE (image), pixbuf);
+	}
+
+	g_object_unref (builder);
+}
+
+
+static char *
+ask_value (ReplaceData *replace_data,
+	   char        *match)
+{
+	GRegex          *re;
+	char           **a;
+	int              len;
+	char            *prompt;
+	char            *default_value;
+	GtkBuilder      *builder;
+	GtkWidget       *dialog;
+	GthThumbLoader  *thumb_loader;
+	int              result;
+	char            *value;
+
+	re = g_regex_new ("%ask(\\{([^}]+)\\}(\\{([^}]+)\\})?)?", 0, 0, NULL);
+	a = g_regex_split (re, match, 0);
+	len = g_strv_length (a);
+	if (len >= 3)
+		prompt = g_strstrip (a[2]);
+	else
+		prompt = _("Enter a value:");
+	if (len >= 5)
+		default_value = g_strstrip (a[4]);
+	else
+		default_value = "";
+
+	builder = _gtk_builder_new_from_file ("ask-value.ui", "list_tools");
+	dialog = _gtk_builder_get_widget (builder, "ask_value_dialog");
+	gtk_label_set_text (GTK_LABEL (_gtk_builder_get_widget (builder, "request_label")), prompt);
+	gtk_entry_set_text (GTK_ENTRY (_gtk_builder_get_widget (builder, "request_entry")), default_value);
+	gtk_window_set_transient_for (GTK_WINDOW (dialog), replace_data->parent);
+	gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
+
+	g_object_ref (builder);
+	thumb_loader = gth_thumb_loader_new (128, 128);
+	g_signal_connect (thumb_loader, "ready", G_CALLBACK (thumb_loader_ready_cb), builder);
+	gth_thumb_loader_set_file (thumb_loader, (GthFileData *) replace_data->file_list->data);
+	gth_thumb_loader_load (thumb_loader);
+
+	result = gtk_dialog_run (GTK_DIALOG (dialog));
+	if (result == 2)
+		value = g_utf8_normalize (gtk_entry_get_text (GTK_ENTRY (_gtk_builder_get_widget (builder, "request_entry"))), -1, G_NORMALIZE_NFC);
+	else
+		value = NULL;
+
+	gtk_widget_destroy (dialog);
+
+	g_object_unref (builder);
+	g_strfreev (a);
+	g_regex_unref (re);
+
+	return value;
+}
+
+
+static char *
+create_attribute_list (GList    *file_list,
+		      char     *match,
+		      gboolean  quote_value)
+{
+	GRegex    *re;
+	char     **a;
+	char      *attribute = NULL;
+	gboolean   first_value = TRUE;
+	GString   *s;
+	GList     *scan;
+
+	re = g_regex_new ("%prop\\{([^}]+)\\}", 0, 0, NULL);
+	a = g_regex_split (re, match, 0);
+	if (g_strv_length (a) >= 2)
+		attribute = g_strstrip (a[1]);
+	if (attribute == NULL)
+		return NULL;
+
+	s = g_string_new ("");
+	for (scan = file_list; scan; scan = scan->next) {
+		GthFileData *file_data = scan->data;
+		char        *value;
+		char        *quoted;
+
+		value = gth_file_data_get_attribute_as_string (file_data, attribute);
+		if (value == NULL)
+			continue;
+
+		if (value != NULL) {
+			char *tmp_value;
+
+			tmp_value = _g_utf8_replace (value, "[\r\n]", " ");
+			g_free (value);
+			value = tmp_value;
+		}
+		if (quote_value)
+			quoted = g_shell_quote (value);
+		else
+			quoted = g_strdup (value);
+
+		if (! first_value)
+			g_string_append (s, " ");
+		g_string_append (s, quoted);
+
+		first_value = FALSE;
+
+		g_free (quoted);
+		g_free (value);
+	}
+
+	g_strfreev (a);
+	g_regex_unref (re);
+
+	return g_string_free (s, FALSE);
+}
+
+
+static gboolean
+command_line_eval_cb (const GMatchInfo *info,
+		      GString          *res,
+		      gpointer          data)
+{
+	ReplaceData *replace_data = data;
+	char        *match;
+	char        *r;
+
+	match = g_match_info_fetch (info, 0);
+	if (strcmp (match, "%U") == 0)
+		r = create_file_list (replace_data->file_list, get_uri_func, replace_data->quote_values);
+	else if (strcmp (match, "%F") == 0)
+		r = create_file_list (replace_data->file_list, get_filename_func, replace_data->quote_values);
+	else if (strcmp (match, "%B") == 0)
+		r = create_file_list (replace_data->file_list, get_basename_func, replace_data->quote_values);
+	else if (strcmp (match, "%N") == 0)
+		r = create_file_list (replace_data->file_list, get_name_wo_ext_func, replace_data->quote_values);
+	else if (strcmp (match, "%E") == 0)
+		r = create_file_list (replace_data->file_list, get_ext_func, replace_data->quote_values);
+	else if (strcmp (match, "%P") == 0)
+		r = create_file_list (replace_data->file_list, get_parent_func, replace_data->quote_values);
+	else if (strncmp (match, "%prop", 5) == 0) {
+		r = create_attribute_list (replace_data->file_list, match, replace_data->quote_values);
+		if (r == NULL)
+			*replace_data->error = g_error_new_literal (GTH_TASK_ERROR, GTH_TASK_ERROR_FAILED, _("Malformed command"));
+	}
+	else if (strncmp (match, "%ask", 4) == 0) {
+		r = ask_value (replace_data, match);
+		if (r == NULL)
+			*replace_data->error = g_error_new_literal (GTH_TASK_ERROR, GTH_TASK_ERROR_CANCELLED, "");
+		else if (replace_data->quote_values) {
+			char *q;
+
+			q = g_shell_quote (r);
+			g_free (r);
+			r = q;
+		}
+	}
+
+	if (r != NULL)
+		g_string_append (res, r);
+
+	g_free (r);
+	g_free (match);
+
+	return FALSE;
+}
+
+
+char *
+gth_script_get_command_line (GthScript  *script,
+			     GtkWindow  *parent,
+			     GList      *file_list /* GthFileData */,
+			     GError    **error)
+{
+	ReplaceData  *replace_data;
+	GRegex       *qre;
+	GRegex       *re;
+	char        **a;
+	GString      *command_line;
+	int           i;
+	char         *result;
+
+	replace_data = g_new0 (ReplaceData, 1);
+	replace_data->parent = parent;
+	replace_data->script = script;
+	replace_data->file_list = file_list;
+	replace_data->error = error;
+
+	re = g_regex_new ("%U|%F|%B|%N|%E|%P|%ask(\\{[^}]+\\}(\\{[^}]+\\})?)?|%attr\\{[^}]+\\}", 0, 0, NULL);
+
+	replace_data->quote_values = FALSE;
+	command_line = g_string_new ("");
+	qre = g_regex_new ("%quote\\{([^}]+)\\}", 0, 0, NULL);
+	a = g_regex_split (qre, script->priv->command, 0);
+	for (i = 0; a[i] != NULL; i++) {
+		if (i % 2 == 1) {
+			char *sub_result;
+			char *quoted;
+
+			sub_result = g_regex_replace_eval (re, a[i], -1, 0, 0, command_line_eval_cb, replace_data, error);
+			quoted = g_shell_quote (g_strstrip (sub_result));
+			g_string_append (command_line, quoted);
+
+			g_free (quoted);
+			g_free (sub_result);
+		}
+		else
+			g_string_append (command_line, a[i]);
+	}
+
+	replace_data->quote_values = TRUE;
+	result = g_regex_replace_eval (re, command_line->str, -1, 0, 0, command_line_eval_cb, replace_data, error);
+
+	g_free (replace_data);
+	g_string_free (command_line, TRUE);
+	g_regex_unref (qre);
+	g_regex_unref (re);
+
+	return result;
+}
diff --git a/extensions/list_tools/gth-script.h b/extensions/list_tools/gth-script.h
index b812207..765effd 100644
--- a/extensions/list_tools/gth-script.h
+++ b/extensions/list_tools/gth-script.h
@@ -51,16 +51,19 @@ 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 */);
+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);
+char *            gth_script_get_requested_attributes  (GthScript  *script);
+char *            gth_script_get_command_line          (GthScript  *script,
+						        GtkWindow  *parent,
+						        GList      *file_list /* GthFileData */,
+						        GError    **error);
 
 G_END_DECLS
 
diff --git a/gthumb/glib-utils.c b/gthumb/glib-utils.c
index c56559e..1ded506 100644
--- a/gthumb/glib-utils.c
+++ b/gthumb/glib-utils.c
@@ -1860,6 +1860,28 @@ _g_file_append_path (GFile      *file,
 
 
 gboolean
+_g_file_attributes_matches (const char *attributes,
+			    const char *mask)
+{
+	GFileAttributeMatcher *matcher;
+	gboolean               matches;
+
+	matcher = g_file_attribute_matcher_new (mask);
+
+	matches = g_file_attribute_matcher_matches (matcher, attributes);
+	if (! matches) {
+		g_file_attribute_matcher_unref (matcher);
+		matcher = g_file_attribute_matcher_new (attributes);
+		matches = g_file_attribute_matcher_matches (matcher, mask);
+	}
+
+	g_file_attribute_matcher_unref (matcher);
+
+	return matches;
+}
+
+
+gboolean
 _g_mime_type_is_image (const char *mime_type)
 {
 	g_return_val_if_fail (mime_type != NULL, FALSE);
diff --git a/gthumb/glib-utils.h b/gthumb/glib-utils.h
index b1ace60..7255b1b 100644
--- a/gthumb/glib-utils.h
+++ b/gthumb/glib-utils.h
@@ -229,7 +229,8 @@ GFile *         _g_file_append_prefix            (GFile      *file,
 						  const char *prefix);
 GFile *         _g_file_append_path              (GFile      *file,
 						  const char *path);
-
+gboolean        _g_file_attributes_matches       (const char *attributes,
+						  const char *mask);
 gboolean        _g_mime_type_is_image            (const char *mime_type);
 gboolean        _g_mime_type_is_video            (const char *mime_type);
 gboolean        _g_mime_type_is_audio            (const char *mime_type);
diff --git a/gthumb/gnome-desktop-thumbnail.c b/gthumb/gnome-desktop-thumbnail.c
index ca81ad5..7cdbf95 100644
--- a/gthumb/gnome-desktop-thumbnail.c
+++ b/gthumb/gnome-desktop-thumbnail.c
@@ -865,12 +865,12 @@ gnome_desktop_thumbnail_factory_generate_thumbnail (GnomeDesktopThumbnailFactory
 
 
 gboolean
-gnome_desktop_thumbnail_factory_generate_thumbnail_async (GnomeDesktopThumbnailFactory *factory,
-							  const char            *uri,
-							  const char            *mime_type,
-							  GPid                  *pid,
-							  char                 **tmpname,
-							  GError               **error)
+gnome_desktop_thumbnail_factory_generate_thumbnail_async (GnomeDesktopThumbnailFactory  *factory,
+							  const char                    *uri,
+							  const char                    *mime_type,
+							  GPid                          *pid,
+							  char                         **tmpname,
+							  GError                       **error)
 {
 	gboolean   retval = FALSE;
 	int        size;
diff --git a/gthumb/gth-file-data.c b/gthumb/gth-file-data.c
index e09b24f..1174634 100644
--- a/gthumb/gth-file-data.c
+++ b/gthumb/gth-file-data.c
@@ -24,7 +24,9 @@
 #include <string.h>
 #include "glib-utils.h"
 #include "gth-duplicable.h"
+#include "gth-metadata.h"
 #include "gth-file-data.h"
+#include "gth-string-list.h"
 
 
 #define GTH_FILE_DATA_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GTH_TYPE_FILE_DATA, GthFileDataPrivate))
@@ -293,6 +295,22 @@ gth_file_data_update_all (GthFileData *fd,
 
 
 GList*
+gth_file_data_list_dup (GList *list)
+{
+	GList *result = NULL;
+	GList *scan;
+
+	for (scan = list; scan; scan = scan->next) {
+		GthFileData *file_data = scan->data;
+
+		result = g_list_prepend (result, gth_file_data_dup (file_data));
+	}
+
+	return g_list_reverse (result);
+}
+
+
+GList*
 gth_file_data_list_from_uri_list (GList *list)
 {
 	GList *result = NULL;
@@ -420,3 +438,41 @@ gth_file_data_ready_with_error (GthFileData     *file_data,
 	data->error = error;
 	data->id = g_idle_add (exec_ready_func, data);
 }
+
+
+char *
+gth_file_data_get_attribute_as_string (GthFileData *file_data,
+				       const char  *id)
+{
+	char     *value;
+	GObject  *obj;
+
+	switch (g_file_info_get_attribute_type (file_data->info, id)) {
+	case G_FILE_ATTRIBUTE_TYPE_OBJECT:
+		obj = g_file_info_get_attribute_object (file_data->info, id);
+		if (GTH_IS_METADATA (obj))
+			g_object_get (obj, "formatted", &value, NULL);
+		else if (GTH_IS_STRING_LIST (obj)) {
+			GList   *list;
+			GList   *scan;
+			GString *str;
+
+			list = gth_string_list_get_list (GTH_STRING_LIST (obj));
+			str = g_string_new ("");
+			for (scan = list; scan; scan = scan->next) {
+				if (scan != list)
+					g_string_append (str, " ");
+				g_string_append (str, (char *) scan->data);
+			}
+			value = g_string_free (str, FALSE);
+		}
+		else
+			value = g_file_info_get_attribute_as_string (file_data->info, id);
+		break;
+	default:
+		value = g_file_info_get_attribute_as_string (file_data->info, id);
+		break;
+	}
+
+	return value;
+}
diff --git a/gthumb/gth-file-data.h b/gthumb/gth-file-data.h
index 51499c1..d5949a1 100644
--- a/gthumb/gth-file-data.h
+++ b/gthumb/gth-file-data.h
@@ -66,42 +66,45 @@ typedef struct {
 	GthFileDataCompFunc  cmp_func;
 } GthFileDataSort;
 
-GType         gth_file_data_get_type               (void);
-GthFileData * gth_file_data_new                    (GFile          *file,
-						    GFileInfo      *info);
-GthFileData * gth_file_data_new_for_uri            (const char     *uri,
-						    const char     *mime_type);
-GthFileData * gth_file_data_dup                    (GthFileData    *self);
-void          gth_file_data_set_file               (GthFileData    *self,
-						    GFile          *file);
-void          gth_file_data_set_info               (GthFileData    *self,
-						    GFileInfo      *info);
-void          gth_file_data_set_mime_type          (GthFileData    *self,
-						    const char     *mime_type);
-const char *  gth_file_data_get_mime_type          (GthFileData    *self);
-const char *  gth_file_data_get_filename_sort_key  (GthFileData    *self);
-time_t        gth_file_data_get_mtime              (GthFileData    *self);
-GTimeVal *    gth_file_data_get_modification_time  (GthFileData    *self);
-gboolean      gth_file_data_is_readable            (GthFileData    *self);
-void          gth_file_data_update_info            (GthFileData    *self,
-						    const char     *attributes);
-void          gth_file_data_update_mime_type       (GthFileData    *self,
-						    gboolean        fast);
-void          gth_file_data_update_all             (GthFileData    *self,
-						    gboolean        fast);
+GType         gth_file_data_get_type                (void);
+GthFileData * gth_file_data_new                     (GFile          *file,
+						     GFileInfo      *info);
+GthFileData * gth_file_data_new_for_uri             (const char     *uri,
+						     const char     *mime_type);
+GthFileData * gth_file_data_dup                     (GthFileData    *self);
+void          gth_file_data_set_file                (GthFileData    *self,
+						     GFile          *file);
+void          gth_file_data_set_info                (GthFileData    *self,
+						     GFileInfo      *info);
+void          gth_file_data_set_mime_type           (GthFileData    *self,
+						     const char     *mime_type);
+const char *  gth_file_data_get_mime_type           (GthFileData    *self);
+const char *  gth_file_data_get_filename_sort_key   (GthFileData    *self);
+time_t        gth_file_data_get_mtime               (GthFileData    *self);
+GTimeVal *    gth_file_data_get_modification_time   (GthFileData    *self);
+gboolean      gth_file_data_is_readable             (GthFileData    *self);
+void          gth_file_data_update_info             (GthFileData    *self,
+						     const char     *attributes);
+void          gth_file_data_update_mime_type        (GthFileData    *self,
+						     gboolean        fast);
+void          gth_file_data_update_all              (GthFileData    *self,
+						     gboolean        fast);
 
-GList*        gth_file_data_list_from_uri_list     (GList          *list);
-GList*        gth_file_data_list_to_uri_list       (GList          *list);
-GList*        gth_file_data_list_to_file_list      (GList          *list);
-GList*        gth_file_data_list_find_file         (GList          *list,
-						    GFile          *file);
-GList*        gth_file_data_list_find_uri          (GList          *list,
-						    const char     *uri);
+GList*        gth_file_data_list_dup                (GList          *list);
+GList*        gth_file_data_list_from_uri_list      (GList          *list);
+GList*        gth_file_data_list_to_uri_list        (GList          *list);
+GList*        gth_file_data_list_to_file_list       (GList          *list);
+GList*        gth_file_data_list_find_file          (GList          *list,
+						     GFile          *file);
+GList*        gth_file_data_list_find_uri           (GList          *list,
+						     const char     *uri);
 
-void          gth_file_data_ready_with_error       (GthFileData    *file_data,
-						    GthFileDataFunc ready_func,
-						    gpointer        ready_data,
-						    GError         *error);
+void          gth_file_data_ready_with_error        (GthFileData    *file_data,
+						     GthFileDataFunc ready_func,
+						     gpointer        ready_data,
+						     GError         *error);
+char *        gth_file_data_get_attribute_as_string (GthFileData    *file_data,
+				                     const char     *id);
 
 G_END_DECLS
 
diff --git a/gthumb/gth-file-properties.c b/gthumb/gth-file-properties.c
index d0d710f..84ca908 100644
--- a/gthumb/gth-file-properties.c
+++ b/gthumb/gth-file-properties.c
@@ -109,9 +109,7 @@ gth_file_properties_real_set_file (GthPropertyView *self,
 	for (i = 0; i < metadata_info->len; i++) {
 		GthMetadataInfo     *info;
 		GthMetadataCategory *category;
-		GObject             *obj;
 		char                *value;
-		char                *tmp_value;
 		char                *tooltip;
 		GtkTreeIter          iter;
 
@@ -124,40 +122,19 @@ gth_file_properties_real_set_file (GthPropertyView *self,
 
 		category = gth_main_get_metadata_category (info->category);
 
-		switch (g_file_info_get_attribute_type (file_data->info, info->id)) {
-		case G_FILE_ATTRIBUTE_TYPE_OBJECT:
-			obj = g_file_info_get_attribute_object (file_data->info, info->id);
-			if (GTH_IS_METADATA (obj))
-				g_object_get (obj, "formatted", &value, NULL);
-			else if (GTH_IS_STRING_LIST (obj)) {
-				GList   *list;
-				GList   *scan;
-				GString *str;
-
-				list = gth_string_list_get_list (GTH_STRING_LIST (obj));
-				str = g_string_new ("");
-				for (scan = list; scan; scan = scan->next) {
-					if (scan != list)
-						g_string_append (str, " ");
-					g_string_append (str, (char *) scan->data);
-				}
-				value = g_string_free (str, FALSE);
-			}
-			else
-				value = g_file_info_get_attribute_as_string (file_data->info, info->id);
+		value = gth_file_data_get_attribute_as_string (file_data, info->id);
+		if (value != NULL) {
+			char *tmp_value;
+
 			tmp_value = _g_utf8_replace (value, "[\r\n]", " ");
 			g_free (value);
 			value = tmp_value;
-			break;
-		default:
-			value = g_file_info_get_attribute_as_string (file_data->info, info->id);
-			break;
 		}
 
 		if ((value == NULL) || (*value == '\0'))
 			continue;
 
-		tooltip = g_markup_printf_escaped ("%s: %s", info->display_name /*info->id*/, value);
+		tooltip = g_markup_printf_escaped ("%s: %s", /*info->display_name*/ info->id, value);
 
 		if (g_hash_table_lookup (category_root, category->id) == NULL) {
 			GtkTreeIter parent;
diff --git a/gthumb/gth-metadata-provider.c b/gthumb/gth-metadata-provider.c
index e17da12..4f31f18 100644
--- a/gthumb/gth-metadata-provider.c
+++ b/gthumb/gth-metadata-provider.c
@@ -199,19 +199,15 @@ static gboolean
 attribute_matches_attributes (const char  *attributes,
 			      char       **attribute_v)
 {
-	GFileAttributeMatcher *matcher;
-	gboolean               matches;
-	int                    i;
+	gboolean matches;
+	int      i;
 
 	if (attributes == NULL)
 		return FALSE;
 
-	matcher = g_file_attribute_matcher_new (attributes);
 	matches = FALSE;
 	for (i = 0; ! matches && (attribute_v[i] != NULL); i++)
-		matches = g_file_attribute_matcher_matches (matcher, attribute_v[i]);
-
-	g_file_attribute_matcher_unref (matcher);
+		matches = _g_file_attributes_matches (attributes, attribute_v[i]);
 
 	return matches;
 }
@@ -285,8 +281,6 @@ query_metadata_done (QueryMetadataData *rmd)
 {
 	QueryMetadataThreadData *rmtd = rmd->rmtd;
 
-	g_thread_join (rmd->thread);
-
 	if (rmd->ready_func != NULL)
 		(*rmd->ready_func) (rmtd->files, rmtd->error, rmd->user_data);
 
@@ -374,7 +368,7 @@ _g_query_metadata_async (GList             *files,       /* GthFileData * list *
 	rmd->ready_func = ready_func;
 	rmd->user_data = user_data;
 	rmd->rmtd = rmtd;
-	rmd->thread = g_thread_create (read_metadata_thread, rmtd, TRUE, NULL);
+	rmd->thread = g_thread_create (read_metadata_thread, rmtd, FALSE, NULL);
 	rmd->check_id = g_timeout_add (CHECK_THREAD_RATE, check_read_metadata_thread, rmd);
 }
 
diff --git a/gthumb/gth-thumb-loader.h b/gthumb/gth-thumb-loader.h
index ff624c5..7842499 100644
--- a/gthumb/gth-thumb-loader.h
+++ b/gthumb/gth-thumb-loader.h
@@ -54,7 +54,8 @@ struct _GthThumbLoaderClass
 
 	/* -- Signals -- */
 
-	void (* ready) (GthThumbLoader *il);
+	void (* ready) (GthThumbLoader *il,
+			GError         *error);
 };
 
 GType            gth_thumb_loader_get_type           (void);
@@ -62,7 +63,7 @@ GthThumbLoader * gth_thumb_loader_new                (int             width,
 					              int             height);
 void             gth_thumb_loader_set_thumb_size     (GthThumbLoader *tl,
 					              int             width,
-					              int             height);					   
+					              int             height);
 void             gth_thumb_loader_use_cache          (GthThumbLoader *tl,
 					              gboolean        use);
 void             gth_thumb_loader_save_thumbnails    (GthThumbLoader *tl,
diff --git a/gthumb/gthumb-error.h b/gthumb/gthumb-error.h
index 67c84c2..894230d 100644
--- a/gthumb/gthumb-error.h
+++ b/gthumb/gthumb-error.h
@@ -28,6 +28,7 @@
 G_BEGIN_DECLS
 
 typedef enum {
+	GTH_ERROR_CANCELLED,
 	GTH_ERROR_GENERIC,
 	GTH_ERROR_EXTENSION_DEPENDENCY
 } GthErrorCode;
diff --git a/tests/glib-utils-test.c b/tests/glib-utils-test.c
index b3c3496..12df23e 100644
--- a/tests/glib-utils-test.c
+++ b/tests/glib-utils-test.c
@@ -29,7 +29,7 @@ static void
 test_g_rand_string (void)
 {
 	char *id;
-	
+
 	id = _g_rand_string (8);
 	g_assert_cmpint (strlen (id), == , 8);
 	g_free (id);
@@ -40,14 +40,45 @@ test_g_rand_string (void)
 }
 
 
-int 
+static void
+test_regexp (void)
+{
+	GRegex   *re;
+	char    **a;
+	int       i, n, j;
+	char    **b;
+	char     *attributes;
+
+	re = g_regex_new ("%prop\\{([^}]+)\\}", 0, 0, NULL);
+	a = g_regex_split (re, "thunderbird -compose %prop{ Exif::Image::DateTime } %ask %prop{ File::Size }", 0);
+	for (i = 0, n = 0; a[i] != NULL; i += 2)
+		n++;
+
+	b = g_new (char *, n + 1);
+	for (i = 1, j = 0; a[i] != NULL; i += 2) {
+		b[j++] = g_strstrip (a[i]);
+	}
+	b[j] = NULL;
+	attributes = g_strjoinv (",", b);
+	g_print ("==> %s\n", attributes);
+
+	g_free (attributes);
+	g_free (b);
+	g_strfreev (a);
+	g_regex_unref (re);
+}
+
+
+int
 main (int   argc,
       char *argv[])
 {
 	g_type_init ();
 	g_test_init (&argc, &argv, NULL);
-	
+
 	g_test_add_func ("/glib-utils/_g_rand_string/1", test_g_rand_string);
-	 
+
+	test_regexp ();
+
 	return g_test_run ();
 }



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