Index: libeog/eog-job-queue.c
===================================================================
RCS file: /cvs/gnome/eog/libeog/Attic/eog-job-queue.c,v
retrieving revision 1.1.2.4
diff -u -r1.1.2.4 eog-job-queue.c
--- libeog/eog-job-queue.c	9 Oct 2006 13:14:59 -0000	1.1.2.4
+++ libeog/eog-job-queue.c	25 Dec 2006 14:54:31 -0000
@@ -32,6 +32,7 @@
 static GQueue *load_queue = NULL;
 static GQueue *model_queue = NULL;
 static GQueue *transform_queue = NULL;
+static GQueue *save_queue = NULL;
 
 static gboolean
 remove_job_from_queue (GQueue *queue, EogJob *job)
@@ -79,6 +80,8 @@
 		eog_job_model_run (EOG_JOB_MODEL (job));
 	else if (EOG_IS_JOB_TRANSFORM (job))
 		eog_job_transform_run (EOG_JOB_TRANSFORM (job));
+	else if (EOG_IS_JOB_SAVE (job))
+		eog_job_save_run (EOG_JOB_SAVE (job));
 
 	g_idle_add_full (G_PRIORITY_DEFAULT_IDLE,
 			 (GSourceFunc) notify_finished,
@@ -92,7 +95,8 @@
 	return  g_queue_is_empty (load_queue)      &&
 		g_queue_is_empty (transform_queue) &&
 		g_queue_is_empty (thumbnail_queue) &&
-		g_queue_is_empty (model_queue);
+		g_queue_is_empty (model_queue) &&
+		g_queue_is_empty (save_queue);
 }
 
 static EogJob *
@@ -115,6 +119,10 @@
 	job = (EogJob *) g_queue_pop_head (model_queue);
 	if (job)
 		return job;
+	
+	job = (EogJob *) g_queue_pop_head (save_queue);
+	if (job)
+		return job;
 
 	return NULL;
 }
@@ -157,6 +165,7 @@
 	load_queue = g_queue_new ();
 	model_queue = g_queue_new ();
 	transform_queue = g_queue_new ();
+	save_queue = g_queue_new ();
 
 	g_thread_create (eog_render_thread, NULL, FALSE, NULL);
 }
@@ -172,6 +181,8 @@
 		return model_queue;
 	} else if (EOG_IS_JOB_TRANSFORM (job)) {
 		return transform_queue;
+	} else if (EOG_IS_JOB_SAVE (job)) {
+		return save_queue;
 	}
 
 	g_assert_not_reached ();
@@ -212,6 +223,8 @@
 		retval = remove_job_from_queue (model_queue, job);
 	} else if (EOG_IS_JOB_TRANSFORM (job)) {
 		retval = remove_job_from_queue (transform_queue, job);
+	} else if (EOG_IS_JOB_SAVE (job)) {
+		retval = remove_job_from_queue (save_queue, job);
 	} else {
 		g_assert_not_reached ();
 	}
Index: libeog/eog-jobs.c
===================================================================
RCS file: /cvs/gnome/eog/libeog/Attic/eog-jobs.c,v
retrieving revision 1.1.2.4
diff -u -r1.1.2.4 eog-jobs.c
--- libeog/eog-jobs.c	5 Oct 2006 23:14:09 -0000	1.1.2.4
+++ libeog/eog-jobs.c	25 Dec 2006 14:54:31 -0000
@@ -22,6 +22,7 @@
  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  */
 
+#include "eog-uri-converter.h"
 #include "eog-jobs.h"
 #include "eog-job-queue.h"
 #include "eog-image.h"
@@ -29,6 +30,7 @@
 #include "eog-list-store.h"
 #include "eog-thumbnail.h"
 #include "eog-thumb-shadow.h"
+#include "eog-pixbuf-util.h"
 
 #include <libgnomeui/gnome-thumbnail.h>
 
@@ -40,6 +42,8 @@
 G_DEFINE_TYPE (EogJobLoad, eog_job_load, EOG_TYPE_JOB);
 G_DEFINE_TYPE (EogJobModel, eog_job_model, EOG_TYPE_JOB);
 G_DEFINE_TYPE (EogJobTransform, eog_job_transform, EOG_TYPE_JOB);
+G_DEFINE_TYPE (EogJobSave, eog_job_save, EOG_TYPE_JOB);
+G_DEFINE_TYPE (EogJobSaveAs, eog_job_save_as, EOG_TYPE_JOB_SAVE);
 
 enum
 {
@@ -48,6 +52,9 @@
 	SIGNAL_LAST_SIGNAL
 };
 
+static void eog_job_save_run_private (EogJobSave *job);
+static void eog_job_save_as_run_private (EogJobSave *job);
+
 static guint job_signals[SIGNAL_LAST_SIGNAL] = { 0 };
 
 static void eog_job_init (EogJob *job) 
@@ -247,6 +254,57 @@
 	oclass->dispose = eog_job_transform_dispose;
 }
 
+static void eog_job_save_init (EogJobSave *job) { /* do nothing */ }
+
+static void
+eog_job_save_dispose (GObject *object)
+{
+	EogJobSave *job;
+
+	job = EOG_JOB_SAVE (object);
+
+	if (job->images) {
+		g_list_foreach (job->images, (GFunc) g_object_unref, NULL);
+		g_list_free (job->images);
+		job->images = NULL;
+	}
+
+	(* G_OBJECT_CLASS (eog_job_save_parent_class)->dispose) (object);
+}
+
+static void
+eog_job_save_class_init (EogJobSaveClass *class)
+{
+	G_OBJECT_CLASS (class)->dispose = eog_job_save_dispose;
+        class->run = eog_job_save_run_private;
+}
+
+static void eog_job_save_as_init (EogJobSaveAs *job) { /* do nothing */ }
+
+static void eog_job_save_as_dispose (GObject *object)
+{
+	EogJobSaveAs *job = EOG_JOB_SAVE_AS (object);
+
+	if (job->converter != NULL) {
+		g_object_unref (job->converter);
+		job->converter = NULL;
+	}
+	
+	if (job->uri != NULL) {
+		gnome_vfs_uri_unref (job->uri);
+		job->uri = NULL;
+	}
+
+	(* G_OBJECT_CLASS (eog_job_save_as_parent_class)->dispose) (object);
+}
+
+static void
+eog_job_save_as_class_init (EogJobSaveAsClass *class)
+{
+	G_OBJECT_CLASS (class)->dispose = eog_job_save_as_dispose;
+        EOG_JOB_SAVE_CLASS (class)->run = eog_job_save_as_run_private;
+}
+
 EogJob *
 eog_job_thumbnail_new (GnomeVFSURI *uri_entry)
 {
@@ -494,3 +552,132 @@
 	EOG_JOB (job)->finished = TRUE;
 }
 
+EogJob *
+eog_job_save_new (GList *images)
+{
+	EogJobSave *job;
+
+	job = g_object_new (EOG_TYPE_JOB_SAVE, NULL);
+
+	job->images = images;
+
+	return EOG_JOB (job);
+}
+
+static void
+eog_job_save_run_private (EogJobSave *job)
+{
+	GList *it;
+	
+	for (it = job->images; it != NULL; it = it->next) {
+		EogImage *image = EOG_IMAGE (it->data);
+		EogImageSaveInfo *save_info = NULL;
+		gboolean success = FALSE;
+
+		/* Make sure the image doesn't go away while saving */
+		eog_image_data_ref (image);
+
+		if (!eog_image_has_data (image, EOG_IMAGE_DATA_ALL)) {
+			/* This hopefully doesn't create new race conditions */
+			eog_image_load (image, EOG_IMAGE_DATA_ALL,
+					EOG_JOB (job), &EOG_JOB (job)->error);
+		}
+
+		save_info = eog_image_save_info_from_image (image);
+		success = eog_image_save_by_info (image, save_info, &EOG_JOB (job)->error);
+
+		if (save_info)
+			g_object_unref (save_info);
+
+		eog_image_data_unref (image);
+
+		if (!success)
+			break;
+	}
+
+
+	EOG_JOB (job)->finished = TRUE;
+}
+
+void
+eog_job_save_run (EogJobSave *job)
+{
+	EOG_JOB_SAVE_GET_CLASS (job)->run(job);
+}
+
+EogJob *
+eog_job_save_as_new (GList *images, EogURIConverter *converter, GnomeVFSURI *uri)
+{
+	EogJobSaveAs *job;
+
+	g_assert (converter != NULL || g_list_length (images) == 1);
+
+	job = g_object_new (EOG_TYPE_JOB_SAVE_AS, NULL);
+	EOG_JOB_SAVE(job)->images = images;
+	job->converter = (converter) ? g_object_ref (converter) : NULL;
+	job->uri = (uri) ? gnome_vfs_uri_ref (uri) : NULL;
+
+	return EOG_JOB (job);
+}
+
+static void
+eog_job_save_as_run_private (EogJobSave *job)
+{
+	EogJobSaveAs *saveas_job;
+	GList *it;
+	guint n_images = g_list_length (job->images);
+	
+
+	g_assert (EOG_IS_JOB_SAVE_AS (job));
+	
+	saveas_job = EOG_JOB_SAVE_AS (job);
+
+	for (it = job->images; it != NULL; it = it->next) {
+		GdkPixbufFormat *format;
+		EogImageSaveInfo *src_info, *dest_info;
+		EogImage *image = EOG_IMAGE (it->data);
+		gboolean success = FALSE;
+		
+		eog_image_data_ref (image);
+
+		if (!eog_image_has_data (image, EOG_IMAGE_DATA_ALL)) {
+			/* This hopefully doesn't create new race conditions */
+			eog_image_load (image, EOG_IMAGE_DATA_ALL,
+					EOG_JOB (job), &EOG_JOB (job)->error);
+		}
+
+		g_assert (EOG_JOB (job)->error == NULL);
+
+		src_info = eog_image_save_info_from_image (image);
+		if (n_images == 1) {
+			g_assert (saveas_job->uri != NULL);
+			format = eog_pixbuf_get_format_by_vfs_uri (saveas_job->uri);
+			dest_info = eog_image_save_info_from_vfs_uri (saveas_job->uri, format);
+			success = eog_image_save_as_by_info (image, src_info, dest_info, &EOG_JOB (job)->error);
+		} else {
+			GnomeVFSURI *dest_uri;
+			gboolean result;
+
+			result = eog_uri_converter_do (saveas_job->converter,
+							image, &dest_uri,
+							&format, NULL);
+			g_assert (result);
+
+			dest_info = eog_image_save_info_from_vfs_uri (dest_uri, format);
+			success = eog_image_save_as_by_info (image, src_info, dest_info, &EOG_JOB (job)->error);
+
+		}
+		
+		if (src_info)
+			g_object_unref (src_info);
+		if (dest_info)
+			g_object_unref (dest_info);
+
+		eog_image_data_unref (image);
+		
+		if (!success)
+			break;
+	}
+
+	EOG_JOB (job)->finished = TRUE;
+}
Index: libeog/eog-jobs.h
===================================================================
RCS file: /cvs/gnome/eog/libeog/Attic/eog-jobs.h,v
retrieving revision 1.1.2.3
diff -u -r1.1.2.3 eog-jobs.h
--- libeog/eog-jobs.h	7 Jun 2006 03:57:06 -0000	1.1.2.3
+++ libeog/eog-jobs.h	25 Dec 2006 14:54:31 -0000
@@ -39,6 +39,12 @@
   typedef struct _EogImage EogImage;
 #endif
 
+#ifndef __EOG_URI_CONVERTER_DECLR__
+#define __EOG_URI_CONVERTER_DECLR__
+typedef struct _EogURIConverter EogURIConverter;
+#endif
+
+
 typedef struct _EogJob EogJob;
 typedef struct _EogJobClass EogJobClass;
 
@@ -54,6 +60,12 @@
 typedef struct _EogJobTransform EogJobTransform;
 typedef struct _EogJobTransformClass EogJobTransformClass;
 
+typedef struct _EogJobSave EogJobSave;
+typedef struct _EogJobSaveClass EogJobSaveClass;
+
+typedef struct _EogJobSaveAs EogJobSaveAs;
+typedef struct _EogJobSaveAsClass EogJobSaveAsClass;
+
 #define EOG_TYPE_JOB		       (eog_job_get_type())
 #define EOG_JOB(obj)		       (G_TYPE_CHECK_INSTANCE_CAST((obj), EOG_TYPE_JOB, EogJob))
 #define EOG_JOB_CLASS(klass)	       (G_TYPE_CHECK_CLASS_CAST((klass),  EOG_TYPE_JOB, EogJobClass))
@@ -79,6 +91,17 @@
 #define EOG_JOB_TRANSFORM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),  EOG_TYPE_JOB_TRANSFORM, EogJobTransformClass))
 #define EOG_IS_JOB_TRANSFORM(obj)      (G_TYPE_CHECK_INSTANCE_TYPE((obj), EOG_TYPE_JOB_TRANSFORM))
 
+#define EOG_TYPE_JOB_SAVE              (eog_job_save_get_type())
+#define EOG_JOB_SAVE(obj)              (G_TYPE_CHECK_INSTANCE_CAST((obj), EOG_TYPE_JOB_SAVE, EogJobSave))
+#define EOG_JOB_SAVE_CLASS(klass)      (G_TYPE_CHECK_CLASS_CAST((klass), EOG_TYPE_JOB_SAVE, EogJobSaveClass))
+#define EOG_IS_JOB_SAVE(obj)           (G_TYPE_CHECK_INSTANCE_TYPE((obj), EOG_TYPE_JOB_SAVE))
+#define EOG_JOB_SAVE_GET_CLASS(obj)    (G_TYPE_INSTANCE_GET_CLASS ((obj), EOG_TYPE_JOB_SAVE, EogJobSaveClass))
+
+#define EOG_TYPE_JOB_SAVE_AS           (eog_job_save_as_get_type())
+#define EOG_JOB_SAVE_AS(obj)           (G_TYPE_CHECK_INSTANCE_CAST((obj), EOG_TYPE_JOB_SAVE_AS, EogJobSaveAs))
+#define EOG_JOB_SAVE_AS_CLASS(klass)   (G_TYPE_CHECK_CLASS_CAST((klass), EOG_TYPE_JOB_SAVE_AS, EogJobSaveAsClass))
+#define EOG_IS_JOB_SAVE_AS(obj)        (G_TYPE_CHECK_INSTANCE_TYPE((obj), EOG_TYPE_JOB_SAVE_AS))
+
 struct _EogJob
 {
 	GObject  parent;
@@ -144,6 +167,41 @@
         EogJobClass parent_class;
 };
 
+typedef enum {
+	EOG_SAVE_RESPONSE_NONE,
+	EOG_SAVE_RESPONSE_RETRY,
+	EOG_SAVE_RESPONSE_SKIP,
+	EOG_SAVE_RESPONSE_OVERWRITE,
+	EOG_SAVE_RESPONSE_CANCEL,
+	EOG_SAVE_RESPONSE_LAST
+} EogJobSaveResponse;
+
+struct _EogJobSave
+{
+	EogJob   parent;
+	GList	 *images;
+};
+
+struct _EogJobSaveClass
+{
+	EogJobClass parent_class;
+
+        void (*run) (EogJobSave *job);
+	
+};
+
+struct _EogJobSaveAs
+{
+	EogJobSave      parent;
+	EogURIConverter *converter;
+	GnomeVFSURI     *uri;
+};
+
+struct _EogJobSaveAsClass
+{
+	EogJobSaveClass parent;
+};
+
 /* Base job class */
 GType           eog_job_get_type           (void);
 void            eog_job_finished           (EogJob          *job);
@@ -170,6 +228,17 @@
 EogJob 	       *eog_job_transform_new      (GList           *images,
 					    EogTransform    *trans);
 void            eog_job_transform_run      (EogJobTransform *model);
+
+/* EogJobSave */
+GType		eog_job_save_get_type      (void);
+EogJob         *eog_job_save_new           (GList           *images);
+void            eog_job_save_run           (EogJobSave      *job);
+
+/* EogJobSaveAs */
+GType		eog_job_save_as_get_type   (void);
+EogJob         *eog_job_save_as_new        (GList           *images,
+					    EogURIConverter *converter,
+					    GnomeVFSURI     *uri);
 
 G_END_DECLS
 
Index: libeog/eog-uri-converter.h
===================================================================
RCS file: /cvs/gnome/eog/libeog/eog-uri-converter.h,v
retrieving revision 1.4.4.1
diff -u -r1.4.4.1 eog-uri-converter.h
--- libeog/eog-uri-converter.h	2 Dec 2006 10:51:06 -0000	1.4.4.1
+++ libeog/eog-uri-converter.h	25 Dec 2006 14:54:31 -0000
@@ -14,7 +14,10 @@
 #define EOG_IS_URI_CONVERTER_CLASS(k)   (G_TYPE_CHECK_CLASS_TYPE ((k), EOG_TYPE_URI_CONVERTER))
 #define EOG_URI_CONVERTER_GET_CLASS(o)  (G_TYPE_INSTANCE_GET_CLASS ((o), EOG_TYPE_URI_CONVERTER, EogURIConverterClass))
 
+#ifndef __EOG_URI_CONVERTER_DECLR__
+#define __EOG_URI_CONVERTER_DECLR__
 typedef struct _EogURIConverter EogURIConverter;
+#endif
 typedef struct _EogURIConverterClass EogURIConverterClass;
 typedef struct _EogURIConverterPrivate EogURIConverterPrivate;
 
Index: shell/eog-window.c
===================================================================
RCS file: /cvs/gnome/eog/shell/eog-window.c,v
retrieving revision 1.189.2.39
diff -u -r1.189.2.39 eog-window.c
--- shell/eog-window.c	21 Dec 2006 18:17:40 -0000	1.189.2.39
+++ shell/eog-window.c	25 Dec 2006 14:54:32 -0000
@@ -46,6 +46,7 @@
 #include "eog-jobs.h"
 #include "eog-util.h"
 #include "eog-thumbnail.h"
+#include "eog-save-as-dialog-helper.h"
 
 #include <gconf/gconf-client.h>
 #include <glib.h>
@@ -113,6 +114,7 @@
 
         EogJob              *load_job;
         EogJob              *transform_job;
+	gboolean             save_job_active;
 
         guint                image_info_message_cid;
         guint                tip_message_cid;
@@ -1531,15 +1533,122 @@
 }
 
 static void
+eog_job_save_cb (EogJobSave *job, gpointer user_data)
+{
+	EogWindow *window = EOG_WINDOW (user_data);
+
+	g_signal_handlers_disconnect_by_func (job, eog_job_save_cb, window);
+	window->priv->save_job_active = FALSE;
+}
+
+static void
 eog_window_cmd_save (GtkAction *action, gpointer user_data)
 {
-	g_print ("Not implemented yet!\n");
+	GList *images;
+	EogJob *save_job;
+	EogWindow *window = EOG_WINDOW (user_data);
+
+	if (window->priv->save_job_active)
+		return;
+	window->priv->save_job_active = TRUE;
+	images = eog_thumb_view_get_selected_images (EOG_THUMB_VIEW (window->priv->thumbview));
+	save_job = eog_job_save_new (images);
+	g_signal_connect (save_job, "finished",
+			  G_CALLBACK (eog_job_save_cb), window);
+	eog_job_queue_add_job (save_job);
+	g_object_unref (save_job);
 }
 
+static GnomeVFSURI*
+eog_window_retrieve_save_as_uri (EogWindow *window, EogImage *image)
+{
+	GtkWidget *dialog;
+	GnomeVFSURI *save_uri = NULL;
+	GnomeVFSURI *parent_uri;
+	GnomeVFSURI *image_uri;
+	gchar *folder_uri;
+	gint response;
+
+	g_assert (image != NULL);
+
+	image_uri = eog_image_get_uri (image);
+	parent_uri = gnome_vfs_uri_get_parent (image_uri);
+	folder_uri = gnome_vfs_uri_to_string (parent_uri, GNOME_VFS_URI_HIDE_NONE);
+	gnome_vfs_uri_unref (parent_uri);
+	gnome_vfs_uri_unref (image_uri);
+
+	dialog = eog_file_chooser_new (GTK_FILE_CHOOSER_ACTION_SAVE);
+	gtk_file_chooser_set_current_folder_uri (GTK_FILE_CHOOSER (dialog),
+						 folder_uri);
+	response = gtk_dialog_run (GTK_DIALOG (dialog));
+	gtk_widget_hide (dialog);
+
+	if (response == GTK_RESPONSE_OK) {
+		gchar *new_uri;
+
+		new_uri = gtk_file_chooser_get_uri (GTK_FILE_CHOOSER (dialog));
+
+		g_assert (new_uri != NULL);
+
+		save_uri = gnome_vfs_uri_new (new_uri);
+		g_free (new_uri);
+	}
+	gtk_widget_destroy (dialog);
+	if (folder_uri)
+		g_free (folder_uri);
+	
+	return save_uri;
+}
 static void
 eog_window_cmd_save_as (GtkAction *action, gpointer user_data)
 {
-	g_print ("Not implemented yet!\n");
+	GList *images;
+        EogJob *save_job;
+        EogWindow *window = EOG_WINDOW (user_data);
+
+	if (window->priv->save_job_active)
+		return;
+	window->priv->save_job_active = TRUE;
+	images = eog_thumb_view_get_selected_images (EOG_THUMB_VIEW (window->priv->thumbview));
+	if (g_list_length (images) == 1) {
+		GnomeVFSURI *uri;
+		
+		uri = eog_window_retrieve_save_as_uri (window, images->data);
+		save_job = eog_job_save_as_new (images, NULL, uri); 
+		gnome_vfs_uri_unref (uri);
+	} else {
+		GnomeVFSURI *baseuri;
+		GtkWidget *dialog;
+		gchar *basedir;
+		EogURIConverter *converter;
+		
+		basedir = g_get_current_dir ();
+		baseuri = gnome_vfs_uri_new (basedir);
+		g_free (basedir);
+
+		dialog = eog_save_as_dialog_new (GTK_WINDOW (window),
+						 images, baseuri);
+		gtk_widget_show_all (dialog);
+		if (gtk_dialog_run (GTK_DIALOG (dialog)) != GTK_RESPONSE_OK) {
+			gnome_vfs_uri_unref (baseuri);
+			gtk_widget_destroy (dialog);
+			window->priv->save_job_active = FALSE;
+			return;
+		}
+		converter = eog_save_as_dialog_get_converter (dialog);
+
+		g_assert (converter != NULL);
+		save_job = eog_job_save_as_new (images, converter, NULL);
+		gtk_widget_destroy (dialog);
+		g_object_unref (converter);
+		gnome_vfs_uri_unref (baseuri);
+	}
+	
+	g_signal_connect (save_job, "finished",
+			  G_CALLBACK (eog_job_save_cb), window);
+	eog_job_queue_add_job (save_job);
+	g_object_unref (save_job);
+
 }
 
 static void
