[gnome-photos] Borrow eog-image.[ch], eog-print.[ch] and friends from Eye of GNOME
- From: Debarshi Ray <debarshir src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-photos] Borrow eog-image.[ch], eog-print.[ch] and friends from Eye of GNOME
- Date: Sat, 8 Sep 2012 17:32:55 +0000 (UTC)
commit 980f9d5fca6836e84c36b5962f96a0229b20890d
Author: Debarshi Ray <debarshir gnome org>
Date: Sat Sep 8 19:25:58 2012 +0200
Borrow eog-image.[ch], eog-print.[ch] and friends from Eye of GNOME
The following changes were made:
+ removed EogJobModel
+ removed EogJobThumbnail
+ removed thumbnailing code from EogImage
+ replaced Eog marshallers with ones from Photos
eog-image-jpeg.[ch] was left out. It will be used if required in the
future.
New dependencies are:
+ exempi-2.0 >= 1.99.5
+ gtk+-unix-print-3.0
+ lcms2
+ libexif >= 0.6.14
+ librsvg-2.0 >= 2.26.0
+ zlib
configure.ac | 37 +
po/POTFILES.in | 5 +
src/Makefile.am | 36 +
src/eog-enums.h | 37 +
src/eog-exif-util.c | 325 ++++++
src/eog-exif-util.h | 59 +
src/eog-image-private.h | 97 ++
src/eog-image-save-info.c | 147 +++
src/eog-image-save-info.h | 57 +
src/eog-image.c | 2475 +++++++++++++++++++++++++++++++++++++++++
src/eog-image.h | 220 ++++
src/eog-job-queue.c | 212 ++++
src/eog-job-queue.h | 42 +
src/eog-jobs.c | 690 ++++++++++++
src/eog-jobs.h | 228 ++++
src/eog-metadata-reader-jpg.c | 671 +++++++++++
src/eog-metadata-reader-jpg.h | 55 +
src/eog-metadata-reader-png.c | 646 +++++++++++
src/eog-metadata-reader-png.h | 55 +
src/eog-metadata-reader.c | 132 +++
src/eog-metadata-reader.h | 112 ++
src/eog-print-image-setup.c | 1139 +++++++++++++++++++
src/eog-print-image-setup.h | 72 ++
src/eog-print.c | 521 +++++++++
src/eog-print.h | 49 +
src/eog-transform.c | 449 ++++++++
src/eog-transform.h | 76 ++
src/eog-uri-converter.c | 988 ++++++++++++++++
src/eog-uri-converter.h | 107 ++
src/eog-util.c | 506 +++++++++
src/eog-util.h | 74 ++
src/photos-marshalers.list | 1 +
32 files changed, 10320 insertions(+), 0 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 47f77eb..ef13ef6 100644
--- a/configure.ac
+++ b/configure.ac
@@ -26,7 +26,10 @@ GLIB_GSETTINGS
CLUTTER_GTK_MIN_VERSION=1.3.2
CLUTTER_MIN_VERSION=1.11
+EXEMPI_MIN_VERSION=1.99.5
GLIB_MIN_VERSION=2.28
+LIBEXIF_MIN_VERSION=0.6.14
+LIBRSVG_MIN_VERSION=2.26.0
GNOME_COMMON_INIT
GNOME_COMPILE_WARNINGS([maximum])
@@ -43,9 +46,39 @@ AM_GLIB_GNU_GETTEXT
LT_INIT
+# ****************************************************************
+# Support for nl_langinfo (_NL_MEASUREMENT_MEASUREMENT) (optional)
+# ****************************************************************
+AC_MSG_CHECKING([for _NL_MEASUREMENT_MEASUREMENT])
+AC_TRY_LINK([#include <langinfo.h>], [
+char c;
+c = *((unsigned char *) nl_langinfo(_NL_MEASUREMENT_MEASUREMENT));
+], have_langinfo_measurement=yes, have_langinfo_measurement=no)
+AC_MSG_RESULT($have_langinfo_measurement)
+if test "$have_langinfo_measurement" = "yes"; then
+ AC_DEFINE([HAVE__NL_MEASUREMENT_MEASUREMENT], [1],
+ [Define if _NL_MEASUREMENT_MEASUREMENT is available])
+fi
+
+# ***************
+# ZLIB (required)
+# ***************
+
+have_zlib=yes
+AC_CHECK_HEADERS([zlib.h],
+ [AC_CHECK_LIB([z], [inflate],
+ [AC_CHECK_LIB([z], [crc32], [], [have_zlib=no])],
+ [have_zlib=no])],
+ [have_zlib=no])
+
+if test x$have_zlib = xno; then
+ AC_MSG_ERROR([No sufficient zlib library found on your system.])
+fi
+
PKG_CHECK_MODULES(CAIRO, [cairo])
PKG_CHECK_MODULES(CLUTTER, [clutter-1.0 >= $CLUTTER_MIN_VERSION])
PKG_CHECK_MODULES(CLUTTER_GTK, [clutter-gtk-1.0 >= $CLUTTER_GTK_MIN_VERSION])
+PKG_CHECK_MODULES(EXEMPI, [exempi-2.0 >= $EXEMPI_MIN_VERSION])
PKG_CHECK_MODULES(GDK_PIXBUF, [gdk-pixbuf-2.0])
PKG_CHECK_MODULES(GLIB, [glib-2.0 >= $GLIB_MIN_VERSION])
PKG_CHECK_MODULES(GIO, [gio-2.0])
@@ -57,6 +90,10 @@ PKG_CHECK_MODULES(GOA, [goa-1.0])
AC_DEFINE([GOA_API_IS_SUBJECT_TO_CHANGE], [], [We are aware that GOA's API can change])
PKG_CHECK_MODULES(GTK, [gtk+-3.0])
+PKG_CHECK_MODULES(GTK_UNIX_PRINT, [gtk+-unix-print-3.0])
+PKG_CHECK_MODULES(LCMS, [lcms2])
+PKG_CHECK_MODULES(LIBEXIF, [libexif >= $LIBEXIF_MIN_VERSION])
+PKG_CHECK_MODULES(LIBRSVG, [librsvg-2.0 >= $LIBRSVG_MIN_VERSION])
PKG_CHECK_MODULES(TRACKER, [tracker-sparql-0.14])
AC_CONFIG_FILES([
diff --git a/po/POTFILES.in b/po/POTFILES.in
index e85a1a3..6a0cdf7 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -2,6 +2,11 @@
# Please keep this list sorted alphabetically.
data/gnome-photos.desktop.in.in
data/org.gnome.photos.gschema.xml.in
+src/eog-exif-util.c
+src/eog-image.c
+src/eog-print-image-setup.c
+src/eog-uri-converter.c
+src/eog-util.c
src/gd-main-toolbar.c
src/photos-application.c
src/photos-load-more-button.c
diff --git a/src/Makefile.am b/src/Makefile.am
index 5ee0d94..3237272 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -15,8 +15,34 @@ gnome_photos_SOURCES = \
$(gnome_photos_built_sources) \
eog-debug.c \
eog-debug.h \
+ eog-enums.h \
+ eog-exif-util.c \
+ eog-exif-util.h \
+ eog-image.c \
+ eog-image.h \
+ eog-image-private.h \
+ eog-image-save-info.c \
+ eog-image-save-info.h \
+ eog-job-queue.c \
+ eog-job-queue.h \
+ eog-jobs.c \
+ eog-jobs.h \
+ eog-metadata-reader.c \
+ eog-metadata-reader.h \
+ eog-metadata-reader-jpg.c \
+ eog-metadata-reader-jpg.h \
+ eog-metadata-reader-png.c \
+ eog-metadata-reader-png.h \
+ eog-print-image-setup.c \
+ eog-print-image-setup.h \
eog-print-preview.c \
eog-print-preview.h \
+ eog-transform.c \
+ eog-transform.h \
+ eog-uri-converter.c \
+ eog-uri-converter.h \
+ eog-util.c \
+ eog-util.h \
gd-main-icon-view.c \
gd-main-icon-view.h \
gd-main-list-view.c \
@@ -124,12 +150,17 @@ AM_CPPFLAGS = \
$(CAIRO_CFLAGS) \
$(CLUTTER_CFLAGS) \
$(CLUTTER_GTK_CFLAGS) \
+ $(EXEMPI_CFLAGS) \
$(GDK_PIXBUF_CFLAGS) \
$(GIO_CFLAGS) \
$(GLIB_CFLAGS) \
$(GNOME_DESKTOP_CFLAGS) \
$(GOA_CFLAGS) \
$(GTK_CFLAGS) \
+ $(GTK_UNIX_PRINT_CFLAGS) \
+ $(LCMS_CFLAGS) \
+ $(LIBEXIF_CFLAGS) \
+ $(LIBRSVG_CFLAGS) \
$(TRACKER_CFLAGS) \
$(NULL)
@@ -139,12 +170,17 @@ gnome_photos_LDADD = \
$(CAIRO_LIBS) \
$(CLUTTER_LIBS) \
$(CLUTTER_GTK_LIBS) \
+ $(EXEMPI_LIBS) \
$(GDK_PIXBUF_LIBS) \
$(GIO_LIBS) \
$(GLIB_LIBS) \
$(GNOME_DESKTOP_LIBS) \
$(GOA_LIBS) \
$(GTK_LIBS) \
+ $(GTK_UNIX_PRINT_LIBS) \
+ $(LCMS_LIBS) \
+ $(LIBEXIF_LIBS) \
+ $(LIBRSVG_LIBS) \
$(TRACKER_LIBS) \
$(LIBM) \
$(NULL)
diff --git a/src/eog-enums.h b/src/eog-enums.h
new file mode 100644
index 0000000..a3e08fa
--- /dev/null
+++ b/src/eog-enums.h
@@ -0,0 +1,37 @@
+/* Eye of GNOME - General enumerations.
+ *
+ * Copyright (C) 2007-2008 The Free Software Foundation
+ *
+ * Author: Lucas Rocha <lucasr gnome org>
+ *
+ * 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 Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __EOG_ENUMS__
+#define __EOG_ENUMS__
+
+typedef enum {
+ EOG_IMAGE_DATA_IMAGE = 1 << 0,
+ EOG_IMAGE_DATA_DIMENSION = 1 << 1,
+ EOG_IMAGE_DATA_EXIF = 1 << 2,
+ EOG_IMAGE_DATA_XMP = 1 << 3
+} EogImageData;
+
+#define EOG_IMAGE_DATA_ALL (EOG_IMAGE_DATA_IMAGE | \
+ EOG_IMAGE_DATA_DIMENSION | \
+ EOG_IMAGE_DATA_EXIF | \
+ EOG_IMAGE_DATA_XMP)
+
+#endif
diff --git a/src/eog-exif-util.c b/src/eog-exif-util.c
new file mode 100644
index 0000000..a11a3a7
--- /dev/null
+++ b/src/eog-exif-util.c
@@ -0,0 +1,325 @@
+/* Eye Of Gnome - EXIF Utilities
+ *
+ * Copyright (C) 2006-2007 The Free Software Foundation
+ *
+ * Author: Lucas Rocha <lucasr gnome org>
+ * Author: Claudio Saavedra <csaavedra alumnos utalca cl>
+ * Author: Felix Riemann <felix hsgheli de>
+ *
+ * Based on code by:
+ * - Jens Finke <jens gnome org>
+ *
+ * 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 Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifdef HAVE_STRPTIME
+#define _XOPEN_SOURCE
+#define _XOPEN_SOURCE_EXTENDED 1
+#endif
+#include <time.h>
+
+#include "eog-exif-util.h"
+#include "eog-util.h"
+
+#include <string.h>
+#include <glib/gi18n.h>
+
+#define DATE_BUF_SIZE 200
+
+/* gboolean <-> gpointer conversion macros taken from gedit */
+#ifndef GBOOLEAN_TO_POINTER
+#define GBOOLEAN_TO_POINTER(i) (GINT_TO_POINTER ((i) ? 2 : 1))
+#endif
+#ifndef GPOINTER_TO_BOOLEAN
+#define GPOINTER_TO_BOOLEAN(i) ((gboolean) ((GPOINTER_TO_INT (i) == 2) ? TRUE : FALSE))
+#endif
+
+typedef ExifData EogExifData;
+
+/* Define EogExifData type */
+G_DEFINE_BOXED_TYPE(EogExifData, eog_exif_data, eog_exif_data_copy, eog_exif_data_free)
+
+static gpointer
+_check_strptime_updates_wday (gpointer data)
+{
+ struct tm tm;
+
+ memset (&tm, '\0', sizeof (tm));
+ strptime ("2008:12:24 20:30:45", "%Y:%m:%d %T", &tm);
+ /* Check if tm.tm_wday is set to Wednesday (3) now */
+ return GBOOLEAN_TO_POINTER (tm.tm_wday == 3);
+}
+
+/**
+ * _calculate_wday_yday:
+ * @tm: A struct tm that should be updated.
+ *
+ * Ensure tm_wday and tm_yday are set correctly in a struct tm.
+ * The other date (dmy) values must have been set already.
+ **/
+static void
+_calculate_wday_yday (struct tm *tm)
+{
+ GDate *exif_date;
+ struct tm tmp_tm;
+
+ exif_date = g_date_new_dmy (tm->tm_mday,
+ tm->tm_mon+1,
+ tm->tm_year+1900);
+
+ g_return_if_fail (exif_date != NULL && g_date_valid (exif_date));
+
+ // Use this to get GLib <-> libc corrected values
+ g_date_to_struct_tm (exif_date, &tmp_tm);
+ g_date_free (exif_date);
+
+ tm->tm_wday = tmp_tm.tm_wday;
+ tm->tm_yday = tmp_tm.tm_yday;
+}
+
+#ifdef HAVE_STRPTIME
+static gchar *
+eog_exif_util_format_date_with_strptime (const gchar *date)
+{
+ static GOnce strptime_updates_wday = G_ONCE_INIT;
+ gchar *new_date = NULL;
+ gchar tmp_date[DATE_BUF_SIZE];
+ gchar *p;
+ gsize dlen;
+ struct tm tm;
+
+ memset (&tm, '\0', sizeof (tm));
+ p = strptime (date, "%Y:%m:%d %T", &tm);
+
+ if (p == date + strlen (date)) {
+ g_once (&strptime_updates_wday,
+ _check_strptime_updates_wday,
+ NULL);
+
+ // Ensure tm.tm_wday and tm.tm_yday are set
+ if (!GPOINTER_TO_BOOLEAN (strptime_updates_wday.retval))
+ _calculate_wday_yday (&tm);
+
+ /* A strftime-formatted string, to display the date the image was taken. */
+ dlen = strftime (tmp_date, DATE_BUF_SIZE * sizeof(gchar), _("%a, %d %B %Y %X"), &tm);
+ new_date = g_strndup (tmp_date, dlen);
+ }
+
+ return new_date;
+}
+#else
+static gchar *
+eog_exif_util_format_date_by_hand (const gchar *date)
+{
+ int year, month, day, hour, minutes, seconds;
+ int result;
+ gchar *new_date = NULL;
+
+ result = sscanf (date, "%d:%d:%d %d:%d:%d",
+ &year, &month, &day, &hour, &minutes, &seconds);
+
+ if (result < 3 || !g_date_valid_dmy (day, month, year)) {
+ return NULL;
+ } else {
+ gchar tmp_date[DATE_BUF_SIZE];
+ gsize dlen;
+ time_t secs;
+ struct tm tm;
+
+ memset (&tm, '\0', sizeof (tm));
+ tm.tm_mday = day;
+ tm.tm_mon = month-1;
+ tm.tm_year = year-1900;
+ // Calculate tm.tm_wday
+ _calculate_wday_yday (&tm);
+
+ if (result < 5) {
+ /* A strftime-formatted string, to display the date the image was taken, for the case we don't have the time. */
+ dlen = strftime (tmp_date, DATE_BUF_SIZE * sizeof(gchar), _("%a, %d %B %Y"), &tm);
+ } else {
+ tm.tm_sec = result < 6 ? 0 : seconds;
+ tm.tm_min = minutes;
+ tm.tm_hour = hour;
+ /* A strftime-formatted string, to display the date the image was taken. */
+ dlen = strftime (tmp_date, DATE_BUF_SIZE * sizeof(gchar), _("%a, %d %B %Y %X"), &tm);
+ }
+
+ if (dlen == 0)
+ return NULL;
+ else
+ new_date = g_strndup (tmp_date, dlen);
+ }
+ return new_date;
+}
+#endif /* HAVE_STRPTIME */
+
+/**
+ * eog_exif_util_format_date:
+ * @date: a date string following Exif specifications
+ *
+ * Takes a date string formatted after Exif specifications and generates a
+ * more readable, possibly localized, string out of it.
+ *
+ * Returns: a newly allocated date string formatted according to the
+ * current locale.
+ */
+gchar *
+eog_exif_util_format_date (const gchar *date)
+{
+ gchar *new_date;
+#ifdef HAVE_STRPTIME
+ new_date = eog_exif_util_format_date_with_strptime (date);
+#else
+ new_date = eog_exif_util_format_date_by_hand (date);
+#endif /* HAVE_STRPTIME */
+ return new_date;
+}
+
+void
+eog_exif_util_set_label_text (GtkLabel *label,
+ EogExifData *exif_data,
+ gint tag_id)
+{
+ gchar exif_buffer[512];
+ const gchar *buf_ptr;
+ gchar *label_text = NULL;
+
+ g_return_if_fail (GTK_IS_LABEL (label));
+
+ if (exif_data) {
+ buf_ptr = eog_exif_data_get_value (exif_data, tag_id,
+ exif_buffer, 512);
+
+ if (tag_id == EXIF_TAG_DATE_TIME_ORIGINAL && buf_ptr)
+ label_text = eog_exif_util_format_date (buf_ptr);
+ else
+ label_text = eog_util_make_valid_utf8 (buf_ptr);
+ }
+
+ gtk_label_set_text (label, label_text);
+ g_free (label_text);
+}
+
+void
+eog_exif_util_set_focal_length_label_text (GtkLabel *label,
+ EogExifData *exif_data)
+{
+ ExifEntry *entry = NULL, *entry35mm = NULL;
+ ExifByteOrder byte_order;
+ gfloat f_val = 0.0;
+ gchar *fl_text = NULL,*fl35_text = NULL;
+
+ /* If no ExifData is supplied the label will be
+ * cleared later as fl35_text is NULL. */
+ if (exif_data != NULL) {
+ entry = exif_data_get_entry (exif_data, EXIF_TAG_FOCAL_LENGTH);
+ entry35mm = exif_data_get_entry (exif_data,
+ EXIF_TAG_FOCAL_LENGTH_IN_35MM_FILM);
+ byte_order = exif_data_get_byte_order (exif_data);
+ }
+
+ if (entry && G_LIKELY (entry->format == EXIF_FORMAT_RATIONAL)) {
+ ExifRational value;
+
+ /* Decode value by hand as libexif is not necessarily returning
+ * it in the format we want it to be.
+ */
+ value = exif_get_rational (entry->data, byte_order);
+ /* Guard against div by zero */
+ if (G_LIKELY(value.denominator != 0))
+ f_val = (gfloat)value.numerator/
+ (gfloat)value.denominator;
+
+ /* TRANSLATORS: This is the actual focal length used when
+ the image was taken.*/
+ fl_text = g_strdup_printf (_("%.1f (lens)"), f_val);
+
+ }
+ if (entry35mm && G_LIKELY (entry35mm->format == EXIF_FORMAT_SHORT)) {
+ ExifShort s_val;
+
+ s_val = exif_get_short (entry35mm->data, byte_order);
+
+ /* Print as float to get a similar look as above. */
+ /* TRANSLATORS: This is the equivalent focal length assuming
+ a 35mm film camera. */
+ fl35_text = g_strdup_printf(_("%.1f (35mm film)"),(float)s_val);
+ }
+
+ if (fl_text) {
+ if (fl35_text) {
+ gchar *merged_txt;
+
+ merged_txt = g_strconcat (fl35_text,", ", fl_text, NULL);
+ gtk_label_set_text (label, merged_txt);
+ g_free (merged_txt);
+ } else {
+ gtk_label_set_text (label, fl_text);
+ }
+ } else {
+ /* This will also clear the label if no ExifData was supplied */
+ gtk_label_set_text (label, fl35_text);
+ }
+
+ g_free (fl35_text);
+ g_free (fl_text);
+}
+
+/**
+ * eog_exif_data_get_value:
+ * @exif_data: pointer to an <structname>ExifData</structname> struct
+ * @tag_id: the requested tag's id. See <filename>exif-tag.h</filename>
+ * from the libexif package for possible values (e.g. %EXIF_TAG_EXPOSURE_MODE).
+ * @buffer: a pre-allocated output buffer
+ * @buf_size: size of @buffer
+ *
+ * Convenience function to extract a string representation of an Exif tag
+ * directly from an <structname>ExifData</structname> struct. The string is
+ * written into @buffer as far as @buf_size permits.
+ *
+ * Returns: a pointer to @buffer.
+ */
+const gchar *
+eog_exif_data_get_value (EogExifData *exif_data, gint tag_id, gchar *buffer, guint buf_size)
+{
+ ExifEntry *exif_entry;
+ const gchar *exif_value;
+
+ exif_entry = exif_data_get_entry (exif_data, tag_id);
+
+ buffer[0] = 0;
+
+ exif_value = exif_entry_get_value (exif_entry, buffer, buf_size);
+
+ return exif_value;
+}
+
+EogExifData *
+eog_exif_data_copy (EogExifData *data)
+{
+ exif_data_ref (data);
+
+ return data;
+}
+
+void
+eog_exif_data_free (EogExifData *data)
+{
+ exif_data_unref (data);
+}
diff --git a/src/eog-exif-util.h b/src/eog-exif-util.h
new file mode 100644
index 0000000..7646234
--- /dev/null
+++ b/src/eog-exif-util.h
@@ -0,0 +1,59 @@
+/* Eye Of Gnome - EXIF Utilities
+ *
+ * Copyright (C) 2006-2007 The Free Software Foundation
+ *
+ * Author: Lucas Rocha <lucasr gnome org>
+ * Author: Claudio Saavedra <csaavedra alumnos utalca cl>
+ * Author: Felix Riemann <felix hsgheli de>
+ *
+ * Based on code by:
+ * - Jens Finke <jens gnome org>
+ *
+ * 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 Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __EOG_EXIF_UTIL_H__
+#define __EOG_EXIF_UTIL_H__
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gtk/gtk.h>
+#include <libexif/exif-data.h>
+
+G_BEGIN_DECLS
+
+#define EOG_TYPE_EXIF_DATA eog_exif_data_get_type ()
+
+gchar *eog_exif_util_format_date (const gchar *date);
+void eog_exif_util_set_label_text (GtkLabel *label,
+ ExifData *exif_data,
+ gint tag_id);
+
+void eog_exif_util_set_focal_length_label_text (GtkLabel *label,
+ ExifData *exif_data);
+
+
+const gchar *eog_exif_data_get_value (ExifData *exif_data,
+ gint tag_id, gchar *buffer,
+ guint buf_size);
+
+GType eog_exif_data_get_type (void) G_GNUC_CONST;
+
+ExifData *eog_exif_data_copy (ExifData *data);
+void eog_exif_data_free (ExifData *data);
+
+G_END_DECLS
+
+#endif /* __EOG_EXIF_UTIL_H__ */
diff --git a/src/eog-image-private.h b/src/eog-image-private.h
new file mode 100644
index 0000000..3af4e90
--- /dev/null
+++ b/src/eog-image-private.h
@@ -0,0 +1,97 @@
+/* Eye Of Gnome - Image Private Data
+ *
+ * Copyright (C) 2007 The Free Software Foundation
+ *
+ * Author: Lucas Rocha <lucasr gnome org>
+ *
+ * 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 Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __EOG_IMAGE_PRIVATE_H__
+#define __EOG_IMAGE_PRIVATE_H__
+
+#include "eog-image.h"
+#ifdef HAVE_RSVG
+#include <librsvg/rsvg.h>
+#endif
+
+G_BEGIN_DECLS
+
+struct _EogImagePrivate {
+ GFile *file;
+
+ EogImageStatus status;
+ EogImageStatus prev_status;
+ gboolean is_monitored;
+ EogImageMetadataStatus metadata_status;
+
+ GdkPixbuf *image;
+ GdkPixbufAnimation *anim;
+ GdkPixbufAnimationIter *anim_iter;
+ gboolean is_playing;
+ GdkPixbuf *thumbnail;
+#ifdef HAVE_RSVG
+ RsvgHandle *svg;
+#endif
+
+ gint width;
+ gint height;
+
+ goffset bytes;
+ gchar *file_type;
+ gboolean threadsafe_format;
+
+ /* Holds EXIF raw data */
+ guint exif_chunk_len;
+ guchar *exif_chunk;
+
+ /* Holds IPTC raw data */
+ guchar *iptc_chunk;
+ guint iptc_chunk_len;
+
+ gboolean modified;
+ gboolean file_is_changed;
+
+ gboolean autorotate;
+ gint orientation;
+#ifdef HAVE_EXIF
+ ExifData *exif;
+#endif
+#ifdef HAVE_EXEMPI
+ XmpPtr xmp;
+#endif
+
+#ifdef HAVE_LCMS
+ cmsHPROFILE profile;
+#endif
+
+ gchar *caption;
+
+ gchar *collate_key;
+
+ GMutex status_mutex;
+
+ gboolean cancel_loading;
+ guint data_ref_count;
+
+ GSList *undo_stack;
+
+ EogTransform *trans;
+ EogTransform *trans_autorotate;
+};
+
+G_END_DECLS
+
+#endif /* __EOG_IMAGE_PRIVATE_H__ */
diff --git a/src/eog-image-save-info.c b/src/eog-image-save-info.c
new file mode 100644
index 0000000..c7844d7
--- /dev/null
+++ b/src/eog-image-save-info.c
@@ -0,0 +1,147 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include "eog-image-save-info.h"
+#include "eog-image-private.h"
+#include "eog-image.h"
+#include "photos-utils.h"
+
+G_DEFINE_TYPE (EogImageSaveInfo, eog_image_save_info, G_TYPE_OBJECT)
+
+static void
+eog_image_save_info_dispose (GObject *object)
+{
+ EogImageSaveInfo *info = EOG_IMAGE_SAVE_INFO (object);
+
+ if (info->file != NULL) {
+ g_object_unref (info->file);
+ info->file = NULL;
+ }
+
+ if (info->format != NULL) {
+ g_free (info->format);
+ info->format = NULL;
+ }
+
+ G_OBJECT_CLASS (eog_image_save_info_parent_class)->dispose (object);
+}
+
+static void
+eog_image_save_info_init (EogImageSaveInfo *obj)
+{
+
+}
+
+static void
+eog_image_save_info_class_init (EogImageSaveInfoClass *klass)
+{
+ GObjectClass *object_class = (GObjectClass*) klass;
+
+ object_class->dispose = eog_image_save_info_dispose;
+}
+
+/* is_local_uri:
+ *
+ * Checks if the URI points to a local file system. This tests simply
+ * if the URI scheme is 'file'. This function is used to ensure that
+ * we can write to the path-part of the URI with non-VFS aware
+ * filesystem calls.
+ */
+static gboolean
+is_local_file (GFile *file)
+{
+ char *scheme;
+ gboolean ret;
+
+ g_return_val_if_fail (file != NULL, FALSE);
+
+ scheme = g_file_get_uri_scheme (file);
+
+ ret = (g_ascii_strcasecmp (scheme, "file") == 0);
+ g_free (scheme);
+ return ret;
+}
+
+static char*
+get_save_file_type_by_file (GFile *file)
+{
+ GdkPixbufFormat *format;
+ char *type = NULL;
+
+ format = photos_utils_get_pixbuf_format (file);
+ if (format != NULL) {
+ type = gdk_pixbuf_format_get_name (format);
+ }
+
+ return type;
+}
+
+EogImageSaveInfo*
+eog_image_save_info_new_from_image (EogImage *image)
+{
+ EogImageSaveInfo *info = NULL;
+
+ g_return_val_if_fail (EOG_IS_IMAGE (image), NULL);
+
+ info = g_object_new (EOG_TYPE_IMAGE_SAVE_INFO, NULL);
+
+ info->file = eog_image_get_file (image);
+ info->format = g_strdup (image->priv->file_type);
+ info->exists = g_file_query_exists (info->file, NULL);
+ info->local = is_local_file (info->file);
+ info->has_metadata = eog_image_has_data (image, EOG_IMAGE_DATA_EXIF);
+ info->modified = eog_image_is_modified (image);
+ info->overwrite = FALSE;
+
+ info->jpeg_quality = -1.0;
+
+ return info;
+}
+
+EogImageSaveInfo*
+eog_image_save_info_new_from_uri (const char *txt_uri, GdkPixbufFormat *format)
+{
+ GFile *file;
+ EogImageSaveInfo *info;
+
+ g_return_val_if_fail (txt_uri != NULL, NULL);
+
+ file = g_file_new_for_uri (txt_uri);
+
+ info = eog_image_save_info_new_from_file (file, format);
+
+ g_object_unref (file);
+
+ return info;
+}
+
+EogImageSaveInfo*
+eog_image_save_info_new_from_file (GFile *file, GdkPixbufFormat *format)
+{
+ EogImageSaveInfo *info;
+
+ g_return_val_if_fail (file != NULL, NULL);
+
+ info = g_object_new (EOG_TYPE_IMAGE_SAVE_INFO, NULL);
+
+ info->file = g_object_ref (file);
+ if (format == NULL) {
+ info->format = get_save_file_type_by_file (info->file);
+ }
+ else {
+ info->format = gdk_pixbuf_format_get_name (format);
+ }
+ info->exists = g_file_query_exists (file, NULL);
+ info->local = is_local_file (file);
+ info->has_metadata = FALSE;
+ info->modified = FALSE;
+ info->overwrite = FALSE;
+
+ info->jpeg_quality = -1.0;
+
+ g_assert (info->format != NULL);
+
+ return info;
+}
diff --git a/src/eog-image-save-info.h b/src/eog-image-save-info.h
new file mode 100644
index 0000000..33d6b27
--- /dev/null
+++ b/src/eog-image-save-info.h
@@ -0,0 +1,57 @@
+#ifndef _EOG_IMAGE_SAVE_INFO_H_
+#define _EOG_IMAGE_SAVE_INFO_H_
+
+#include <glib-object.h>
+#include <gio/gio.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+G_BEGIN_DECLS
+
+#ifndef __EOG_IMAGE_DECLR__
+#define __EOG_IMAGE_DECLR__
+typedef struct _EogImage EogImage;
+#endif
+
+#define EOG_TYPE_IMAGE_SAVE_INFO (eog_image_save_info_get_type ())
+#define EOG_IMAGE_SAVE_INFO(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), EOG_TYPE_IMAGE_SAVE_INFO, EogImageSaveInfo))
+#define EOG_IMAGE_SAVE_INFO_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), EOG_TYPE_IMAGE_SAVE_INFO, EogImageSaveInfoClass))
+#define EOG_IS_IMAGE_SAVE_INFO(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), EOG_TYPE_IMAGE_SAVE_INFO))
+#define EOG_IS_IMAGE_SAVE_INFO_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), EOG_TYPE_IMAGE_SAVE_INFO))
+#define EOG_IMAGE_SAVE_INFO_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), EOG_TYPE_IMAGE_SAVE_INFO, EogImageSaveInfoClass))
+
+typedef struct _EogImageSaveInfo EogImageSaveInfo;
+typedef struct _EogImageSaveInfoClass EogImageSaveInfoClass;
+
+struct _EogImageSaveInfo {
+ GObject parent;
+
+ GFile *file;
+ char *format;
+ gboolean exists;
+ gboolean local;
+ gboolean has_metadata;
+ gboolean modified;
+ gboolean overwrite;
+
+ float jpeg_quality; /* valid range: [0.0 ... 1.0] */
+};
+
+struct _EogImageSaveInfoClass {
+ GObjectClass parent_klass;
+};
+
+#define EOG_FILE_FORMAT_JPEG "jpeg"
+
+GType eog_image_save_info_get_type (void) G_GNUC_CONST;
+
+EogImageSaveInfo *eog_image_save_info_new_from_image (EogImage *image);
+
+EogImageSaveInfo *eog_image_save_info_new_from_uri (const char *uri,
+ GdkPixbufFormat *format);
+
+EogImageSaveInfo *eog_image_save_info_new_from_file (GFile *file,
+ GdkPixbufFormat *format);
+
+G_END_DECLS
+
+#endif /* _EOG_IMAGE_SAVE_INFO_H_ */
diff --git a/src/eog-image.c b/src/eog-image.c
new file mode 100644
index 0000000..c9fe899
--- /dev/null
+++ b/src/eog-image.c
@@ -0,0 +1,2475 @@
+/* Eye Of Gnome - Image
+ *
+ * Copyright (C) 2006 The Free Software Foundation
+ *
+ * Author: Lucas Rocha <lucasr gnome org>
+ *
+ * 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 Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#define GDK_PIXBUF_ENABLE_BACKEND
+
+#include "eog-image.h"
+#include "eog-image-private.h"
+#include "eog-debug.h"
+
+#ifdef HAVE_JPEG
+#include "eog-image-jpeg.h"
+#endif
+
+#include "eog-metadata-reader.h"
+#include "eog-image-save-info.h"
+#include "eog-transform.h"
+#include "eog-util.h"
+#include "eog-jobs.h"
+#include "photos-marshalers.h"
+#include "photos-utils.h"
+
+#include <unistd.h>
+#include <string.h>
+
+#include <glib.h>
+#include <glib-object.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#ifdef HAVE_EXIF
+#include "eog-exif-util.h"
+#include <libexif/exif-data.h>
+#include <libexif/exif-utils.h>
+#include <libexif/exif-loader.h>
+#endif
+
+#ifdef HAVE_LCMS
+#include <lcms2.h>
+#ifndef EXIF_TAG_GAMMA
+#define EXIF_TAG_GAMMA 0xa500
+#endif
+#endif
+
+#ifdef HAVE_RSVG
+#include <librsvg/rsvg.h>
+#include <librsvg/librsvg-features.h>
+#endif
+
+#define EOG_IMAGE_GET_PRIVATE(object) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((object), EOG_TYPE_IMAGE, EogImagePrivate))
+
+G_DEFINE_TYPE (EogImage, eog_image, G_TYPE_OBJECT)
+
+enum {
+ SIGNAL_CHANGED,
+ SIGNAL_SIZE_PREPARED,
+ SIGNAL_SAVE_PROGRESS,
+ SIGNAL_NEXT_FRAME,
+ SIGNAL_FILE_CHANGED,
+ SIGNAL_LAST
+};
+
+static gint signals[SIGNAL_LAST];
+
+static GList *supported_mime_types = NULL;
+
+#define EOG_IMAGE_READ_BUFFER_SIZE 65535
+
+static void
+eog_image_free_mem_private (EogImage *image)
+{
+ EogImagePrivate *priv;
+
+ priv = image->priv;
+
+ if (priv->status == EOG_IMAGE_STATUS_LOADING) {
+ eog_image_cancel_load (image);
+ } else {
+ if (priv->anim_iter != NULL) {
+ g_object_unref (priv->anim_iter);
+ priv->anim_iter = NULL;
+ }
+
+ if (priv->anim != NULL) {
+ g_object_unref (priv->anim);
+ priv->anim = NULL;
+ }
+
+ priv->is_playing = FALSE;
+
+ if (priv->image != NULL) {
+ g_object_unref (priv->image);
+ priv->image = NULL;
+ }
+
+#ifdef HAVE_RSVG
+ if (priv->svg != NULL) {
+ g_object_unref (priv->svg);
+ priv->svg = NULL;
+ }
+#endif
+
+#ifdef HAVE_EXIF
+ if (priv->exif != NULL) {
+ exif_data_unref (priv->exif);
+ priv->exif = NULL;
+ }
+#endif
+
+ if (priv->exif_chunk != NULL) {
+ g_free (priv->exif_chunk);
+ priv->exif_chunk = NULL;
+ }
+
+ priv->exif_chunk_len = 0;
+
+#ifdef HAVE_EXEMPI
+ if (priv->xmp != NULL) {
+ xmp_free (priv->xmp);
+ priv->xmp = NULL;
+ }
+#endif
+
+#ifdef HAVE_LCMS
+ if (priv->profile != NULL) {
+ cmsCloseProfile (priv->profile);
+ priv->profile = NULL;
+ }
+#endif
+
+ priv->status = EOG_IMAGE_STATUS_UNKNOWN;
+ priv->metadata_status = EOG_IMAGE_METADATA_NOT_READ;
+ }
+}
+
+static void
+eog_image_dispose (GObject *object)
+{
+ EogImagePrivate *priv;
+
+ priv = EOG_IMAGE (object)->priv;
+
+ eog_image_free_mem_private (EOG_IMAGE (object));
+
+ if (priv->file) {
+ g_object_unref (priv->file);
+ priv->file = NULL;
+ }
+
+ if (priv->caption) {
+ g_free (priv->caption);
+ priv->caption = NULL;
+ }
+
+ if (priv->collate_key) {
+ g_free (priv->collate_key);
+ priv->collate_key = NULL;
+ }
+
+ if (priv->file_type) {
+ g_free (priv->file_type);
+ priv->file_type = NULL;
+ }
+
+ g_mutex_clear (&priv->status_mutex);
+
+ if (priv->trans) {
+ g_object_unref (priv->trans);
+ priv->trans = NULL;
+ }
+
+ if (priv->trans_autorotate) {
+ g_object_unref (priv->trans_autorotate);
+ priv->trans_autorotate = NULL;
+ }
+
+ if (priv->undo_stack) {
+ g_slist_foreach (priv->undo_stack, (GFunc) g_object_unref, NULL);
+ g_slist_free (priv->undo_stack);
+ priv->undo_stack = NULL;
+ }
+
+ G_OBJECT_CLASS (eog_image_parent_class)->dispose (object);
+}
+
+static void
+eog_image_finalize (GObject *object)
+{
+ EogImagePrivate *priv;
+
+ priv = EOG_IMAGE (object)->priv;
+
+ g_mutex_clear (&priv->status_mutex);
+
+ G_OBJECT_CLASS (eog_image_parent_class)->finalize (object);
+}
+
+static void
+eog_image_class_init (EogImageClass *klass)
+{
+ GObjectClass *object_class = (GObjectClass*) klass;
+
+ object_class->dispose = eog_image_dispose;
+ object_class->finalize = eog_image_finalize;
+
+ signals[SIGNAL_SIZE_PREPARED] =
+ g_signal_new ("size-prepared",
+ EOG_TYPE_IMAGE,
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (EogImageClass, size_prepared),
+ NULL, NULL,
+ _photos_marshal_VOID__INT_INT,
+ G_TYPE_NONE, 2,
+ G_TYPE_INT,
+ G_TYPE_INT);
+
+ signals[SIGNAL_CHANGED] =
+ g_signal_new ("changed",
+ EOG_TYPE_IMAGE,
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (EogImageClass, changed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ signals[SIGNAL_SAVE_PROGRESS] =
+ g_signal_new ("save-progress",
+ EOG_TYPE_IMAGE,
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (EogImageClass, save_progress),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__FLOAT,
+ G_TYPE_NONE, 1,
+ G_TYPE_FLOAT);
+ /**
+ * EogImage::next-frame:
+ * @img: the object which received the signal.
+ * @delay: number of milliseconds the current frame will be displayed.
+ *
+ * The ::next-frame signal will be emitted each time an animated image
+ * advances to the next frame.
+ */
+ signals[SIGNAL_NEXT_FRAME] =
+ g_signal_new ("next-frame",
+ EOG_TYPE_IMAGE,
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (EogImageClass, next_frame),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__INT,
+ G_TYPE_NONE, 1,
+ G_TYPE_INT);
+
+ signals[SIGNAL_FILE_CHANGED] = g_signal_new ("file-changed",
+ EOG_TYPE_IMAGE,
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (EogImageClass, file_changed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ g_type_class_add_private (object_class, sizeof (EogImagePrivate));
+}
+
+static void
+eog_image_init (EogImage *img)
+{
+ img->priv = EOG_IMAGE_GET_PRIVATE (img);
+
+ img->priv->file = NULL;
+ img->priv->image = NULL;
+ img->priv->anim = NULL;
+ img->priv->anim_iter = NULL;
+ img->priv->is_playing = FALSE;
+ img->priv->width = -1;
+ img->priv->height = -1;
+ img->priv->modified = FALSE;
+ img->priv->file_is_changed = FALSE;
+ g_mutex_init (&img->priv->status_mutex);
+ img->priv->status = EOG_IMAGE_STATUS_UNKNOWN;
+ img->priv->metadata_status = EOG_IMAGE_METADATA_NOT_READ;
+ img->priv->is_monitored = FALSE;
+ img->priv->undo_stack = NULL;
+ img->priv->trans = NULL;
+ img->priv->trans_autorotate = NULL;
+ img->priv->data_ref_count = 0;
+#ifdef HAVE_EXIF
+ img->priv->orientation = 0;
+ img->priv->autorotate = FALSE;
+ img->priv->exif = NULL;
+#endif
+#ifdef HAVE_EXEMPI
+ img->priv->xmp = NULL;
+#endif
+#ifdef HAVE_LCMS
+ img->priv->profile = NULL;
+#endif
+#ifdef HAVE_RSVG
+ img->priv->svg = NULL;
+#endif
+}
+
+EogImage *
+eog_image_new (const char *txt_uri)
+{
+ EogImage *img;
+
+ img = EOG_IMAGE (g_object_new (EOG_TYPE_IMAGE, NULL));
+
+ img->priv->file = g_file_new_for_uri (txt_uri);
+
+ return img;
+}
+
+EogImage *
+eog_image_new_file (GFile *file)
+{
+ EogImage *img;
+
+ img = EOG_IMAGE (g_object_new (EOG_TYPE_IMAGE, NULL));
+
+ img->priv->file = g_object_ref (file);
+
+ return img;
+}
+
+GQuark
+eog_image_error_quark (void)
+{
+ static GQuark q = 0;
+
+ if (q == 0) {
+ q = g_quark_from_static_string ("eog-image-error-quark");
+ }
+
+ return q;
+}
+
+static void
+eog_image_update_exif_data (EogImage *image)
+{
+#ifdef HAVE_EXIF
+ EogImagePrivate *priv;
+ ExifEntry *entry;
+ ExifByteOrder bo;
+
+ eog_debug (DEBUG_IMAGE_DATA);
+
+ g_return_if_fail (EOG_IS_IMAGE (image));
+
+ priv = image->priv;
+
+ if (priv->exif == NULL) return;
+
+ bo = exif_data_get_byte_order (priv->exif);
+
+ /* Update image width */
+ entry = exif_data_get_entry (priv->exif, EXIF_TAG_PIXEL_X_DIMENSION);
+ if (entry != NULL && (priv->width >= 0)) {
+ if (entry->format == EXIF_FORMAT_LONG)
+ exif_set_long (entry->data, bo, priv->width);
+ else if (entry->format == EXIF_FORMAT_SHORT)
+ exif_set_short (entry->data, bo, priv->width);
+ else
+ g_warning ("Exif entry has unsupported size");
+ }
+
+ /* Update image height */
+ entry = exif_data_get_entry (priv->exif, EXIF_TAG_PIXEL_Y_DIMENSION);
+ if (entry != NULL && (priv->height >= 0)) {
+ if (entry->format == EXIF_FORMAT_LONG)
+ exif_set_long (entry->data, bo, priv->height);
+ else if (entry->format == EXIF_FORMAT_SHORT)
+ exif_set_short (entry->data, bo, priv->height);
+ else
+ g_warning ("Exif entry has unsupported size");
+ }
+
+ /* Update image orientation */
+ entry = exif_data_get_entry (priv->exif, EXIF_TAG_ORIENTATION);
+ if (entry != NULL) {
+ if (entry->format == EXIF_FORMAT_LONG)
+ exif_set_long (entry->data, bo, 1);
+ else if (entry->format == EXIF_FORMAT_SHORT)
+ exif_set_short (entry->data, bo, 1);
+ else
+ g_warning ("Exif entry has unsupported size");
+
+ priv->orientation = 1;
+ }
+#endif
+}
+
+static void
+eog_image_real_transform (EogImage *img,
+ EogTransform *trans,
+ gboolean is_undo,
+ EogJob *job)
+{
+ EogImagePrivate *priv;
+ GdkPixbuf *transformed;
+ gboolean modified = FALSE;
+
+ g_return_if_fail (EOG_IS_IMAGE (img));
+ g_return_if_fail (EOG_IS_TRANSFORM (trans));
+
+ priv = img->priv;
+
+ if (priv->image != NULL) {
+ transformed = eog_transform_apply (trans, priv->image, job);
+
+ g_object_unref (priv->image);
+ priv->image = transformed;
+
+ priv->width = gdk_pixbuf_get_width (transformed);
+ priv->height = gdk_pixbuf_get_height (transformed);
+
+ modified = TRUE;
+ }
+
+ if (modified) {
+ priv->modified = TRUE;
+ eog_image_update_exif_data (img);
+ }
+
+ if (priv->trans == NULL) {
+ g_object_ref (trans);
+ priv->trans = trans;
+ } else {
+ EogTransform *composition;
+
+ composition = eog_transform_compose (priv->trans, trans);
+
+ g_object_unref (priv->trans);
+
+ priv->trans = composition;
+ }
+
+ if (!is_undo) {
+ g_object_ref (trans);
+ priv->undo_stack = g_slist_prepend (priv->undo_stack, trans);
+ }
+}
+
+static gboolean
+do_emit_size_prepared_signal (EogImage *img)
+{
+ g_signal_emit (img, signals[SIGNAL_SIZE_PREPARED], 0,
+ img->priv->width, img->priv->height);
+ return FALSE;
+}
+
+static void
+eog_image_emit_size_prepared (EogImage *img)
+{
+ gdk_threads_add_idle_full (G_PRIORITY_DEFAULT_IDLE,
+ (GSourceFunc) do_emit_size_prepared_signal,
+ g_object_ref (img), g_object_unref);
+}
+
+static gboolean
+check_loader_threadsafety (GdkPixbufLoader *loader, gboolean *result)
+{
+ GdkPixbufFormat *format;
+ gboolean ret_val = FALSE;
+
+ format = gdk_pixbuf_loader_get_format (loader);
+ if (format) {
+ ret_val = TRUE;
+ if (result)
+ /* FIXME: We should not be accessing this struct internals
+ * directly. Keep track of bug #469209 to fix that. */
+ *result = format->flags & GDK_PIXBUF_FORMAT_THREADSAFE;
+ }
+
+ return ret_val;
+}
+
+static void
+eog_image_pre_size_prepared (GdkPixbufLoader *loader,
+ gint width,
+ gint height,
+ gpointer data)
+{
+ EogImage *img;
+
+ eog_debug (DEBUG_IMAGE_LOAD);
+
+ g_return_if_fail (EOG_IS_IMAGE (data));
+
+ img = EOG_IMAGE (data);
+ check_loader_threadsafety (loader, &img->priv->threadsafe_format);
+}
+
+static void
+eog_image_size_prepared (GdkPixbufLoader *loader,
+ gint width,
+ gint height,
+ gpointer data)
+{
+ EogImage *img;
+
+ eog_debug (DEBUG_IMAGE_LOAD);
+
+ g_return_if_fail (EOG_IS_IMAGE (data));
+
+ img = EOG_IMAGE (data);
+
+ g_mutex_lock (&img->priv->status_mutex);
+
+ img->priv->width = width;
+ img->priv->height = height;
+
+ g_mutex_unlock (&img->priv->status_mutex);
+
+#ifdef HAVE_EXIF
+ if (img->priv->threadsafe_format && (!img->priv->autorotate || img->priv->exif))
+#else
+ if (img->priv->threadsafe_format)
+#endif
+ eog_image_emit_size_prepared (img);
+}
+
+static EogMetadataReader*
+check_for_metadata_img_format (EogImage *img, guchar *buffer, guint bytes_read)
+{
+ EogMetadataReader *md_reader = NULL;
+
+ eog_debug_message (DEBUG_IMAGE_DATA, "Check image format for jpeg: %x%x - length: %i",
+ buffer[0], buffer[1], bytes_read);
+
+ if (bytes_read >= 2) {
+ /* SOI (start of image) marker for JPEGs is 0xFFD8 */
+ if ((buffer[0] == 0xFF) && (buffer[1] == 0xD8)) {
+ md_reader = eog_metadata_reader_new (EOG_METADATA_JPEG);
+ }
+ if (bytes_read >= 8 &&
+ memcmp (buffer, "\x89PNG\x0D\x0A\x1a\x0A", 8) == 0) {
+ md_reader = eog_metadata_reader_new (EOG_METADATA_PNG);
+ }
+ }
+
+ return md_reader;
+}
+
+static gboolean
+eog_image_needs_transformation (EogImage *img)
+{
+ g_return_val_if_fail (EOG_IS_IMAGE (img), FALSE);
+
+ return (img->priv->trans != NULL || img->priv->trans_autorotate != NULL);
+}
+
+static gboolean
+eog_image_apply_transformations (EogImage *img, GError **error)
+{
+ GdkPixbuf *transformed = NULL;
+ EogTransform *composition = NULL;
+ EogImagePrivate *priv;
+
+ g_return_val_if_fail (EOG_IS_IMAGE (img), FALSE);
+
+ priv = img->priv;
+
+ if (priv->trans == NULL && priv->trans_autorotate == NULL) {
+ return TRUE;
+ }
+
+ if (priv->image == NULL) {
+ g_set_error (error,
+ EOG_IMAGE_ERROR,
+ EOG_IMAGE_ERROR_NOT_LOADED,
+ _("Transformation on unloaded image."));
+
+ return FALSE;
+ }
+
+ if (priv->trans != NULL && priv->trans_autorotate != NULL) {
+ composition = eog_transform_compose (priv->trans,
+ priv->trans_autorotate);
+ } else if (priv->trans != NULL) {
+ composition = g_object_ref (priv->trans);
+ } else if (priv->trans_autorotate != NULL) {
+ composition = g_object_ref (priv->trans_autorotate);
+ }
+
+ if (composition != NULL) {
+ transformed = eog_transform_apply (composition, priv->image, NULL);
+ }
+
+ g_object_unref (priv->image);
+ priv->image = transformed;
+
+ if (transformed != NULL) {
+ priv->width = gdk_pixbuf_get_width (priv->image);
+ priv->height = gdk_pixbuf_get_height (priv->image);
+ } else {
+ g_set_error (error,
+ EOG_IMAGE_ERROR,
+ EOG_IMAGE_ERROR_GENERIC,
+ _("Transformation failed."));
+ }
+
+ g_object_unref (composition);
+
+ return (transformed != NULL);
+}
+
+static void
+eog_image_get_file_info (EogImage *img,
+ goffset *bytes,
+ gchar **mime_type,
+ GError **error)
+{
+ GFileInfo *file_info;
+
+ file_info = g_file_query_info (img->priv->file,
+ G_FILE_ATTRIBUTE_STANDARD_SIZE ","
+ G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
+ 0, NULL, error);
+
+ if (file_info == NULL) {
+ if (bytes)
+ *bytes = 0;
+
+ if (mime_type)
+ *mime_type = NULL;
+
+ g_set_error (error,
+ EOG_IMAGE_ERROR,
+ EOG_IMAGE_ERROR_VFS,
+ "Error in getting image file info");
+ } else {
+ if (bytes)
+ *bytes = g_file_info_get_size (file_info);
+
+ if (mime_type)
+ *mime_type = g_strdup (g_file_info_get_content_type (file_info));
+ g_object_unref (file_info);
+ }
+}
+
+#ifdef HAVE_LCMS
+void
+eog_image_apply_display_profile (EogImage *img, cmsHPROFILE screen)
+{
+ EogImagePrivate *priv;
+ cmsHTRANSFORM transform;
+ gint row, width, rows, stride;
+ guchar *p;
+
+ g_return_if_fail (img != NULL);
+
+ priv = img->priv;
+
+ if (screen == NULL || priv->profile == NULL) return;
+
+ /* TODO: support other colorspaces than RGB */
+ if (cmsGetColorSpace (priv->profile) != cmsSigRgbData ||
+ cmsGetColorSpace (screen) != cmsSigRgbData) {
+ eog_debug_message (DEBUG_LCMS, "One or both ICC profiles not in RGB colorspace; not correcting");
+ return;
+ }
+
+ /* TODO: find the right way to colorcorrect RGBA images */
+ if (gdk_pixbuf_get_has_alpha (priv->image)) {
+ eog_debug_message (DEBUG_LCMS, "Colorcorrecting RGBA images is unsupported.");
+ return;
+ }
+
+ transform = cmsCreateTransform (priv->profile,
+ TYPE_RGB_8,
+ screen,
+ TYPE_RGB_8,
+ INTENT_PERCEPTUAL,
+ 0);
+
+ if (G_LIKELY (transform != NULL)) {
+ rows = gdk_pixbuf_get_height (priv->image);
+ width = gdk_pixbuf_get_width (priv->image);
+ stride = gdk_pixbuf_get_rowstride (priv->image);
+ p = gdk_pixbuf_get_pixels (priv->image);
+
+ for (row = 0; row < rows; ++row) {
+ cmsDoTransform (transform, p, p, width);
+ p += stride;
+ }
+ cmsDeleteTransform (transform);
+ }
+}
+
+static void
+eog_image_set_icc_data (EogImage *img, EogMetadataReader *md_reader)
+{
+ EogImagePrivate *priv = img->priv;
+
+ priv->profile = eog_metadata_reader_get_icc_profile (md_reader);
+
+
+}
+#endif
+
+static void
+eog_image_set_orientation (EogImage *img)
+{
+ EogImagePrivate *priv;
+#ifdef HAVE_EXIF
+ ExifData* exif;
+#endif
+
+ g_return_if_fail (EOG_IS_IMAGE (img));
+
+ priv = img->priv;
+
+#ifdef HAVE_EXIF
+ exif = (ExifData*) eog_image_get_exif_info (img);
+
+ if (exif != NULL) {
+ ExifByteOrder o = exif_data_get_byte_order (exif);
+
+ ExifEntry *entry = exif_data_get_entry (exif,
+ EXIF_TAG_ORIENTATION);
+
+ if (entry && entry->data != NULL) {
+ priv->orientation = exif_get_short (entry->data, o);
+ }
+
+ exif_data_unref (exif);
+ } else
+#endif
+ {
+ GdkPixbuf *pbuf;
+
+ pbuf = eog_image_get_pixbuf (img);
+
+ if (pbuf) {
+ const gchar *o_str;
+
+ o_str = gdk_pixbuf_get_option (pbuf, "orientation");
+ if (o_str) {
+ short t = (short) g_ascii_strtoll (o_str,
+ NULL, 10);
+ if (t >= 0 && t < 9)
+ priv->orientation = t;
+ }
+ g_object_unref (pbuf);
+ }
+ }
+
+ if (priv->orientation > 4 &&
+ priv->orientation < 9) {
+ gint tmp;
+
+ tmp = priv->width;
+ priv->width = priv->height;
+ priv->height = tmp;
+ }
+}
+
+static void
+eog_image_real_autorotate (EogImage *img)
+{
+ static const EogTransformType lookup[8] = {EOG_TRANSFORM_NONE,
+ EOG_TRANSFORM_FLIP_HORIZONTAL,
+ EOG_TRANSFORM_ROT_180,
+ EOG_TRANSFORM_FLIP_VERTICAL,
+ EOG_TRANSFORM_TRANSPOSE,
+ EOG_TRANSFORM_ROT_90,
+ EOG_TRANSFORM_TRANSVERSE,
+ EOG_TRANSFORM_ROT_270};
+ EogImagePrivate *priv;
+ EogTransformType type;
+
+ g_return_if_fail (EOG_IS_IMAGE (img));
+
+ priv = img->priv;
+
+ type = (priv->orientation >= 1 && priv->orientation <= 8 ?
+ lookup[priv->orientation - 1] : EOG_TRANSFORM_NONE);
+
+ if (type != EOG_TRANSFORM_NONE) {
+ img->priv->trans_autorotate = eog_transform_new (type);
+ }
+
+ /* Disable auto orientation for next loads */
+ priv->autorotate = FALSE;
+}
+
+void
+eog_image_autorotate (EogImage *img)
+{
+ g_return_if_fail (EOG_IS_IMAGE (img));
+
+ /* Schedule auto orientation */
+ img->priv->autorotate = TRUE;
+}
+
+#ifdef HAVE_EXEMPI
+static void
+eog_image_set_xmp_data (EogImage *img, EogMetadataReader *md_reader)
+{
+ EogImagePrivate *priv;
+
+ g_return_if_fail (EOG_IS_IMAGE (img));
+
+ priv = img->priv;
+
+ if (priv->xmp) {
+ xmp_free (priv->xmp);
+ }
+ priv->xmp = eog_metadata_reader_get_xmp_data (md_reader);
+}
+#endif
+
+static void
+eog_image_set_exif_data (EogImage *img, EogMetadataReader *md_reader)
+{
+ EogImagePrivate *priv;
+
+ g_return_if_fail (EOG_IS_IMAGE (img));
+
+ priv = img->priv;
+
+#ifdef HAVE_EXIF
+ g_mutex_lock (&priv->status_mutex);
+ if (priv->exif) {
+ exif_data_unref (priv->exif);
+ }
+ priv->exif = eog_metadata_reader_get_exif_data (md_reader);
+ g_mutex_unlock (&priv->status_mutex);
+
+ priv->exif_chunk = NULL;
+ priv->exif_chunk_len = 0;
+
+ /* EXIF data is already available, set the image orientation */
+ if (priv->autorotate) {
+ eog_image_set_orientation (img);
+
+ /* Emit size prepared signal if we have the size */
+ if (priv->width > 0 &&
+ priv->height > 0) {
+ eog_image_emit_size_prepared (img);
+ }
+ }
+#else
+ if (priv->exif_chunk) {
+ g_free (priv->exif_chunk);
+ }
+ eog_metadata_reader_get_exif_chunk (md_reader,
+ &priv->exif_chunk,
+ &priv->exif_chunk_len);
+#endif
+}
+
+static gboolean
+eog_image_real_load (EogImage *img,
+ guint data2read,
+ EogJob *job,
+ GError **error)
+{
+ EogImagePrivate *priv;
+ GFileInputStream *input_stream;
+ EogMetadataReader *md_reader = NULL;
+ GdkPixbufFormat *format;
+ gchar *mime_type;
+ GdkPixbufLoader *loader = NULL;
+ guchar *buffer;
+ goffset bytes_read, bytes_read_total = 0;
+ gboolean failed = FALSE;
+ gboolean first_run = TRUE;
+ gboolean set_metadata = TRUE;
+ gboolean use_rsvg = FALSE;
+ gboolean read_image_data = (data2read & EOG_IMAGE_DATA_IMAGE);
+ gboolean read_only_dimension = (data2read & EOG_IMAGE_DATA_DIMENSION) &&
+ ((data2read ^ EOG_IMAGE_DATA_DIMENSION) == 0);
+
+
+ priv = img->priv;
+
+ g_assert (!read_image_data || priv->image == NULL);
+
+ if (read_image_data && priv->file_type != NULL) {
+ g_free (priv->file_type);
+ priv->file_type = NULL;
+ }
+
+ priv->threadsafe_format = FALSE;
+
+ eog_image_get_file_info (img, &priv->bytes, &mime_type, error);
+
+ if (error && *error) {
+ g_free (mime_type);
+ return FALSE;
+ }
+
+ input_stream = g_file_read (priv->file, NULL, error);
+
+ if (input_stream == NULL) {
+ g_free (mime_type);
+
+ if (error != NULL) {
+ g_clear_error (error);
+ g_set_error (error,
+ EOG_IMAGE_ERROR,
+ EOG_IMAGE_ERROR_VFS,
+ "Failed to open input stream for file");
+ }
+ return FALSE;
+ }
+
+ buffer = g_new0 (guchar, EOG_IMAGE_READ_BUFFER_SIZE);
+
+ if (read_image_data || read_only_dimension) {
+ gboolean checked_threadsafety = FALSE;
+
+#ifdef HAVE_RSVG
+ if (priv->svg != NULL) {
+ g_object_unref (priv->svg);
+ priv->svg = NULL;
+ }
+
+ if (!strcmp (mime_type, "image/svg+xml")
+#if LIBRSVG_CHECK_FEATURE(SVGZ)
+ || !strcmp (mime_type, "image/svg+xml-compressed")
+#endif
+ ) {
+ gchar *file_path;
+ /* Keep the object for rendering */
+ priv->svg = rsvg_handle_new ();
+ use_rsvg = (priv->svg != NULL);
+ file_path = g_file_get_path (priv->file);
+ rsvg_handle_set_base_uri (priv->svg, file_path);
+ g_free (file_path);
+ }
+#endif
+
+ if (!use_rsvg) {
+ loader = gdk_pixbuf_loader_new_with_mime_type (mime_type, error);
+
+ if (error && *error) {
+ g_error_free (*error);
+ *error = NULL;
+
+ loader = gdk_pixbuf_loader_new ();
+ } else {
+ /* The mimetype-based loader should know the
+ * format here already. */
+ checked_threadsafety = check_loader_threadsafety (loader, &priv->threadsafe_format);
+ }
+
+ /* This is used to detect non-threadsafe loaders and disable
+ * any possible asyncronous task that could bring deadlocks
+ * to image loading process. */
+ if (!checked_threadsafety)
+ g_signal_connect (loader,
+ "size-prepared",
+ G_CALLBACK (eog_image_pre_size_prepared),
+ img);
+
+ g_signal_connect_object (G_OBJECT (loader),
+ "size-prepared",
+ G_CALLBACK (eog_image_size_prepared),
+ img,
+ 0);
+ }
+ }
+ g_free (mime_type);
+
+ while (!priv->cancel_loading) {
+ /* FIXME: make this async */
+ bytes_read = g_input_stream_read (G_INPUT_STREAM (input_stream),
+ buffer,
+ EOG_IMAGE_READ_BUFFER_SIZE,
+ NULL, error);
+
+ if (bytes_read == 0) {
+ /* End of the file */
+ break;
+ } else if (bytes_read == -1) {
+ failed = TRUE;
+
+ g_set_error (error,
+ EOG_IMAGE_ERROR,
+ EOG_IMAGE_ERROR_VFS,
+ "Failed to read from input stream");
+
+ break;
+ }
+
+ if ((read_image_data || read_only_dimension)) {
+#ifdef HAVE_RSVG
+ if (use_rsvg) {
+ gboolean res;
+
+ res = rsvg_handle_write (priv->svg, buffer,
+ bytes_read, error);
+
+ if (G_UNLIKELY (!res)) {
+ failed = TRUE;
+ break;
+ }
+ } else
+#endif
+ if (!gdk_pixbuf_loader_write (loader, buffer, bytes_read, error)) {
+ failed = TRUE;
+ break;
+ }
+ }
+
+ bytes_read_total += bytes_read;
+
+ if (job != NULL) {
+ float progress = (float) bytes_read_total / (float) priv->bytes;
+ eog_job_set_progress (job, progress);
+ }
+
+ if (first_run) {
+ md_reader = check_for_metadata_img_format (img, buffer, bytes_read);
+
+ if (md_reader == NULL) {
+ if (data2read == EOG_IMAGE_DATA_EXIF) {
+ g_set_error (error,
+ EOG_IMAGE_ERROR,
+ EOG_IMAGE_ERROR_GENERIC,
+ _("EXIF not supported for this file format."));
+ break;
+ }
+
+ if (priv->threadsafe_format)
+ eog_image_emit_size_prepared (img);
+
+ priv->metadata_status = EOG_IMAGE_METADATA_NOT_AVAILABLE;
+ }
+
+ first_run = FALSE;
+ }
+
+ if (md_reader != NULL) {
+ eog_metadata_reader_consume (md_reader, buffer, bytes_read);
+
+ if (eog_metadata_reader_finished (md_reader)) {
+ if (set_metadata) {
+ eog_image_set_exif_data (img, md_reader);
+
+#ifdef HAVE_LCMS
+ eog_image_set_icc_data (img, md_reader);
+#endif
+
+#ifdef HAVE_EXEMPI
+ eog_image_set_xmp_data (img, md_reader);
+#endif
+ set_metadata = FALSE;
+ priv->metadata_status = EOG_IMAGE_METADATA_READY;
+ }
+
+ if (data2read == EOG_IMAGE_DATA_EXIF)
+ break;
+ }
+ }
+
+ if (read_only_dimension &&
+ eog_image_has_data (img, EOG_IMAGE_DATA_DIMENSION)) {
+ break;
+ }
+ }
+
+ if (read_image_data || read_only_dimension) {
+#ifdef HAVE_RSVG
+ if (use_rsvg) {
+ rsvg_handle_close (priv->svg, error);
+ } else
+#endif
+ if (failed) {
+ gdk_pixbuf_loader_close (loader, NULL);
+ } else if (!gdk_pixbuf_loader_close (loader, error)) {
+ if (gdk_pixbuf_loader_get_pixbuf (loader) != NULL) {
+ /* Clear error in order to support partial
+ * images as well. */
+ g_clear_error (error);
+ }
+ }
+ }
+
+ g_free (buffer);
+
+ g_object_unref (G_OBJECT (input_stream));
+
+ failed = (failed ||
+ priv->cancel_loading ||
+ bytes_read_total == 0 ||
+ (error && *error != NULL));
+
+ if (failed) {
+ if (priv->cancel_loading) {
+ priv->cancel_loading = FALSE;
+ priv->status = EOG_IMAGE_STATUS_UNKNOWN;
+ } else {
+ priv->status = EOG_IMAGE_STATUS_FAILED;
+ }
+ } else if (read_image_data) {
+ if (priv->image != NULL) {
+ g_object_unref (priv->image);
+ }
+
+#ifdef HAVE_RSVG
+ if (use_rsvg) {
+ priv->image = rsvg_handle_get_pixbuf (priv->svg);
+ } else
+#endif
+ {
+
+ priv->anim = gdk_pixbuf_loader_get_animation (loader);
+
+ if (gdk_pixbuf_animation_is_static_image (priv->anim)) {
+ priv->image = gdk_pixbuf_animation_get_static_image (priv->anim);
+ priv->anim = NULL;
+ } else {
+ priv->anim_iter = gdk_pixbuf_animation_get_iter (priv->anim,NULL);
+ priv->image = gdk_pixbuf_animation_iter_get_pixbuf (priv->anim_iter);
+ }
+
+ }
+
+ if (G_LIKELY (priv->image != NULL)) {
+ if (!use_rsvg)
+ g_object_ref (priv->image);
+
+ priv->width = gdk_pixbuf_get_width (priv->image);
+ priv->height = gdk_pixbuf_get_height (priv->image);
+
+ if (use_rsvg) {
+ format = NULL;
+ priv->file_type = g_strdup ("svg");
+ } else {
+ format = gdk_pixbuf_loader_get_format (loader);
+ }
+
+ if (format != NULL) {
+ priv->file_type = gdk_pixbuf_format_get_name (format);
+ }
+
+ priv->file_is_changed = FALSE;
+
+ /* Set orientation again for safety, eg. if we don't
+ * have Exif data or HAVE_EXIF is undefined. */
+ eog_image_set_orientation (img);
+
+ /* If it's non-threadsafe loader, then trigger window
+ * showing in the end of the process. */
+ if (!priv->threadsafe_format)
+ eog_image_emit_size_prepared (img);
+ } else {
+ /* Some loaders don't report errors correctly.
+ * Error will be set below. */
+ failed = TRUE;
+ priv->status = EOG_IMAGE_STATUS_FAILED;
+ }
+ }
+
+ if (loader != NULL) {
+ g_object_unref (loader);
+ }
+
+ if (md_reader != NULL) {
+ g_object_unref (md_reader);
+ md_reader = NULL;
+ }
+
+ /* Catch-all in case of poor-error reporting */
+ if (failed && error && *error == NULL) {
+ g_set_error (error,
+ EOG_IMAGE_ERROR,
+ EOG_IMAGE_ERROR_GENERIC,
+ _("Image loading failed."));
+ }
+
+ return !failed;
+}
+
+gboolean
+eog_image_has_data (EogImage *img, EogImageData req_data)
+{
+ EogImagePrivate *priv;
+ gboolean has_data = TRUE;
+
+ g_return_val_if_fail (EOG_IS_IMAGE (img), FALSE);
+
+ priv = img->priv;
+
+ if ((req_data & EOG_IMAGE_DATA_IMAGE) > 0) {
+ req_data = (req_data & ~EOG_IMAGE_DATA_IMAGE);
+ has_data = has_data && (priv->image != NULL);
+ }
+
+ if ((req_data & EOG_IMAGE_DATA_DIMENSION) > 0 ) {
+ req_data = (req_data & ~EOG_IMAGE_DATA_DIMENSION);
+ has_data = has_data && (priv->width >= 0) && (priv->height >= 0);
+ }
+
+ if ((req_data & EOG_IMAGE_DATA_EXIF) > 0) {
+ req_data = (req_data & ~EOG_IMAGE_DATA_EXIF);
+#ifdef HAVE_EXIF
+ has_data = has_data && (priv->exif != NULL);
+#else
+ has_data = has_data && (priv->exif_chunk != NULL);
+#endif
+ }
+
+ if ((req_data & EOG_IMAGE_DATA_XMP) > 0) {
+ req_data = (req_data & ~EOG_IMAGE_DATA_XMP);
+#ifdef HAVE_EXEMPI
+ has_data = has_data && (priv->xmp != NULL);
+#endif
+ }
+
+ if (req_data != 0) {
+ g_warning ("Asking for unknown data, remaining: %i\n", req_data);
+ has_data = FALSE;
+ }
+
+ return has_data;
+}
+
+gboolean
+eog_image_load (EogImage *img, EogImageData data2read, EogJob *job, GError **error)
+{
+ EogImagePrivate *priv;
+ gboolean success = FALSE;
+
+ eog_debug (DEBUG_IMAGE_LOAD);
+
+ g_return_val_if_fail (EOG_IS_IMAGE (img), FALSE);
+
+ priv = EOG_IMAGE (img)->priv;
+
+ if (data2read == 0) {
+ return TRUE;
+ }
+
+ if (eog_image_has_data (img, data2read)) {
+ return TRUE;
+ }
+
+ priv->status = EOG_IMAGE_STATUS_LOADING;
+
+ success = eog_image_real_load (img, data2read, job, error);
+
+
+ /* Check that the metadata was loaded at least once before
+ * trying to autorotate. Also only an image load job should try to
+ * autorotate an image. */
+ if (priv->autorotate &&
+#ifdef HAVE_EXIF
+ priv->metadata_status != EOG_IMAGE_METADATA_NOT_READ &&
+#endif
+ data2read & EOG_IMAGE_DATA_IMAGE) {
+ eog_image_real_autorotate (img);
+ }
+
+ if (success && eog_image_needs_transformation (img)) {
+ success = eog_image_apply_transformations (img, error);
+ }
+
+ if (success) {
+ priv->status = EOG_IMAGE_STATUS_LOADED;
+ } else {
+ priv->status = EOG_IMAGE_STATUS_FAILED;
+ }
+
+ return success;
+}
+
+/**
+ * eog_image_get_pixbuf:
+ * @img: a #EogImage
+ *
+ * Gets the #GdkPixbuf of the image
+ *
+ * Returns: (transfer full): a #GdkPixbuf
+ **/
+GdkPixbuf *
+eog_image_get_pixbuf (EogImage *img)
+{
+ GdkPixbuf *image = NULL;
+
+ g_return_val_if_fail (EOG_IS_IMAGE (img), NULL);
+
+ g_mutex_lock (&img->priv->status_mutex);
+ image = img->priv->image;
+ g_mutex_unlock (&img->priv->status_mutex);
+
+ if (image != NULL) {
+ g_object_ref (image);
+ }
+
+ return image;
+}
+
+#ifdef HAVE_LCMS
+cmsHPROFILE
+eog_image_get_profile (EogImage *img)
+{
+ g_return_val_if_fail (EOG_IS_IMAGE (img), NULL);
+
+ return img->priv->profile;
+}
+#endif
+
+void
+eog_image_get_size (EogImage *img, int *width, int *height)
+{
+ EogImagePrivate *priv;
+
+ g_return_if_fail (EOG_IS_IMAGE (img));
+
+ priv = img->priv;
+
+ *width = priv->width;
+ *height = priv->height;
+}
+
+void
+eog_image_transform (EogImage *img, EogTransform *trans, EogJob *job)
+{
+ eog_image_real_transform (img, trans, FALSE, job);
+}
+
+void
+eog_image_undo (EogImage *img)
+{
+ EogImagePrivate *priv;
+ EogTransform *trans;
+ EogTransform *inverse;
+
+ g_return_if_fail (EOG_IS_IMAGE (img));
+
+ priv = img->priv;
+
+ if (priv->undo_stack != NULL) {
+ trans = EOG_TRANSFORM (priv->undo_stack->data);
+
+ inverse = eog_transform_reverse (trans);
+
+ eog_image_real_transform (img, inverse, TRUE, NULL);
+
+ priv->undo_stack = g_slist_delete_link (priv->undo_stack, priv->undo_stack);
+
+ g_object_unref (trans);
+ g_object_unref (inverse);
+
+ if (eog_transform_is_identity (priv->trans)) {
+ g_object_unref (priv->trans);
+ priv->trans = NULL;
+ }
+ }
+
+ priv->modified = (priv->undo_stack != NULL);
+}
+
+static GFile *
+tmp_file_get (void)
+{
+ GFile *tmp_file;
+ char *tmp_file_path;
+ gint fd;
+
+ tmp_file_path = g_build_filename (g_get_tmp_dir (), "eog-save-XXXXXX", NULL);
+ fd = g_mkstemp (tmp_file_path);
+ if (fd == -1) {
+ g_free (tmp_file_path);
+ return NULL;
+ }
+ else {
+ tmp_file = g_file_new_for_path (tmp_file_path);
+ g_free (tmp_file_path);
+ return tmp_file;
+ }
+}
+
+static void
+transfer_progress_cb (goffset cur_bytes,
+ goffset total_bytes,
+ gpointer user_data)
+{
+ EogImage *image = EOG_IMAGE (user_data);
+
+ if (cur_bytes > 0) {
+ g_signal_emit (G_OBJECT(image),
+ signals[SIGNAL_SAVE_PROGRESS],
+ 0,
+ (gfloat) cur_bytes / (gfloat) total_bytes);
+ }
+}
+
+static void
+tmp_file_restore_unix_attributes (GFile *temp_file,
+ GFile *target_file)
+{
+ GFileInfo *file_info;
+ guint uid;
+ guint gid;
+ guint mode;
+ guint mode_mask = 00600;
+
+ GError *error = NULL;
+
+ g_return_if_fail (G_IS_FILE (temp_file));
+ g_return_if_fail (G_IS_FILE (target_file));
+
+ /* check if file exists */
+ if (!g_file_query_exists (target_file, NULL)) {
+ eog_debug_message (DEBUG_IMAGE_SAVE,
+ "Target file doesn't exist. Setting default attributes.");
+ return;
+ }
+
+ /* retrieve UID, GID, and MODE of the original file info */
+ file_info = g_file_query_info (target_file,
+ "unix::uid,unix::gid,unix::mode",
+ G_FILE_QUERY_INFO_NONE,
+ NULL,
+ &error);
+
+ /* check that there aren't any error */
+ if (error != NULL) {
+ eog_debug_message (DEBUG_IMAGE_SAVE,
+ "File information not available. Setting default attributes.");
+
+ /* free objects */
+ g_object_unref (file_info);
+ g_clear_error (&error);
+
+ return;
+ }
+
+ /* save UID, GID and MODE values */
+ uid = g_file_info_get_attribute_uint32 (file_info,
+ G_FILE_ATTRIBUTE_UNIX_UID);
+
+ gid = g_file_info_get_attribute_uint32 (file_info,
+ G_FILE_ATTRIBUTE_UNIX_GID);
+
+ mode = g_file_info_get_attribute_uint32 (file_info,
+ G_FILE_ATTRIBUTE_UNIX_MODE);
+
+ /* apply default mode mask to file mode */
+ mode |= mode_mask;
+
+ /* restore original UID, GID, and MODE into the temporal file */
+ g_file_set_attribute_uint32 (temp_file,
+ G_FILE_ATTRIBUTE_UNIX_UID,
+ uid,
+ G_FILE_QUERY_INFO_NONE,
+ NULL,
+ &error);
+
+ /* check that there aren't any error */
+ if (error != NULL) {
+ eog_debug_message (DEBUG_IMAGE_SAVE,
+ "You do not have the permissions necessary to change the file UID.");
+
+ g_clear_error (&error);
+ }
+
+ g_file_set_attribute_uint32 (temp_file,
+ G_FILE_ATTRIBUTE_UNIX_GID,
+ gid,
+ G_FILE_QUERY_INFO_NONE,
+ NULL,
+ &error);
+
+ /* check that there aren't any error */
+ if (error != NULL) {
+ eog_debug_message (DEBUG_IMAGE_SAVE,
+ "You do not have the permissions necessary to change the file GID. Setting user default GID.");
+
+ g_clear_error (&error);
+ }
+
+ g_file_set_attribute_uint32 (temp_file,
+ G_FILE_ATTRIBUTE_UNIX_MODE,
+ mode,
+ G_FILE_QUERY_INFO_NONE,
+ NULL,
+ &error);
+
+ /* check that there aren't any error */
+ if (error != NULL) {
+ eog_debug_message (DEBUG_IMAGE_SAVE,
+ "You do not have the permissions necessary to change the file MODE.");
+
+ g_clear_error (&error);
+ }
+
+ /* free objects */
+ g_object_unref (file_info);
+}
+
+static gboolean
+tmp_file_move_to_uri (EogImage *image,
+ GFile *tmpfile,
+ GFile *file,
+ gboolean overwrite,
+ GError **error)
+{
+ gboolean result;
+ GError *ioerror = NULL;
+
+ /* try to restore target file unix attributes */
+ tmp_file_restore_unix_attributes (tmpfile, file);
+
+ /* replace target file with temporal file */
+ result = g_file_move (tmpfile,
+ file,
+ (overwrite ? G_FILE_COPY_OVERWRITE : 0) |
+ G_FILE_COPY_ALL_METADATA,
+ NULL,
+ (GFileProgressCallback) transfer_progress_cb,
+ image,
+ &ioerror);
+
+ if (result == FALSE) {
+ if (g_error_matches (ioerror, G_IO_ERROR,
+ G_IO_ERROR_EXISTS)) {
+ g_set_error (error, EOG_IMAGE_ERROR,
+ EOG_IMAGE_ERROR_FILE_EXISTS,
+ "File exists");
+ } else {
+ g_set_error (error, EOG_IMAGE_ERROR,
+ EOG_IMAGE_ERROR_VFS,
+ "VFS error moving the temp file");
+ }
+ g_clear_error (&ioerror);
+ }
+
+ return result;
+}
+
+static gboolean
+tmp_file_delete (GFile *tmpfile)
+{
+ gboolean result;
+ GError *err = NULL;
+
+ if (tmpfile == NULL) return FALSE;
+
+ result = g_file_delete (tmpfile, NULL, &err);
+ if (result == FALSE) {
+ char *tmpfile_path;
+ if (err != NULL) {
+ if (err->code == G_IO_ERROR_NOT_FOUND) {
+ g_error_free (err);
+ return TRUE;
+ }
+ g_error_free (err);
+ }
+ tmpfile_path = g_file_get_path (tmpfile);
+ g_warning ("Couldn't delete temporary file: %s", tmpfile_path);
+ g_free (tmpfile_path);
+ }
+
+ return result;
+}
+
+static void
+eog_image_reset_modifications (EogImage *image)
+{
+ EogImagePrivate *priv;
+
+ g_return_if_fail (EOG_IS_IMAGE (image));
+
+ priv = image->priv;
+
+ g_slist_foreach (priv->undo_stack, (GFunc) g_object_unref, NULL);
+ g_slist_free (priv->undo_stack);
+ priv->undo_stack = NULL;
+
+ if (priv->trans != NULL) {
+ g_object_unref (priv->trans);
+ priv->trans = NULL;
+ }
+
+ if (priv->trans_autorotate != NULL) {
+ g_object_unref (priv->trans_autorotate);
+ priv->trans_autorotate = NULL;
+ }
+
+ priv->modified = FALSE;
+}
+
+static void
+eog_image_link_with_target (EogImage *image, EogImageSaveInfo *target)
+{
+ EogImagePrivate *priv;
+
+ g_return_if_fail (EOG_IS_IMAGE (image));
+ g_return_if_fail (EOG_IS_IMAGE_SAVE_INFO (target));
+
+ priv = image->priv;
+
+ /* update file location */
+ if (priv->file != NULL) {
+ g_object_unref (priv->file);
+ }
+ priv->file = g_object_ref (target->file);
+
+ /* Clear caption and caption key, these will be
+ * updated on next eog_image_get_caption call.
+ */
+ if (priv->caption != NULL) {
+ g_free (priv->caption);
+ priv->caption = NULL;
+ }
+ if (priv->collate_key != NULL) {
+ g_free (priv->collate_key);
+ priv->collate_key = NULL;
+ }
+
+ /* update file format */
+ if (priv->file_type != NULL) {
+ g_free (priv->file_type);
+ }
+ priv->file_type = g_strdup (target->format);
+}
+
+static gboolean
+check_if_file_is_writable (GFile *file)
+{
+ GFile *file_to_check;
+ GFileInfo *file_info;
+ GError *error = NULL;
+ gboolean is_writable;
+
+ g_return_val_if_fail (G_IS_FILE (file), FALSE);
+
+ /* check if file exists */
+ if (!g_file_query_exists (file, NULL)) {
+ eog_debug_message (DEBUG_IMAGE_SAVE, "File doesn't exist. "
+ "Checking parent directory.");
+
+ file_to_check = g_file_get_parent (file);
+ } else {
+ /* Add another ref so we don't need to split between file and
+ * parent file again after querying and can simply unref it. */
+ file_to_check = g_object_ref (file);
+ }
+
+ /* recover file information */
+ file_info = g_file_query_info (file_to_check,
+ G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE,
+ G_FILE_QUERY_INFO_NONE,
+ NULL,
+ &error);
+
+ /* we assume that the image can't be saved when
+ we can't retrieve any file information */
+ if (G_UNLIKELY (file_info == NULL)) {
+ eog_debug_message (DEBUG_IMAGE_SAVE,
+ "Couldn't query file info: %s",
+ error->message);
+ g_error_free (error);
+ g_object_unref (file_to_check);
+ return FALSE;
+ }
+
+ /* check if file can be writed */
+ is_writable = g_file_info_get_attribute_boolean (file_info,
+ G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE);
+
+ /* free objects */
+ g_object_unref (file_info);
+ g_object_unref (file_to_check);
+
+ return is_writable;
+}
+
+gboolean
+eog_image_save_by_info (EogImage *img, EogImageSaveInfo *source, GError **error)
+{
+ EogImagePrivate *priv;
+ EogImageStatus prev_status;
+ gboolean success = FALSE;
+ GFile *tmp_file;
+ char *tmp_file_path;
+
+ g_return_val_if_fail (EOG_IS_IMAGE (img), FALSE);
+ g_return_val_if_fail (EOG_IS_IMAGE_SAVE_INFO (source), FALSE);
+
+ priv = img->priv;
+
+ prev_status = priv->status;
+
+ /* Image is now being saved */
+ priv->status = EOG_IMAGE_STATUS_SAVING;
+
+ /* see if we need any saving at all */
+ if (source->exists && !source->modified) {
+ return TRUE;
+ }
+
+ /* fail if there is no image to save */
+ if (priv->image == NULL) {
+ g_set_error (error, EOG_IMAGE_ERROR,
+ EOG_IMAGE_ERROR_NOT_LOADED,
+ _("No image loaded."));
+ return FALSE;
+ }
+
+ /* fail if there is not write rights to save */
+ if (!check_if_file_is_writable (priv->file)) {
+ g_set_error (error, EOG_IMAGE_ERROR,
+ EOG_IMAGE_ERROR_NOT_SAVED,
+ _("You do not have the permissions necessary to save the file."));
+ return FALSE;
+ }
+
+ /* generate temporary file */
+ tmp_file = tmp_file_get ();
+
+ if (tmp_file == NULL) {
+ g_set_error (error, EOG_IMAGE_ERROR,
+ EOG_IMAGE_ERROR_TMP_FILE_FAILED,
+ _("Temporary file creation failed."));
+ return FALSE;
+ }
+
+ tmp_file_path = g_file_get_path (tmp_file);
+
+#ifdef HAVE_JPEG
+ /* determine kind of saving */
+ if ((g_ascii_strcasecmp (source->format, EOG_FILE_FORMAT_JPEG) == 0) &&
+ source->exists && source->modified)
+ {
+ success = eog_image_jpeg_save_file (img, tmp_file_path, source, NULL, error);
+ }
+#endif
+
+ if (!success && (*error == NULL)) {
+ success = gdk_pixbuf_save (priv->image, tmp_file_path, source->format, error, NULL);
+ }
+
+ if (success) {
+ /* try to move result file to target uri */
+ success = tmp_file_move_to_uri (img, tmp_file, priv->file, TRUE /*overwrite*/, error);
+ }
+
+ if (success) {
+ eog_image_reset_modifications (img);
+ }
+
+ tmp_file_delete (tmp_file);
+
+ g_free (tmp_file_path);
+ g_object_unref (tmp_file);
+
+ priv->status = prev_status;
+
+ return success;
+}
+
+static gboolean
+eog_image_copy_file (EogImage *image, EogImageSaveInfo *source, EogImageSaveInfo *target, GError **error)
+{
+ gboolean result;
+ GError *ioerror = NULL;
+
+ g_return_val_if_fail (EOG_IS_IMAGE_SAVE_INFO (source), FALSE);
+ g_return_val_if_fail (EOG_IS_IMAGE_SAVE_INFO (target), FALSE);
+
+ /* copy the image */
+ result = g_file_copy (source->file,
+ target->file,
+ (target->overwrite ? G_FILE_COPY_OVERWRITE : 0) |
+ G_FILE_COPY_ALL_METADATA,
+ NULL,
+ EOG_IS_IMAGE (image) ? transfer_progress_cb :NULL,
+ image,
+ &ioerror);
+
+ if (result == FALSE) {
+ if (ioerror->code == G_IO_ERROR_EXISTS) {
+ g_set_error (error, EOG_IMAGE_ERROR,
+ EOG_IMAGE_ERROR_FILE_EXISTS,
+ "%s", ioerror->message);
+ } else {
+ g_set_error (error, EOG_IMAGE_ERROR,
+ EOG_IMAGE_ERROR_VFS,
+ "%s", ioerror->message);
+ }
+ g_error_free (ioerror);
+ } else {
+ /* reset NAUTILUS-ICON-POSITION metadata attribute */
+ g_file_set_attribute (target->file,
+ "metadata::nautilus-icon-position",
+ G_FILE_ATTRIBUTE_TYPE_INVALID,
+ NULL,
+ G_FILE_QUERY_INFO_NONE,
+ NULL,
+ NULL);
+ }
+
+ return result;
+}
+
+gboolean
+eog_image_save_as_by_info (EogImage *img, EogImageSaveInfo *source, EogImageSaveInfo *target, GError **error)
+{
+ EogImagePrivate *priv;
+ gboolean success = FALSE;
+ char *tmp_file_path;
+ GFile *tmp_file;
+ gboolean direct_copy = FALSE;
+
+ g_return_val_if_fail (EOG_IS_IMAGE (img), FALSE);
+ g_return_val_if_fail (EOG_IS_IMAGE_SAVE_INFO (source), FALSE);
+ g_return_val_if_fail (EOG_IS_IMAGE_SAVE_INFO (target), FALSE);
+
+ priv = img->priv;
+
+ /* fail if there is no image to save */
+ if (priv->image == NULL) {
+ g_set_error (error,
+ EOG_IMAGE_ERROR,
+ EOG_IMAGE_ERROR_NOT_LOADED,
+ _("No image loaded."));
+
+ return FALSE;
+ }
+
+ /* fail if there is not write rights to save on target */
+ if (!check_if_file_is_writable (target->file)) {
+ g_set_error (error, EOG_IMAGE_ERROR,
+ EOG_IMAGE_ERROR_NOT_SAVED,
+ _("You do not have the permissions necessary to save the file."));
+ return FALSE;
+ }
+
+ /* generate temporary file name */
+ tmp_file = tmp_file_get ();
+
+ if (tmp_file == NULL) {
+ g_set_error (error,
+ EOG_IMAGE_ERROR,
+ EOG_IMAGE_ERROR_TMP_FILE_FAILED,
+ _("Temporary file creation failed."));
+
+ return FALSE;
+ }
+ tmp_file_path = g_file_get_path (tmp_file);
+
+ /* determine kind of saving */
+ if (g_ascii_strcasecmp (source->format, target->format) == 0 && !source->modified) {
+ success = eog_image_copy_file (img, source, target, error);
+ direct_copy = success;
+ }
+
+#ifdef HAVE_JPEG
+ else if ((g_ascii_strcasecmp (source->format, EOG_FILE_FORMAT_JPEG) == 0 && source->exists) ||
+ (g_ascii_strcasecmp (target->format, EOG_FILE_FORMAT_JPEG) == 0))
+ {
+ success = eog_image_jpeg_save_file (img, tmp_file_path, source, target, error);
+ }
+#endif
+
+ if (!success && (*error == NULL)) {
+ success = gdk_pixbuf_save (priv->image, tmp_file_path, target->format, error, NULL);
+ }
+
+ if (success && !direct_copy) { /* not required if we alredy copied the file directly */
+ /* try to move result file to target uri */
+ success = tmp_file_move_to_uri (img, tmp_file, target->file, target->overwrite, error);
+ }
+
+ if (success) {
+ /* update image information to new uri */
+ eog_image_reset_modifications (img);
+ eog_image_link_with_target (img, target);
+ }
+
+ tmp_file_delete (tmp_file);
+ g_object_unref (tmp_file);
+ g_free (tmp_file_path);
+
+ priv->status = EOG_IMAGE_STATUS_UNKNOWN;
+
+ return success;
+}
+
+
+/*
+ * This function is extracted from
+ * File: nautilus/libnautilus-private/nautilus-file.c
+ * Revision: 1.309
+ * Author: Darin Adler <darin bentspoon com>
+ */
+static gboolean
+have_broken_filenames (void)
+{
+ static gboolean initialized = FALSE;
+ static gboolean broken;
+
+ if (initialized) {
+ return broken;
+ }
+
+ broken = g_getenv ("G_BROKEN_FILENAMES") != NULL;
+
+ initialized = TRUE;
+
+ return broken;
+}
+
+/*
+ * This function is inspired by
+ * nautilus/libnautilus-private/nautilus-file.c:nautilus_file_get_display_name_nocopy
+ * Revision: 1.309
+ * Author: Darin Adler <darin bentspoon com>
+ */
+const gchar*
+eog_image_get_caption (EogImage *img)
+{
+ EogImagePrivate *priv;
+ char *name;
+ char *utf8_name;
+ char *scheme;
+ gboolean validated = FALSE;
+ gboolean broken_filenames;
+
+ g_return_val_if_fail (EOG_IS_IMAGE (img), NULL);
+
+ priv = img->priv;
+
+ if (priv->file == NULL) return NULL;
+
+ if (priv->caption != NULL)
+ /* Use cached caption string */
+ return priv->caption;
+
+ name = g_file_get_basename (priv->file);
+ scheme = g_file_get_uri_scheme (priv->file);
+
+ if (name != NULL && g_ascii_strcasecmp (scheme, "file") == 0) {
+ /* Support the G_BROKEN_FILENAMES feature of
+ * glib by using g_filename_to_utf8 to convert
+ * local filenames to UTF-8. Also do the same
+ * thing with any local filename that does not
+ * validate as good UTF-8.
+ */
+ broken_filenames = have_broken_filenames ();
+
+ if (broken_filenames || !g_utf8_validate (name, -1, NULL)) {
+ utf8_name = g_locale_to_utf8 (name, -1, NULL, NULL, NULL);
+ if (utf8_name != NULL) {
+ g_free (name);
+ name = utf8_name;
+ /* Guaranteed to be correct utf8 here */
+ validated = TRUE;
+ }
+ } else if (!broken_filenames) {
+ /* name was valid, no need to re-validate */
+ validated = TRUE;
+ }
+ }
+
+ if (!validated && !g_utf8_validate (name, -1, NULL)) {
+ if (name == NULL) {
+ name = g_strdup ("[Invalid Unicode]");
+ } else {
+ utf8_name = eog_util_make_valid_utf8 (name);
+ g_free (name);
+ name = utf8_name;
+ }
+ }
+
+ priv->caption = name;
+
+ if (priv->caption == NULL) {
+ char *short_str;
+
+ short_str = g_file_get_basename (priv->file);
+ if (g_utf8_validate (short_str, -1, NULL)) {
+ priv->caption = g_strdup (short_str);
+ } else {
+ priv->caption = g_filename_to_utf8 (short_str, -1, NULL, NULL, NULL);
+ }
+ g_free (short_str);
+ }
+ g_free (scheme);
+
+ return priv->caption;
+}
+
+const gchar*
+eog_image_get_collate_key (EogImage *img)
+{
+ EogImagePrivate *priv;
+
+ g_return_val_if_fail (EOG_IS_IMAGE (img), NULL);
+
+ priv = img->priv;
+
+ if (priv->collate_key == NULL) {
+ const char *caption;
+
+ caption = eog_image_get_caption (img);
+
+ priv->collate_key = g_utf8_collate_key_for_filename (caption, -1);
+ }
+
+ return priv->collate_key;
+}
+
+void
+eog_image_cancel_load (EogImage *img)
+{
+ EogImagePrivate *priv;
+
+ g_return_if_fail (EOG_IS_IMAGE (img));
+
+ priv = img->priv;
+
+ g_mutex_lock (&priv->status_mutex);
+
+ if (priv->status == EOG_IMAGE_STATUS_LOADING) {
+ priv->cancel_loading = TRUE;
+ }
+
+ g_mutex_unlock (&priv->status_mutex);
+}
+
+#ifdef HAVE_EXIF
+ExifData *
+eog_image_get_exif_info (EogImage *img)
+{
+ EogImagePrivate *priv;
+ ExifData *data = NULL;
+
+ g_return_val_if_fail (EOG_IS_IMAGE (img), NULL);
+
+ priv = img->priv;
+
+ g_mutex_lock (&priv->status_mutex);
+
+ exif_data_ref (priv->exif);
+ data = priv->exif;
+
+ g_mutex_unlock (&priv->status_mutex);
+
+ return data;
+}
+#endif
+
+/**
+ * eog_image_get_xmp_info:
+ * @img: a #EogImage
+ *
+ * Gets the XMP info for @img or NULL if compiled without
+ * libexempi support.
+ *
+ * Returns: (transfer full): the xmp data
+ **/
+gpointer
+eog_image_get_xmp_info (EogImage *img)
+{
+ gpointer data = NULL;
+
+ g_return_val_if_fail (EOG_IS_IMAGE (img), NULL);
+
+#ifdef HAVE_EXEMPI
+ EogImagePrivate *priv;
+ priv = img->priv;
+
+ g_mutex_lock (&priv->status_mutex);
+ data = (gpointer) xmp_copy (priv->xmp);
+ g_mutex_unlock (&priv->status_mutex);
+#endif
+
+ return data;
+}
+
+
+/**
+ * eog_image_get_file:
+ * @img: a #EogImage
+ *
+ * Gets the #GFile associated with @img
+ *
+ * Returns: (transfer full): a #GFile
+ **/
+GFile *
+eog_image_get_file (EogImage *img)
+{
+ g_return_val_if_fail (EOG_IS_IMAGE (img), NULL);
+
+ return g_object_ref (img->priv->file);
+}
+
+gboolean
+eog_image_is_modified (EogImage *img)
+{
+ g_return_val_if_fail (EOG_IS_IMAGE (img), FALSE);
+
+ return img->priv->modified;
+}
+
+goffset
+eog_image_get_bytes (EogImage *img)
+{
+ g_return_val_if_fail (EOG_IS_IMAGE (img), 0);
+
+ return img->priv->bytes;
+}
+
+void
+eog_image_modified (EogImage *img)
+{
+ g_return_if_fail (EOG_IS_IMAGE (img));
+
+ g_signal_emit (G_OBJECT (img), signals[SIGNAL_CHANGED], 0);
+}
+
+gchar*
+eog_image_get_uri_for_display (EogImage *img)
+{
+ EogImagePrivate *priv;
+ gchar *uri_str = NULL;
+ gchar *str = NULL;
+
+ g_return_val_if_fail (EOG_IS_IMAGE (img), NULL);
+
+ priv = img->priv;
+
+ if (priv->file != NULL) {
+ uri_str = g_file_get_uri (priv->file);
+
+ if (uri_str != NULL) {
+ str = g_uri_unescape_string (uri_str, NULL);
+ g_free (uri_str);
+ }
+ }
+
+ return str;
+}
+
+EogImageStatus
+eog_image_get_status (EogImage *img)
+{
+ g_return_val_if_fail (EOG_IS_IMAGE (img), EOG_IMAGE_STATUS_UNKNOWN);
+
+ return img->priv->status;
+}
+
+/**
+ * eog_image_get_metadata_status:
+ * @img: a #EogImage
+ *
+ * Returns the current status of the image metadata, that is,
+ * whether the metadata has not been read yet, is ready, or not available at all.
+ *
+ * Returns: one of #EogImageMetadataStatus
+ **/
+EogImageMetadataStatus
+eog_image_get_metadata_status (EogImage *img)
+{
+ g_return_val_if_fail (EOG_IS_IMAGE (img), EOG_IMAGE_METADATA_NOT_AVAILABLE);
+
+ return img->priv->metadata_status;
+}
+
+void
+eog_image_data_ref (EogImage *img)
+{
+ g_return_if_fail (EOG_IS_IMAGE (img));
+
+ g_object_ref (G_OBJECT (img));
+ img->priv->data_ref_count++;
+
+ g_assert (img->priv->data_ref_count <= G_OBJECT (img)->ref_count);
+}
+
+void
+eog_image_data_unref (EogImage *img)
+{
+ g_return_if_fail (EOG_IS_IMAGE (img));
+
+ if (img->priv->data_ref_count > 0) {
+ img->priv->data_ref_count--;
+ } else {
+ g_warning ("More image data unrefs than refs.");
+ }
+
+ if (img->priv->data_ref_count == 0) {
+ eog_image_free_mem_private (img);
+ }
+
+ g_object_unref (G_OBJECT (img));
+
+ g_assert (img->priv->data_ref_count <= G_OBJECT (img)->ref_count);
+}
+
+static gint
+compare_quarks (gconstpointer a, gconstpointer b)
+{
+ GQuark quark;
+
+ quark = g_quark_from_string ((const gchar *) a);
+
+ return quark - GPOINTER_TO_INT (b);
+}
+
+/**
+ * eog_image_get_supported_mime_types:
+ *
+ * Gets the list of supported mimetypes
+ *
+ * Returns: (transfer none)(element-type utf8): a #GList of supported mimetypes
+ **/
+GList *
+eog_image_get_supported_mime_types (void)
+{
+ GSList *format_list, *it;
+ gchar **mime_types;
+ int i;
+
+ if (!supported_mime_types) {
+ format_list = gdk_pixbuf_get_formats ();
+
+ for (it = format_list; it != NULL; it = it->next) {
+ mime_types =
+ gdk_pixbuf_format_get_mime_types ((GdkPixbufFormat *) it->data);
+
+ for (i = 0; mime_types[i] != NULL; i++) {
+ supported_mime_types =
+ g_list_prepend (supported_mime_types,
+ g_strdup (mime_types[i]));
+ }
+
+ g_strfreev (mime_types);
+ }
+
+ supported_mime_types = g_list_sort (supported_mime_types,
+ (GCompareFunc) compare_quarks);
+
+ g_slist_free (format_list);
+ }
+
+ return supported_mime_types;
+}
+
+gboolean
+eog_image_is_supported_mime_type (const char *mime_type)
+{
+ GList *supported_mime_types, *result;
+ GQuark quark;
+
+ if (mime_type == NULL) {
+ return FALSE;
+ }
+
+ supported_mime_types = eog_image_get_supported_mime_types ();
+
+ quark = g_quark_from_string (mime_type);
+
+ result = g_list_find_custom (supported_mime_types,
+ GINT_TO_POINTER (quark),
+ (GCompareFunc) compare_quarks);
+
+ return (result != NULL);
+}
+
+static gboolean
+eog_image_iter_advance (EogImage *img)
+{
+ EogImagePrivate *priv;
+ gboolean new_frame;
+
+ g_return_val_if_fail (EOG_IS_IMAGE (img), FALSE);
+ g_return_val_if_fail (GDK_IS_PIXBUF_ANIMATION_ITER (img->priv->anim_iter), FALSE);
+
+ priv = img->priv;
+
+ if ((new_frame = gdk_pixbuf_animation_iter_advance (img->priv->anim_iter, NULL)) == TRUE)
+ {
+ g_mutex_lock (&priv->status_mutex);
+ g_object_unref (priv->image);
+ priv->image = gdk_pixbuf_animation_iter_get_pixbuf (priv->anim_iter);
+ g_object_ref (priv->image);
+ /* keep the transformation over time */
+ if (EOG_IS_TRANSFORM (priv->trans)) {
+ GdkPixbuf* transformed = eog_transform_apply (priv->trans, priv->image, NULL);
+ g_object_unref (priv->image);
+ priv->image = transformed;
+ priv->width = gdk_pixbuf_get_width (transformed);
+ priv->height = gdk_pixbuf_get_height (transformed);
+ }
+ g_mutex_unlock (&priv->status_mutex);
+ /* Emit next frame signal so we can update the display */
+ g_signal_emit (img, signals[SIGNAL_NEXT_FRAME], 0,
+ gdk_pixbuf_animation_iter_get_delay_time (priv->anim_iter));
+ }
+
+ return new_frame;
+}
+
+/**
+ * eog_image_is_animation:
+ * @img: a #EogImage
+ *
+ * Checks whether a given image is animated.
+ *
+ * Returns: #TRUE if it is an animated image, #FALSE otherwise.
+ *
+ **/
+gboolean
+eog_image_is_animation (EogImage *img)
+{
+ g_return_val_if_fail (EOG_IS_IMAGE (img), FALSE);
+ return img->priv->anim != NULL;
+}
+
+static gboolean
+private_timeout (gpointer data)
+{
+ EogImage *img = EOG_IMAGE (data);
+ EogImagePrivate *priv = img->priv;
+
+ if (eog_image_is_animation (img) &&
+ !g_source_is_destroyed (g_main_current_source ()) &&
+ priv->is_playing) {
+ while (eog_image_iter_advance (img) != TRUE) {}; /* cpu-sucking ? */
+ g_timeout_add (gdk_pixbuf_animation_iter_get_delay_time (priv->anim_iter), private_timeout, img);
+ return FALSE;
+ }
+ priv->is_playing = FALSE;
+ return FALSE; /* stop playing */
+}
+
+/**
+ * eog_image_start_animation:
+ * @img: a #EogImage
+ *
+ * Starts playing an animated image.
+ *
+ * Returns: %TRUE on success, %FALSE if @img is already playing or isn't an animated image.
+ **/
+gboolean
+eog_image_start_animation (EogImage *img)
+{
+ EogImagePrivate *priv;
+
+ g_return_val_if_fail (EOG_IS_IMAGE (img), FALSE);
+ priv = img->priv;
+
+ if (!eog_image_is_animation (img) || priv->is_playing)
+ return FALSE;
+
+ g_mutex_lock (&priv->status_mutex);
+ g_object_ref (priv->anim_iter);
+ priv->is_playing = TRUE;
+ g_mutex_unlock (&priv->status_mutex);
+
+ g_timeout_add (gdk_pixbuf_animation_iter_get_delay_time (priv->anim_iter), private_timeout, img);
+
+ return TRUE;
+}
+
+#ifdef HAVE_RSVG
+gboolean
+eog_image_is_svg (EogImage *img)
+{
+ g_return_val_if_fail (EOG_IS_IMAGE (img), FALSE);
+
+ return (img->priv->svg != NULL);
+}
+
+RsvgHandle *
+eog_image_get_svg (EogImage *img)
+{
+ g_return_val_if_fail (EOG_IS_IMAGE (img), NULL);
+
+ return img->priv->svg;
+}
+#endif
+
+
+EogTransform *
+eog_image_get_transform (EogImage *img)
+{
+ g_return_val_if_fail (EOG_IS_IMAGE (img), NULL);
+
+ return img->priv->trans;
+}
+
+EogTransform*
+eog_image_get_autorotate_transform (EogImage *img)
+{
+ g_return_val_if_fail (EOG_IS_IMAGE (img), NULL);
+
+ return img->priv->trans_autorotate;
+}
+
+/**
+ * eog_image_file_changed:
+ * @img: a #EogImage
+ *
+ * Marks the image file contents as changed. Also, emits
+ * EogImage::file-changed signal.
+ **/
+void
+eog_image_file_changed (EogImage *img)
+{
+ g_return_if_fail (EOG_IS_IMAGE (img));
+
+ img->priv->file_is_changed = TRUE;
+ g_signal_emit (img, signals[SIGNAL_FILE_CHANGED], 0);
+}
+
+gboolean
+eog_image_is_file_changed (EogImage *img)
+{
+ g_return_val_if_fail (EOG_IS_IMAGE (img), TRUE);
+
+ return img->priv->file_is_changed;
+}
+
+/**
+ * eog_image_is_file_writable:
+ * @img: a #EogImage
+ *
+ * Evaluate if the user has write permission on the image file.
+ *
+ * Returns: %TRUE on success, %FALSE if the user hasn't write permissions on it,
+ * or @img is not an #EogImage.
+ **/
+gboolean
+eog_image_is_file_writable (EogImage *img)
+{
+ gboolean is_writable;
+
+ g_return_val_if_fail (EOG_IS_IMAGE (img), FALSE);
+
+ is_writable = check_if_file_is_writable (img->priv->file);
+
+ return is_writable;
+}
+
+gboolean
+eog_image_is_jpeg (EogImage *img)
+{
+ g_return_val_if_fail (EOG_IS_IMAGE (img), FALSE);
+
+ return ((img->priv->file_type != NULL) && (g_ascii_strcasecmp (img->priv->file_type, EOG_FILE_FORMAT_JPEG) == 0));
+}
diff --git a/src/eog-image.h b/src/eog-image.h
new file mode 100644
index 0000000..3d1ed52
--- /dev/null
+++ b/src/eog-image.h
@@ -0,0 +1,220 @@
+/* Eye Of Gnome - Image
+ *
+ * Copyright (C) 2007 The Free Software Foundation
+ *
+ * Author: Lucas Rocha <lucasr gnome org>
+ *
+ * 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 Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __EOG_IMAGE_H__
+#define __EOG_IMAGE_H__
+
+#include "eog-jobs.h"
+#include "eog-transform.h"
+#include "eog-image-save-info.h"
+#include "eog-enums.h"
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#ifdef HAVE_EXIF
+#include <libexif/exif-data.h>
+#include "eog-exif-util.h"
+#endif
+
+#ifdef HAVE_LCMS
+#include <lcms2.h>
+#endif
+
+#ifdef HAVE_EXEMPI
+#include <exempi/xmp.h>
+#endif
+
+#ifdef HAVE_RSVG
+#include <librsvg/rsvg.h>
+#endif
+
+G_BEGIN_DECLS
+
+#ifndef __EOG_IMAGE_DECLR__
+#define __EOG_IMAGE_DECLR__
+typedef struct _EogImage EogImage;
+#endif
+typedef struct _EogImageClass EogImageClass;
+typedef struct _EogImagePrivate EogImagePrivate;
+
+#define EOG_TYPE_IMAGE (eog_image_get_type ())
+#define EOG_IMAGE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), EOG_TYPE_IMAGE, EogImage))
+#define EOG_IMAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), EOG_TYPE_IMAGE, EogImageClass))
+#define EOG_IS_IMAGE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), EOG_TYPE_IMAGE))
+#define EOG_IS_IMAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), EOG_TYPE_IMAGE))
+#define EOG_IMAGE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), EOG_TYPE_IMAGE, EogImageClass))
+
+typedef enum {
+ EOG_IMAGE_ERROR_SAVE_NOT_LOCAL,
+ EOG_IMAGE_ERROR_NOT_LOADED,
+ EOG_IMAGE_ERROR_NOT_SAVED,
+ EOG_IMAGE_ERROR_VFS,
+ EOG_IMAGE_ERROR_FILE_EXISTS,
+ EOG_IMAGE_ERROR_TMP_FILE_FAILED,
+ EOG_IMAGE_ERROR_GENERIC,
+ EOG_IMAGE_ERROR_UNKNOWN
+} EogImageError;
+
+#define EOG_IMAGE_ERROR eog_image_error_quark ()
+
+typedef enum {
+ EOG_IMAGE_STATUS_UNKNOWN,
+ EOG_IMAGE_STATUS_LOADING,
+ EOG_IMAGE_STATUS_LOADED,
+ EOG_IMAGE_STATUS_SAVING,
+ EOG_IMAGE_STATUS_FAILED
+} EogImageStatus;
+
+typedef enum {
+ EOG_IMAGE_METADATA_NOT_READ,
+ EOG_IMAGE_METADATA_NOT_AVAILABLE,
+ EOG_IMAGE_METADATA_READY
+} EogImageMetadataStatus;
+
+struct _EogImage {
+ GObject parent;
+
+ EogImagePrivate *priv;
+};
+
+struct _EogImageClass {
+ GObjectClass parent_class;
+
+ void (* changed) (EogImage *img);
+
+ void (* size_prepared) (EogImage *img,
+ int width,
+ int height);
+
+ void (* save_progress) (EogImage *img,
+ gfloat progress);
+
+ void (* next_frame) (EogImage *img,
+ gint delay);
+
+ void (* file_changed) (EogImage *img);
+};
+
+GType eog_image_get_type (void) G_GNUC_CONST;
+
+GQuark eog_image_error_quark (void);
+
+EogImage *eog_image_new (const char *txt_uri);
+
+EogImage *eog_image_new_file (GFile *file);
+
+gboolean eog_image_load (EogImage *img,
+ EogImageData data2read,
+ EogJob *job,
+ GError **error);
+
+void eog_image_cancel_load (EogImage *img);
+
+gboolean eog_image_has_data (EogImage *img,
+ EogImageData data);
+
+void eog_image_data_ref (EogImage *img);
+
+void eog_image_data_unref (EogImage *img);
+
+gboolean eog_image_save_as_by_info (EogImage *img,
+ EogImageSaveInfo *source,
+ EogImageSaveInfo *target,
+ GError **error);
+
+gboolean eog_image_save_by_info (EogImage *img,
+ EogImageSaveInfo *source,
+ GError **error);
+
+GdkPixbuf* eog_image_get_pixbuf (EogImage *img);
+
+void eog_image_get_size (EogImage *img,
+ gint *width,
+ gint *height);
+
+goffset eog_image_get_bytes (EogImage *img);
+
+gboolean eog_image_is_modified (EogImage *img);
+
+void eog_image_modified (EogImage *img);
+
+const gchar* eog_image_get_caption (EogImage *img);
+
+const gchar *eog_image_get_collate_key (EogImage *img);
+
+#if HAVE_EXIF
+ExifData* eog_image_get_exif_info (EogImage *img);
+#endif
+
+gpointer eog_image_get_xmp_info (EogImage *img);
+
+GFile* eog_image_get_file (EogImage *img);
+
+gchar* eog_image_get_uri_for_display (EogImage *img);
+
+EogImageStatus eog_image_get_status (EogImage *img);
+
+EogImageMetadataStatus eog_image_get_metadata_status (EogImage *img);
+
+void eog_image_transform (EogImage *img,
+ EogTransform *trans,
+ EogJob *job);
+
+void eog_image_autorotate (EogImage *img);
+
+#ifdef HAVE_LCMS
+cmsHPROFILE eog_image_get_profile (EogImage *img);
+
+void eog_image_apply_display_profile (EogImage *img,
+ cmsHPROFILE display_profile);
+#endif
+
+void eog_image_undo (EogImage *img);
+
+GList *eog_image_get_supported_mime_types (void);
+
+gboolean eog_image_is_supported_mime_type (const char *mime_type);
+
+gboolean eog_image_is_animation (EogImage *img);
+
+gboolean eog_image_start_animation (EogImage *img);
+
+#ifdef HAVE_RSVG
+gboolean eog_image_is_svg (EogImage *img);
+RsvgHandle *eog_image_get_svg (EogImage *img);
+#endif
+
+EogTransform *eog_image_get_transform (EogImage *img);
+EogTransform *eog_image_get_autorotate_transform (EogImage *img);
+
+gboolean eog_image_is_jpeg (EogImage *img);
+
+void eog_image_file_changed (EogImage *img);
+
+gboolean eog_image_is_file_changed (EogImage *img);
+
+gboolean eog_image_is_file_writable (EogImage *img);
+
+G_END_DECLS
+
+#endif /* __EOG_IMAGE_H__ */
diff --git a/src/eog-job-queue.c b/src/eog-job-queue.c
new file mode 100644
index 0000000..ca79396
--- /dev/null
+++ b/src/eog-job-queue.c
@@ -0,0 +1,212 @@
+/* Eye Of Gnome - Jobs Queue
+ *
+ * Copyright (C) 2006 The Free Software Foundation
+ *
+ * Author: Lucas Rocha <lucasr gnome org>
+ *
+ * Based on evince code (shell/ev-job-queue.c) by:
+ * - Martin Kretzschmar <martink gnome org>
+ *
+ * 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 Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "eog-jobs.h"
+#include "eog-job-queue.h"
+
+static GCond render_cond;
+static GMutex eog_queue_mutex;
+
+static GQueue *load_queue = NULL;
+static GQueue *transform_queue = NULL;
+static GQueue *save_queue = NULL;
+static GQueue *copy_queue = NULL;
+
+static gboolean
+remove_job_from_queue (GQueue *queue, EogJob *job)
+{
+ GList *list;
+
+ list = g_queue_find (queue, job);
+
+ if (list) {
+ g_object_unref (G_OBJECT (job));
+ g_queue_delete_link (queue, list);
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+add_job_to_queue_locked (GQueue *queue, EogJob *job)
+{
+ g_object_ref (job);
+ g_queue_push_tail (queue, job);
+ g_cond_broadcast (&render_cond);
+}
+
+static gboolean
+notify_finished (GObject *job)
+{
+ eog_job_finished (EOG_JOB (job));
+
+ return FALSE;
+}
+
+static void
+handle_job (EogJob *job)
+{
+ g_object_ref (G_OBJECT (job));
+
+ // Do the EOG_JOB cast for safety
+ eog_job_run (EOG_JOB (job));
+
+ g_idle_add_full (G_PRIORITY_DEFAULT_IDLE,
+ (GSourceFunc) notify_finished,
+ job,
+ g_object_unref);
+}
+
+static gboolean
+no_jobs_available_unlocked (void)
+{
+ return g_queue_is_empty (load_queue) &&
+ g_queue_is_empty (transform_queue) &&
+ g_queue_is_empty (save_queue) &&
+ g_queue_is_empty (copy_queue);
+}
+
+static EogJob *
+search_for_jobs_unlocked (void)
+{
+ EogJob *job;
+
+ job = (EogJob *) g_queue_pop_head (load_queue);
+ if (job)
+ return job;
+
+ job = (EogJob *) g_queue_pop_head (transform_queue);
+ if (job)
+ return job;
+
+ job = (EogJob *) g_queue_pop_head (save_queue);
+ if (job)
+ return job;
+
+ job = (EogJob *) g_queue_pop_head (copy_queue);
+ if (job)
+ return job;
+
+ return NULL;
+}
+
+static gpointer
+eog_render_thread (gpointer data)
+{
+ while (TRUE) {
+ EogJob *job;
+
+ g_mutex_lock (&eog_queue_mutex);
+
+ if (no_jobs_available_unlocked ()) {
+ g_cond_wait (&render_cond, &eog_queue_mutex);
+ }
+
+ job = search_for_jobs_unlocked ();
+
+ g_mutex_unlock (&eog_queue_mutex);
+
+ /* Now that we have our job, we handle it */
+ if (job) {
+ handle_job (job);
+ g_object_unref (G_OBJECT (job));
+ }
+ }
+ return NULL;
+
+}
+
+void
+eog_job_queue_init (void)
+{
+ load_queue = g_queue_new ();
+ transform_queue = g_queue_new ();
+ save_queue = g_queue_new ();
+ copy_queue = g_queue_new ();
+
+ /* Unref to detach the thread */
+ g_thread_unref (g_thread_new ("EogJobQueue", eog_render_thread, NULL));
+}
+
+static GQueue *
+find_queue (EogJob *job)
+{
+ if (EOG_IS_JOB_LOAD (job)) {
+ return load_queue;
+ } else if (EOG_IS_JOB_TRANSFORM (job)) {
+ return transform_queue;
+ } else if (EOG_IS_JOB_SAVE (job)) {
+ return save_queue;
+ } else if (EOG_IS_JOB_COPY (job)) {
+ return copy_queue;
+ }
+
+ g_assert_not_reached ();
+
+ return NULL;
+}
+
+void
+eog_job_queue_add_job (EogJob *job)
+{
+ GQueue *queue;
+
+ g_return_if_fail (EOG_IS_JOB (job));
+
+ queue = find_queue (job);
+
+ g_mutex_lock (&eog_queue_mutex);
+
+ add_job_to_queue_locked (queue, job);
+
+ g_mutex_unlock (&eog_queue_mutex);
+}
+
+gboolean
+eog_job_queue_remove_job (EogJob *job)
+{
+ gboolean retval = FALSE;
+
+ g_return_val_if_fail (EOG_IS_JOB (job), FALSE);
+
+ g_mutex_lock (&eog_queue_mutex);
+
+ if (EOG_IS_JOB_LOAD (job)) {
+ retval = remove_job_from_queue (load_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 if (EOG_IS_JOB_COPY (job)) {
+ retval = remove_job_from_queue (copy_queue, job);
+ } else {
+ g_assert_not_reached ();
+ }
+
+ g_mutex_unlock (&eog_queue_mutex);
+
+ return retval;
+}
diff --git a/src/eog-job-queue.h b/src/eog-job-queue.h
new file mode 100644
index 0000000..2bed060
--- /dev/null
+++ b/src/eog-job-queue.h
@@ -0,0 +1,42 @@
+/* Eye Of Gnome - Jobs Queue
+ *
+ * Copyright (C) 2006 The Free Software Foundation
+ *
+ * Author: Lucas Rocha <lucasr gnome org>
+ *
+ * Based on evince code (shell/ev-job-queue.h) by:
+ * - Martin Kretzschmar <martink gnome org>
+ *
+ * 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 Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __EOG_JOB_QUEUE_H__
+#define __EOG_JOB_QUEUE_H__
+
+#include "eog-jobs.h"
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+void eog_job_queue_init (void);
+
+void eog_job_queue_add_job (EogJob *job);
+
+gboolean eog_job_queue_remove_job (EogJob *job);
+
+G_END_DECLS
+
+#endif /* __EOG_JOB_QUEUE_H__ */
diff --git a/src/eog-jobs.c b/src/eog-jobs.c
new file mode 100644
index 0000000..25ecd28
--- /dev/null
+++ b/src/eog-jobs.c
@@ -0,0 +1,690 @@
+/* Eye Of Gnome - Jobs
+ *
+ * Copyright (C) 2006 The Free Software Foundation
+ *
+ * Author: Lucas Rocha <lucasr gnome org>
+ *
+ * Based on evince code (shell/ev-jobs.c) by:
+ * - Martin Kretzschmar <martink gnome org>
+ *
+ * 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 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"
+#include "eog-transform.h"
+#include "photos-utils.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#define EOG_JOB_GET_PRIVATE(object) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((object), EOG_TYPE_JOB, EogJobPrivate))
+
+G_DEFINE_TYPE (EogJob, eog_job, G_TYPE_OBJECT);
+G_DEFINE_TYPE (EogJobLoad, eog_job_load, 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);
+G_DEFINE_TYPE (EogJobCopy, eog_job_copy, EOG_TYPE_JOB);
+
+enum
+{
+ SIGNAL_FINISHED,
+ SIGNAL_PROGRESS,
+ SIGNAL_LAST_SIGNAL
+};
+
+static guint job_signals[SIGNAL_LAST_SIGNAL];
+
+static void eog_job_copy_run (EogJob *ejob);
+static void eog_job_load_run (EogJob *ejob);
+static void eog_job_save_run (EogJob *job);
+static void eog_job_save_as_run (EogJob *job);
+static void eog_job_transform_run (EogJob *ejob);
+
+static void eog_job_init (EogJob *job)
+{
+ /* NOTE: We need to allocate the mutex here so the ABI stays the same when it used to use g_mutex_new */
+ job->mutex = g_malloc (sizeof (GMutex));
+ g_mutex_init (job->mutex);
+ job->progress = 0.0;
+}
+
+static void
+eog_job_dispose (GObject *object)
+{
+ EogJob *job;
+
+ job = EOG_JOB (object);
+
+ if (job->error) {
+ g_error_free (job->error);
+ job->error = NULL;
+ }
+
+ if (job->mutex) {
+ g_mutex_clear (job->mutex);
+ g_free (job->mutex);
+ }
+
+ (* G_OBJECT_CLASS (eog_job_parent_class)->dispose) (object);
+}
+
+static void
+eog_job_run_default (EogJob *job)
+{
+ g_critical ("Class \"%s\" does not implement the required run action",
+ G_OBJECT_CLASS_NAME (G_OBJECT_GET_CLASS (job)));
+}
+
+static void
+eog_job_class_init (EogJobClass *class)
+{
+ GObjectClass *oclass;
+
+ oclass = G_OBJECT_CLASS (class);
+
+ oclass->dispose = eog_job_dispose;
+
+ class->run = eog_job_run_default;
+
+ job_signals [SIGNAL_FINISHED] =
+ g_signal_new ("finished",
+ EOG_TYPE_JOB,
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (EogJobClass, finished),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ job_signals [SIGNAL_PROGRESS] =
+ g_signal_new ("progress",
+ EOG_TYPE_JOB,
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (EogJobClass, progress),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__FLOAT,
+ G_TYPE_NONE, 1,
+ G_TYPE_FLOAT);
+}
+
+void
+eog_job_finished (EogJob *job)
+{
+ g_return_if_fail (EOG_IS_JOB (job));
+
+ g_signal_emit (job, job_signals[SIGNAL_FINISHED], 0);
+}
+
+/**
+ * eog_job_run:
+ * @job: the job to execute.
+ *
+ * Executes the job passed as @job. Usually there is no need to call this
+ * on your own. Jobs should be executed by using the EogJobQueue.
+ **/
+void
+eog_job_run (EogJob *job)
+{
+ EogJobClass *class;
+
+ g_return_if_fail (EOG_IS_JOB (job));
+
+ class = EOG_JOB_GET_CLASS (job);
+ if (class->run)
+ class->run (job);
+ else
+ eog_job_run_default (job);
+}
+static gboolean
+notify_progress (gpointer data)
+{
+ EogJob *job = EOG_JOB (data);
+
+ g_signal_emit (job, job_signals[SIGNAL_PROGRESS], 0, job->progress);
+
+ return FALSE;
+}
+
+void
+eog_job_set_progress (EogJob *job, float progress)
+{
+ g_return_if_fail (EOG_IS_JOB (job));
+ g_return_if_fail (progress >= 0.0 && progress <= 1.0);
+
+ g_mutex_lock (job->mutex);
+ job->progress = progress;
+ g_mutex_unlock (job->mutex);
+
+ g_idle_add (notify_progress, job);
+}
+
+static void eog_job_load_init (EogJobLoad *job) { /* Do Nothing */ }
+
+static void
+eog_job_load_dispose (GObject *object)
+{
+ EogJobLoad *job;
+
+ job = EOG_JOB_LOAD (object);
+
+ if (job->image) {
+ g_object_unref (job->image);
+ job->image = NULL;
+ }
+
+ (* G_OBJECT_CLASS (eog_job_load_parent_class)->dispose) (object);
+}
+
+static void
+eog_job_load_class_init (EogJobLoadClass *class)
+{
+ GObjectClass *oclass;
+
+ oclass = G_OBJECT_CLASS (class);
+
+ oclass->dispose = eog_job_load_dispose;
+ EOG_JOB_CLASS (class)->run = eog_job_load_run;
+}
+
+EogJob *
+eog_job_load_new (EogImage *image, EogImageData data)
+{
+ EogJobLoad *job;
+
+ job = g_object_new (EOG_TYPE_JOB_LOAD, NULL);
+
+ if (image) {
+ job->image = g_object_ref (image);
+ }
+ job->data = data;
+
+ return EOG_JOB (job);
+}
+
+static void
+eog_job_load_run (EogJob *job)
+{
+ g_return_if_fail (EOG_IS_JOB_LOAD (job));
+
+ if (job->error) {
+ g_error_free (job->error);
+ job->error = NULL;
+ }
+
+ eog_image_load (EOG_IMAGE (EOG_JOB_LOAD (job)->image),
+ EOG_JOB_LOAD (job)->data,
+ job,
+ &job->error);
+
+ job->finished = TRUE;
+}
+
+static void eog_job_transform_init (EogJobTransform *job) { /* Do Nothing */ }
+
+static void
+eog_job_transform_dispose (GObject *object)
+{
+ EogJobTransform *job;
+
+ job = EOG_JOB_TRANSFORM (object);
+
+ if (job->trans) {
+ g_object_unref (job->trans);
+ job->trans = NULL;
+ }
+
+ g_list_foreach (job->images, (GFunc) g_object_unref, NULL);
+ g_list_free (job->images);
+
+ (* G_OBJECT_CLASS (eog_job_transform_parent_class)->dispose) (object);
+}
+
+static void
+eog_job_transform_class_init (EogJobTransformClass *class)
+{
+ GObjectClass *oclass;
+
+ oclass = G_OBJECT_CLASS (class);
+
+ oclass->dispose = eog_job_transform_dispose;
+
+ EOG_JOB_CLASS (class)->run = eog_job_transform_run;
+}
+
+EogJob *
+eog_job_transform_new (GList *images, EogTransform *trans)
+{
+ EogJobTransform *job;
+
+ job = g_object_new (EOG_TYPE_JOB_TRANSFORM, NULL);
+
+ if (trans) {
+ job->trans = g_object_ref (trans);
+ } else {
+ job->trans = NULL;
+ }
+
+ job->images = images;
+
+ return EOG_JOB (job);
+}
+
+static gboolean
+eog_job_transform_image_modified (gpointer data)
+{
+ g_return_val_if_fail (EOG_IS_IMAGE (data), FALSE);
+
+ eog_image_modified (EOG_IMAGE (data));
+ g_object_unref (G_OBJECT (data));
+
+ return FALSE;
+}
+
+void
+eog_job_transform_run (EogJob *ejob)
+{
+ EogJobTransform *job;
+ GList *it;
+
+ g_return_if_fail (EOG_IS_JOB_TRANSFORM (ejob));
+
+ job = EOG_JOB_TRANSFORM (ejob);
+
+ if (ejob->error) {
+ g_error_free (ejob->error);
+ ejob->error = NULL;
+ }
+
+ for (it = job->images; it != NULL; it = it->next) {
+ EogImage *image = EOG_IMAGE (it->data);
+
+ if (job->trans == NULL) {
+ eog_image_undo (image);
+ } else {
+ eog_image_transform (image, job->trans, ejob);
+ }
+
+ if (eog_image_is_modified (image) || job->trans == NULL) {
+ g_object_ref (image);
+ g_idle_add (eog_job_transform_image_modified, image);
+ }
+ }
+
+ ejob->finished = TRUE;
+}
+
+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;
+ EOG_JOB_CLASS (class)->run = eog_job_save_run;
+}
+
+EogJob *
+eog_job_save_new (GList *images)
+{
+ EogJobSave *job;
+
+ job = g_object_new (EOG_TYPE_JOB_SAVE, NULL);
+
+ job->images = images;
+ job->current_image = NULL;
+
+ return EOG_JOB (job);
+}
+
+static void
+save_progress_handler (EogImage *image, gfloat progress, gpointer data)
+{
+ EogJobSave *job = EOG_JOB_SAVE (data);
+ guint n_images = g_list_length (job->images);
+ gfloat job_progress;
+
+ job_progress = (job->current_pos / (gfloat) n_images) + (progress / n_images);
+
+ eog_job_set_progress (EOG_JOB (job), job_progress);
+}
+
+static void
+eog_job_save_run (EogJob *ejob)
+{
+ EogJobSave *job;
+ GList *it;
+
+ g_return_if_fail (EOG_IS_JOB_SAVE (ejob));
+
+ job = EOG_JOB_SAVE (ejob);
+
+ job->current_pos = 0;
+
+ for (it = job->images; it != NULL; it = it->next, job->current_pos++) {
+ EogImage *image = EOG_IMAGE (it->data);
+ EogImageSaveInfo *save_info = NULL;
+ gulong handler_id = 0;
+ gboolean success = FALSE;
+
+ job->current_image = image;
+
+ /* 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)) {
+ EogImageMetadataStatus m_status;
+ gint data2load = 0;
+
+ m_status = eog_image_get_metadata_status (image);
+ if (!eog_image_has_data (image, EOG_IMAGE_DATA_IMAGE)) {
+ // Queue full read in this case
+ data2load = EOG_IMAGE_DATA_ALL;
+ } else if (m_status == EOG_IMAGE_METADATA_NOT_READ)
+ {
+ // Load only if we haven't read it yet
+ data2load = EOG_IMAGE_DATA_EXIF
+ | EOG_IMAGE_DATA_XMP;
+ }
+
+ if (data2load != 0) {
+ eog_image_load (image,
+ data2load,
+ NULL,
+ &ejob->error);
+ }
+ }
+
+ handler_id = g_signal_connect (G_OBJECT (image),
+ "save-progress",
+ G_CALLBACK (save_progress_handler),
+ job);
+
+ save_info = eog_image_save_info_new_from_image (image);
+
+ success = eog_image_save_by_info (image,
+ save_info,
+ &ejob->error);
+
+ if (save_info)
+ g_object_unref (save_info);
+
+ if (handler_id != 0)
+ g_signal_handler_disconnect (G_OBJECT (image), handler_id);
+
+ eog_image_data_unref (image);
+
+ if (!success) break;
+ }
+
+ ejob->finished = TRUE;
+}
+
+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->file != NULL) {
+ g_object_unref (job->file);
+ job->file = 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_CLASS (class)->run = eog_job_save_as_run;
+}
+
+EogJob *
+eog_job_save_as_new (GList *images, EogURIConverter *converter, GFile *file)
+{
+ 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->file = file ? g_object_ref (file) : NULL;
+
+ return EOG_JOB (job);
+}
+
+static void
+eog_job_save_as_run (EogJob *ejob)
+{
+ EogJobSave *job;
+ EogJobSaveAs *saveas_job;
+ GList *it;
+ guint n_images;
+
+ g_return_if_fail (EOG_IS_JOB_SAVE_AS (ejob));
+
+ job = EOG_JOB_SAVE (ejob);
+
+ n_images = g_list_length (job->images);
+
+ saveas_job = EOG_JOB_SAVE_AS (job);
+
+ job->current_pos = 0;
+
+ for (it = job->images; it != NULL; it = it->next, job->current_pos++) {
+ GdkPixbufFormat *format;
+ EogImageSaveInfo *src_info, *dest_info;
+ EogImage *image = EOG_IMAGE (it->data);
+ gboolean success = FALSE;
+ gulong handler_id = 0;
+
+ job->current_image = image;
+
+ eog_image_data_ref (image);
+
+ if (!eog_image_has_data (image, EOG_IMAGE_DATA_ALL)) {
+ EogImageMetadataStatus m_status;
+ gint data2load = 0;
+
+ m_status = eog_image_get_metadata_status (image);
+ if (!eog_image_has_data (image, EOG_IMAGE_DATA_IMAGE)) {
+ // Queue full read in this case
+ data2load = EOG_IMAGE_DATA_ALL;
+ } else if (m_status == EOG_IMAGE_METADATA_NOT_READ)
+ {
+ // Load only if we haven't read it yet
+ data2load = EOG_IMAGE_DATA_EXIF
+ | EOG_IMAGE_DATA_XMP;
+ }
+
+ if (data2load != 0) {
+ eog_image_load (image,
+ data2load,
+ NULL,
+ &ejob->error);
+ }
+ }
+
+
+ g_assert (ejob->error == NULL);
+
+ handler_id = g_signal_connect (G_OBJECT (image),
+ "save-progress",
+ G_CALLBACK (save_progress_handler),
+ job);
+
+ src_info = eog_image_save_info_new_from_image (image);
+
+ if (n_images == 1) {
+ g_assert (saveas_job->file != NULL);
+
+ format = photos_utils_get_pixbuf_format (saveas_job->file);
+
+ dest_info = eog_image_save_info_new_from_file (saveas_job->file,
+ format);
+
+ /* SaveAsDialog has already secured permission to overwrite */
+ if (dest_info->exists) {
+ dest_info->overwrite = TRUE;
+ }
+ } else {
+ GFile *dest_file;
+ gboolean result;
+
+ result = eog_uri_converter_do (saveas_job->converter,
+ image,
+ &dest_file,
+ &format,
+ NULL);
+
+ g_assert (result);
+
+ dest_info = eog_image_save_info_new_from_file (dest_file,
+ format);
+ }
+
+ success = eog_image_save_as_by_info (image,
+ src_info,
+ dest_info,
+ &ejob->error);
+
+ if (src_info)
+ g_object_unref (src_info);
+
+ if (dest_info)
+ g_object_unref (dest_info);
+
+ if (handler_id != 0)
+ g_signal_handler_disconnect (G_OBJECT (image), handler_id);
+
+ eog_image_data_unref (image);
+
+ if (!success)
+ break;
+ }
+
+ ejob->finished = TRUE;
+}
+
+static void eog_job_copy_init (EogJobCopy *job) { /* do nothing */};
+
+static void
+eog_job_copy_dispose (GObject *object)
+{
+ EogJobCopy *job = EOG_JOB_COPY (object);
+
+ if (job->dest) {
+ g_free (job->dest);
+ job->dest = NULL;
+ }
+
+ (* G_OBJECT_CLASS (eog_job_copy_parent_class)->dispose) (object);
+}
+
+static void
+eog_job_copy_class_init (EogJobCopyClass *class)
+{
+ G_OBJECT_CLASS (class)->dispose = eog_job_copy_dispose;
+ EOG_JOB_CLASS (class)->run = eog_job_copy_run;
+}
+
+EogJob *
+eog_job_copy_new (GList *images, const gchar *dest)
+{
+ EogJobCopy *job;
+
+ g_assert (images != NULL && dest != NULL);
+
+ job = g_object_new (EOG_TYPE_JOB_COPY, NULL);
+
+ job->images = images;
+ job->dest = g_strdup (dest);
+
+ return EOG_JOB (job);
+}
+
+static void
+eog_job_copy_progress_callback (goffset current_num_bytes,
+ goffset total_num_bytes,
+ gpointer user_data)
+{
+ gfloat job_progress;
+ guint n_images;
+ EogJobCopy *job;
+
+ job = EOG_JOB_COPY (user_data);
+ n_images = g_list_length (job->images);
+
+ job_progress = ((current_num_bytes / (gfloat) total_num_bytes) + job->current_pos)/n_images;
+
+ eog_job_set_progress (EOG_JOB (job), job_progress);
+}
+
+void
+eog_job_copy_run (EogJob *ejob)
+{
+ EogJobCopy *job;
+ GList *it;
+ GFile *src, *dest;
+ gchar *filename, *dest_filename;
+
+ g_return_if_fail (EOG_IS_JOB_COPY (ejob));
+
+ job = EOG_JOB_COPY (ejob);
+ job->current_pos = 0;
+
+ for (it = job->images; it != NULL; it = g_list_next (it), job->current_pos++) {
+ src = (GFile *) it->data;
+ filename = g_file_get_basename (src);
+ dest_filename = g_build_filename (job->dest, filename, NULL);
+ dest = g_file_new_for_path (dest_filename);
+
+ g_file_copy (src, dest,
+ G_FILE_COPY_OVERWRITE, NULL,
+ eog_job_copy_progress_callback, job,
+ &ejob->error);
+ g_free (filename);
+ g_free (dest_filename);
+ }
+
+ ejob->finished = TRUE;
+}
diff --git a/src/eog-jobs.h b/src/eog-jobs.h
new file mode 100644
index 0000000..400be0d
--- /dev/null
+++ b/src/eog-jobs.h
@@ -0,0 +1,228 @@
+/* Eye Of Gnome - Jobs
+ *
+ * Copyright (C) 2006 The Free Software Foundation
+ *
+ * Author: Lucas Rocha <lucasr gnome org>
+ *
+ * Based on evince code (shell/ev-jobs.h) by:
+ * - Martin Kretzschmar <martink gnome org>
+ *
+ * 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 Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __EOG_JOBS_H__
+#define __EOG_JOBS_H__
+
+#include "eog-transform.h"
+#include "eog-enums.h"
+
+#include <glib.h>
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#ifndef __EOG_IMAGE_DECLR__
+#define __EOG_IMAGE_DECLR__
+ typedef struct _EogImage EogImage;
+#endif
+
+#ifndef __EOG_URI_CONVERTER_DECLR__
+#define __EOG_URI_CONVERTER_DECLR__
+typedef struct _EogURIConverter EogURIConverter;
+#endif
+
+#ifndef __EOG_JOB_DECLR__
+#define __EOG_JOB_DECLR__
+typedef struct _EogJob EogJob;
+#endif
+typedef struct _EogJobClass EogJobClass;
+
+typedef struct _EogJobLoad EogJobLoad;
+typedef struct _EogJobLoadClass EogJobLoadClass;
+
+typedef struct _EogJobTransform EogJobTransform;
+typedef struct _EogJobTransformClass EogJobTransformClass;
+
+typedef struct _EogJobSave EogJobSave;
+typedef struct _EogJobSaveClass EogJobSaveClass;
+
+typedef struct _EogJobSaveAs EogJobSaveAs;
+typedef struct _EogJobSaveAsClass EogJobSaveAsClass;
+
+typedef struct _EogJobCopy EogJobCopy;
+typedef struct _EogJobCopyClass EogJobCopyClass;
+
+#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))
+#define EOG_IS_JOB(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), EOG_TYPE_JOB))
+#define EOG_JOB_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EOG_TYPE_JOB, EogJobClass))
+
+#define EOG_TYPE_JOB_LOAD (eog_job_load_get_type())
+#define EOG_JOB_LOAD(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), EOG_TYPE_JOB_LOAD, EogJobLoad))
+#define EOG_JOB_LOAD_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), EOG_TYPE_JOB_LOAD, EogJobLoadClass))
+#define EOG_IS_JOB_LOAD(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), EOG_TYPE_JOB_LOAD))
+
+#define EOG_TYPE_JOB_TRANSFORM (eog_job_transform_get_type())
+#define EOG_JOB_TRANSFORM(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), EOG_TYPE_JOB_TRANSFORM, EogJobTransform))
+#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))
+
+#define EOG_TYPE_JOB_COPY (eog_job_copy_get_type())
+#define EOG_JOB_COPY(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), EOG_TYPE_JOB_COPY, EogJobCopy))
+#define EOG_JOB_COPY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), EOG_TYPE_JOB_COPY, EogJobCopyClass))
+#define EOG_IS_JOB_COPY(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), EOG_TYPE_JOB_COPY))
+
+
+struct _EogJob
+{
+ GObject parent;
+
+ GError *error;
+ GMutex *mutex;
+ float progress;
+ gboolean finished;
+};
+
+struct _EogJobClass
+{
+ GObjectClass parent_class;
+
+ void (* finished) (EogJob *job);
+ void (* progress) (EogJob *job, float progress);
+ void (*run) (EogJob *job);
+};
+
+struct _EogJobLoad
+{
+ EogJob parent;
+ EogImage *image;
+ EogImageData data;
+};
+
+struct _EogJobLoadClass
+{
+ EogJobClass parent_class;
+};
+
+struct _EogJobTransform
+{
+ EogJob parent;
+ GList *images;
+ EogTransform *trans;
+};
+
+struct _EogJobTransformClass
+{
+ 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;
+ guint current_pos;
+ EogImage *current_image;
+};
+
+struct _EogJobSaveClass
+{
+ EogJobClass parent_class;
+};
+
+struct _EogJobSaveAs
+{
+ EogJobSave parent;
+ EogURIConverter *converter;
+ GFile *file;
+};
+
+struct _EogJobSaveAsClass
+{
+ EogJobSaveClass parent;
+};
+
+struct _EogJobCopy
+{
+ EogJob parent;
+ GList *images;
+ guint current_pos;
+ gchar *dest;
+};
+
+struct _EogJobCopyClass
+{
+ EogJobClass parent_class;
+};
+
+/* base job class */
+GType eog_job_get_type (void) G_GNUC_CONST;
+void eog_job_finished (EogJob *job);
+void eog_job_run (EogJob *job);
+void eog_job_set_progress (EogJob *job,
+ float progress);
+
+/* EogJobThumbnail */
+GType eog_job_thumbnail_get_type (void) G_GNUC_CONST;
+EogJob *eog_job_thumbnail_new (EogImage *image);
+
+/* EogJobLoad */
+GType eog_job_load_get_type (void) G_GNUC_CONST;
+EogJob *eog_job_load_new (EogImage *image,
+ EogImageData data);
+
+/* EogJobTransform */
+GType eog_job_transform_get_type (void) G_GNUC_CONST;
+EogJob *eog_job_transform_new (GList *images,
+ EogTransform *trans);
+
+/* EogJobSave */
+GType eog_job_save_get_type (void) G_GNUC_CONST;
+EogJob *eog_job_save_new (GList *images);
+
+/* EogJobSaveAs */
+GType eog_job_save_as_get_type (void) G_GNUC_CONST;
+EogJob *eog_job_save_as_new (GList *images,
+ EogURIConverter *converter,
+ GFile *file);
+
+/*EogJobCopy */
+GType eog_job_copy_get_type (void) G_GNUC_CONST;
+EogJob *eog_job_copy_new (GList *images,
+ const gchar *dest);
+
+G_END_DECLS
+
+#endif /* __EOG_JOBS_H__ */
diff --git a/src/eog-metadata-reader-jpg.c b/src/eog-metadata-reader-jpg.c
new file mode 100644
index 0000000..9598f67
--- /dev/null
+++ b/src/eog-metadata-reader-jpg.c
@@ -0,0 +1,671 @@
+/* Eye Of GNOME -- JPEG Metadata Reader
+ *
+ * Copyright (C) 2008 The Free Software Foundation
+ *
+ * Author: Felix Riemann <friemann svn gnome org>
+ *
+ * Based on the original EogMetadataReader code.
+ *
+ * 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 Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+
+#include "eog-metadata-reader.h"
+#include "eog-metadata-reader-jpg.h"
+#include "eog-debug.h"
+
+typedef enum {
+ EMR_READ = 0,
+ EMR_READ_SIZE_HIGH_BYTE,
+ EMR_READ_SIZE_LOW_BYTE,
+ EMR_READ_MARKER,
+ EMR_SKIP_BYTES,
+ EMR_READ_APP1,
+ EMR_READ_EXIF,
+ EMR_READ_XMP,
+ EMR_READ_ICC,
+ EMR_READ_IPTC,
+ EMR_FINISHED
+} EogMetadataReaderState;
+
+typedef enum {
+ EJA_EXIF = 0,
+ EJA_XMP,
+ EJA_OTHER
+} EogJpegApp1Type;
+
+
+#define EOG_JPEG_MARKER_START 0xFF
+#define EOG_JPEG_MARKER_SOI 0xD8
+#define EOG_JPEG_MARKER_APP1 0xE1
+#define EOG_JPEG_MARKER_APP2 0xE2
+#define EOG_JPEG_MARKER_APP14 0xED
+
+#define IS_FINISHED(priv) (priv->state == EMR_READ && \
+ priv->exif_chunk != NULL && \
+ priv->icc_chunk != NULL && \
+ priv->iptc_chunk != NULL && \
+ priv->xmp_chunk != NULL)
+
+struct _EogMetadataReaderJpgPrivate {
+ EogMetadataReaderState state;
+
+ /* data fields */
+ guint exif_len;
+ gpointer exif_chunk;
+
+ gpointer iptc_chunk;
+ guint iptc_len;
+
+ guint icc_len;
+ gpointer icc_chunk;
+
+ gpointer xmp_chunk;
+ guint xmp_len;
+
+ /* management fields */
+ int size;
+ int last_marker;
+ int bytes_read;
+};
+
+#define EOG_METADATA_READER_JPG_GET_PRIVATE(object) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((object), EOG_TYPE_METADATA_READER_JPG, EogMetadataReaderJpgPrivate))
+
+static void
+eog_metadata_reader_jpg_init_emr_iface (gpointer g_iface, gpointer iface_data);
+
+
+G_DEFINE_TYPE_WITH_CODE (EogMetadataReaderJpg, eog_metadata_reader_jpg,
+ G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (EOG_TYPE_METADATA_READER,
+ eog_metadata_reader_jpg_init_emr_iface))
+
+
+static void
+eog_metadata_reader_jpg_dispose (GObject *object)
+{
+ EogMetadataReaderJpg *emr = EOG_METADATA_READER_JPG (object);
+
+ if (emr->priv->exif_chunk != NULL) {
+ g_free (emr->priv->exif_chunk);
+ emr->priv->exif_chunk = NULL;
+ }
+
+ if (emr->priv->iptc_chunk != NULL) {
+ g_free (emr->priv->iptc_chunk);
+ emr->priv->iptc_chunk = NULL;
+ }
+
+ if (emr->priv->xmp_chunk != NULL) {
+ g_free (emr->priv->xmp_chunk);
+ emr->priv->xmp_chunk = NULL;
+ }
+
+ if (emr->priv->icc_chunk != NULL) {
+ g_free (emr->priv->icc_chunk);
+ emr->priv->icc_chunk = NULL;
+ }
+
+ G_OBJECT_CLASS (eog_metadata_reader_jpg_parent_class)->dispose (object);
+}
+
+static void
+eog_metadata_reader_jpg_init (EogMetadataReaderJpg *obj)
+{
+ EogMetadataReaderJpgPrivate *priv;
+
+ priv = obj->priv = EOG_METADATA_READER_JPG_GET_PRIVATE (obj);
+ priv->exif_chunk = NULL;
+ priv->exif_len = 0;
+ priv->iptc_chunk = NULL;
+ priv->iptc_len = 0;
+ priv->icc_chunk = NULL;
+ priv->icc_len = 0;
+}
+
+static void
+eog_metadata_reader_jpg_class_init (EogMetadataReaderJpgClass *klass)
+{
+ GObjectClass *object_class = (GObjectClass*) klass;
+
+ object_class->dispose = eog_metadata_reader_jpg_dispose;
+
+ g_type_class_add_private (klass, sizeof (EogMetadataReaderJpgPrivate));
+}
+
+static gboolean
+eog_metadata_reader_jpg_finished (EogMetadataReaderJpg *emr)
+{
+ g_return_val_if_fail (EOG_IS_METADATA_READER_JPG (emr), TRUE);
+
+ return (emr->priv->state == EMR_FINISHED);
+}
+
+
+static EogJpegApp1Type
+eog_metadata_identify_app1 (gchar *buf, guint len)
+{
+ if (len < 5) {
+ return EJA_OTHER;
+ }
+
+ if (len < 29) {
+ return (strncmp ("Exif", buf, 5) == 0 ? EJA_EXIF : EJA_OTHER);
+ }
+
+ if (strncmp ("Exif", buf, 5) == 0) {
+ return EJA_EXIF;
+ } else if (strncmp ("http://ns.adobe.com/xap/1.0/", buf, 29) == 0) {
+ return EJA_XMP;
+ }
+
+ return EJA_OTHER;
+}
+
+static void
+eog_metadata_reader_get_next_block (EogMetadataReaderJpgPrivate* priv,
+ guchar *chunk,
+ int* i,
+ const guchar *buf,
+ int len,
+ EogMetadataReaderState state)
+{
+ if (*i + priv->size < len) {
+ /* read data in one block */
+ memcpy ((guchar*) (chunk) + priv->bytes_read, &buf[*i], priv->size);
+ priv->state = EMR_READ;
+ *i = *i + priv->size - 1; /* the for-loop consumes the other byte */
+ } else {
+ int chunk_len = len - *i;
+ memcpy ((guchar*) (chunk) + priv->bytes_read, &buf[*i], chunk_len);
+ priv->bytes_read += chunk_len; /* bytes already read */
+ priv->size = (*i + priv->size) - len; /* remaining data to read */
+ *i = len - 1;
+ priv->state = state;
+ }
+}
+
+static void
+eog_metadata_reader_jpg_consume (EogMetadataReaderJpg *emr, const guchar *buf, guint len)
+{
+ EogMetadataReaderJpgPrivate *priv;
+ EogJpegApp1Type app1_type;
+ int i;
+ EogMetadataReaderState next_state = EMR_READ;
+ guchar *chunk = NULL;
+
+ g_return_if_fail (EOG_IS_METADATA_READER_JPG (emr));
+
+ priv = emr->priv;
+
+ if (priv->state == EMR_FINISHED) return;
+
+ for (i = 0; (i < len) && (priv->state != EMR_FINISHED); i++) {
+
+ switch (priv->state) {
+ case EMR_READ:
+ if (buf[i] == EOG_JPEG_MARKER_START) {
+ priv->state = EMR_READ_MARKER;
+ }
+ else {
+ priv->state = EMR_FINISHED;
+ }
+ break;
+
+ case EMR_READ_MARKER:
+ if ((buf [i] & 0xF0) == 0xE0 || buf[i] == 0xFE) {
+ /* we are reading some sort of APPxx or COM marker */
+ /* these are always followed by 2 bytes of size information */
+ priv->last_marker = buf [i];
+ priv->size = 0;
+ priv->state = EMR_READ_SIZE_HIGH_BYTE;
+
+ eog_debug_message (DEBUG_IMAGE_DATA, "APPx or COM Marker Found: %x", priv->last_marker);
+ }
+ else {
+ /* otherwise simply consume the byte */
+ priv->state = EMR_READ;
+ }
+ break;
+
+ case EMR_READ_SIZE_HIGH_BYTE:
+ priv->size = (buf [i] & 0xff) << 8;
+ priv->state = EMR_READ_SIZE_LOW_BYTE;
+ break;
+
+ case EMR_READ_SIZE_LOW_BYTE:
+ priv->size |= (buf [i] & 0xff);
+
+ if (priv->size > 2) /* ignore the two size-bytes */
+ priv->size -= 2;
+
+ if (priv->size == 0) {
+ priv->state = EMR_READ;
+ } else if (priv->last_marker == EOG_JPEG_MARKER_APP1 &&
+ ((priv->exif_chunk == NULL) || (priv->xmp_chunk == NULL)))
+ {
+ priv->state = EMR_READ_APP1;
+ } else if (priv->last_marker == EOG_JPEG_MARKER_APP2 &&
+ priv->icc_chunk == NULL && priv->size > 14)
+ {
+ /* Chunk has 14 bytes identification data */
+ priv->state = EMR_READ_ICC;
+ } else if (priv->last_marker == EOG_JPEG_MARKER_APP14 &&
+ priv->iptc_chunk == NULL)
+ {
+ priv->state = EMR_READ_IPTC;
+ } else {
+ priv->state = EMR_SKIP_BYTES;
+ }
+
+ priv->last_marker = 0;
+ break;
+
+ case EMR_SKIP_BYTES:
+ eog_debug_message (DEBUG_IMAGE_DATA, "Skip bytes: %i", priv->size);
+
+ if (i + priv->size < len) {
+ i = i + priv->size - 1; /* the for-loop consumes the other byte */
+ priv->size = 0;
+ }
+ else {
+ priv->size = (i + priv->size) - len;
+ i = len - 1;
+ }
+ if (priv->size == 0) { /* don't need to skip any more bytes */
+ priv->state = EMR_READ;
+ }
+ break;
+
+ case EMR_READ_APP1:
+ eog_debug_message (DEBUG_IMAGE_DATA, "Read APP1 data, Length: %i", priv->size);
+
+ app1_type = eog_metadata_identify_app1 ((gchar*) &buf[i], priv->size);
+
+ switch (app1_type) {
+ case EJA_EXIF:
+ if (priv->exif_chunk == NULL) {
+ priv->exif_chunk = g_new0 (guchar, priv->size);
+ priv->exif_len = priv->size;
+ priv->bytes_read = 0;
+ chunk = priv->exif_chunk;
+ next_state = EMR_READ_EXIF;
+ } else {
+ chunk = NULL;
+ priv->state = EMR_SKIP_BYTES;
+ }
+ break;
+ case EJA_XMP:
+ if (priv->xmp_chunk == NULL) {
+ priv->xmp_chunk = g_new0 (guchar, priv->size);
+ priv->xmp_len = priv->size;
+ priv->bytes_read = 0;
+ chunk = priv->xmp_chunk;
+ next_state = EMR_READ_XMP;
+ } else {
+ chunk = NULL;
+ priv->state = EMR_SKIP_BYTES;
+ }
+ break;
+ case EJA_OTHER:
+ default:
+ /* skip unknown data */
+ chunk = NULL;
+ priv->state = EMR_SKIP_BYTES;
+ break;
+ }
+
+ if (chunk) {
+ eog_metadata_reader_get_next_block (priv, chunk,
+ &i, buf,
+ len,
+ next_state);
+ }
+
+ if (IS_FINISHED(priv))
+ priv->state = EMR_FINISHED;
+ break;
+
+ case EMR_READ_EXIF:
+ eog_debug_message (DEBUG_IMAGE_DATA, "Read continuation of EXIF data, length: %i", priv->size);
+ {
+ eog_metadata_reader_get_next_block (priv, priv->exif_chunk,
+ &i, buf, len, EMR_READ_EXIF);
+ }
+ if (IS_FINISHED(priv))
+ priv->state = EMR_FINISHED;
+ break;
+
+ case EMR_READ_XMP:
+ eog_debug_message (DEBUG_IMAGE_DATA, "Read continuation of XMP data, length: %i", priv->size);
+ {
+ eog_metadata_reader_get_next_block (priv, priv->xmp_chunk,
+ &i, buf, len, EMR_READ_XMP);
+ }
+ if (IS_FINISHED (priv))
+ priv->state = EMR_FINISHED;
+ break;
+
+ case EMR_READ_ICC:
+ eog_debug_message (DEBUG_IMAGE_DATA,
+ "Read continuation of ICC data, "
+ "length: %i", priv->size);
+
+ if (priv->icc_chunk == NULL) {
+ priv->icc_chunk = g_new0 (guchar, priv->size);
+ priv->icc_len = priv->size;
+ priv->bytes_read = 0;
+ }
+
+ eog_metadata_reader_get_next_block (priv,
+ priv->icc_chunk,
+ &i, buf, len,
+ EMR_READ_ICC);
+
+ /* Test that the chunk actually contains ICC data. */
+ if (priv->state == EMR_READ && priv->icc_chunk) {
+ const char* icc_chunk = priv->icc_chunk;
+ gboolean valid = TRUE;
+
+ /* Chunk should begin with the
+ * ICC_PROFILE\0 identifier */
+ valid &= strncmp (icc_chunk,
+ "ICC_PROFILE\0",12) == 0;
+ /* Make sure this is the first and only
+ * ICC chunk in the file as we don't
+ * support merging chunks yet. */
+ valid &= *(guint16*)(icc_chunk+12) == 0x101;
+
+ if (!valid) {
+ /* This no ICC data. Throw it away. */
+ eog_debug_message (DEBUG_IMAGE_DATA,
+ "Supposed ICC chunk didn't validate. "
+ "Ignoring.");
+ g_free (priv->icc_chunk);
+ priv->icc_chunk = NULL;
+ priv->icc_len = 0;
+ }
+ }
+
+ if (IS_FINISHED(priv))
+ priv->state = EMR_FINISHED;
+ break;
+
+ case EMR_READ_IPTC:
+ eog_debug_message (DEBUG_IMAGE_DATA,
+ "Read continuation of IPTC data, "
+ "length: %i", priv->size);
+
+ if (priv->iptc_chunk == NULL) {
+ priv->iptc_chunk = g_new0 (guchar, priv->size);
+ priv->iptc_len = priv->size;
+ priv->bytes_read = 0;
+ }
+
+ eog_metadata_reader_get_next_block (priv,
+ priv->iptc_chunk,
+ &i, buf, len,
+ EMR_READ_IPTC);
+
+ if (IS_FINISHED(priv))
+ priv->state = EMR_FINISHED;
+ break;
+
+ default:
+ g_assert_not_reached ();
+ }
+ }
+}
+
+/* Returns the raw exif data. NOTE: The caller of this function becomes
+ * the new owner of this piece of memory and is responsible for freeing it!
+ */
+static void
+eog_metadata_reader_jpg_get_exif_chunk (EogMetadataReaderJpg *emr, guchar **data, guint *len)
+{
+ EogMetadataReaderJpgPrivate *priv;
+
+ g_return_if_fail (EOG_IS_METADATA_READER (emr));
+ priv = emr->priv;
+
+ *data = (guchar*) priv->exif_chunk;
+ *len = priv->exif_len;
+
+ priv->exif_chunk = NULL;
+ priv->exif_len = 0;
+}
+
+#ifdef HAVE_EXIF
+static gpointer
+eog_metadata_reader_jpg_get_exif_data (EogMetadataReaderJpg *emr)
+{
+ EogMetadataReaderJpgPrivate *priv;
+ ExifData *data = NULL;
+
+ g_return_val_if_fail (EOG_IS_METADATA_READER (emr), NULL);
+ priv = emr->priv;
+
+ if (priv->exif_chunk != NULL) {
+ data = exif_data_new_from_data (priv->exif_chunk, priv->exif_len);
+ }
+
+ return data;
+}
+#endif
+
+
+#ifdef HAVE_EXEMPI
+
+/* skip the signature */
+#define EOG_XMP_OFFSET (29)
+
+static gpointer
+eog_metadata_reader_jpg_get_xmp_data (EogMetadataReaderJpg *emr )
+{
+ EogMetadataReaderJpgPrivate *priv;
+ XmpPtr xmp = NULL;
+
+ g_return_val_if_fail (EOG_IS_METADATA_READER (emr), NULL);
+
+ priv = emr->priv;
+
+ if (priv->xmp_chunk != NULL) {
+ xmp = xmp_new (priv->xmp_chunk+EOG_XMP_OFFSET,
+ priv->xmp_len-EOG_XMP_OFFSET);
+ }
+
+ return (gpointer)xmp;
+}
+#endif
+
+/*
+ * FIXME: very broken, assumes the profile fits in a single chunk. Change to
+ * parse the sections and construct a single memory chunk, or maybe even parse
+ * the profile.
+ */
+#ifdef HAVE_LCMS
+static gpointer
+eog_metadata_reader_jpg_get_icc_profile (EogMetadataReaderJpg *emr)
+{
+ EogMetadataReaderJpgPrivate *priv;
+ cmsHPROFILE profile = NULL;
+
+ g_return_val_if_fail (EOG_IS_METADATA_READER (emr), NULL);
+
+ priv = emr->priv;
+
+ if (priv->icc_chunk) {
+ profile = cmsOpenProfileFromMem(priv->icc_chunk + 14, priv->icc_len - 14);
+
+ if (profile) {
+ eog_debug_message (DEBUG_LCMS, "JPEG has ICC profile");
+ } else {
+ eog_debug_message (DEBUG_LCMS, "JPEG has invalid ICC profile");
+ }
+ }
+
+#ifdef HAVE_EXIF
+ if (!profile && priv->exif_chunk != NULL) {
+ ExifEntry *entry;
+ ExifByteOrder o;
+ gint color_space;
+ ExifData *exif = eog_metadata_reader_jpg_get_exif_data (emr);
+
+ if (!exif) return NULL;
+
+ o = exif_data_get_byte_order (exif);
+
+ entry = exif_data_get_entry (exif, EXIF_TAG_COLOR_SPACE);
+
+ if (entry == NULL) {
+ exif_data_unref (exif);
+ return NULL;
+ }
+
+ color_space = exif_get_short (entry->data, o);
+
+ switch (color_space) {
+ case 1:
+ eog_debug_message (DEBUG_LCMS, "JPEG is sRGB");
+
+ profile = cmsCreate_sRGBProfile ();
+
+ break;
+ case 2:
+ eog_debug_message (DEBUG_LCMS, "JPEG is Adobe RGB (Disabled)");
+
+ /* TODO: create Adobe RGB profile */
+ //profile = cmsCreate_Adobe1998Profile ();
+
+ break;
+ case 0xFFFF:
+ {
+ cmsCIExyY whitepoint;
+ cmsCIExyYTRIPLE primaries;
+ cmsToneCurve *gamma[3];
+ double gammaValue;
+ ExifRational r;
+
+ const int offset = exif_format_get_size (EXIF_FORMAT_RATIONAL);
+
+ entry = exif_data_get_entry (exif, EXIF_TAG_WHITE_POINT);
+
+ if (entry && entry->components == 2) {
+ r = exif_get_rational (entry->data, o);
+ whitepoint.x = (double) r.numerator / r.denominator;
+
+ r = exif_get_rational (entry->data + offset, o);
+ whitepoint.y = (double) r.numerator / r.denominator;
+ whitepoint.Y = 1.0;
+ } else {
+ eog_debug_message (DEBUG_LCMS, "No whitepoint found");
+ break;
+ }
+
+ entry = exif_data_get_entry (exif, EXIF_TAG_PRIMARY_CHROMATICITIES);
+
+ if (entry && entry->components == 6) {
+ r = exif_get_rational (entry->data + 0 * offset, o);
+ primaries.Red.x = (double) r.numerator / r.denominator;
+
+ r = exif_get_rational (entry->data + 1 * offset, o);
+ primaries.Red.y = (double) r.numerator / r.denominator;
+
+ r = exif_get_rational (entry->data + 2 * offset, o);
+ primaries.Green.x = (double) r.numerator / r.denominator;
+
+ r = exif_get_rational (entry->data + 3 * offset, o);
+ primaries.Green.y = (double) r.numerator / r.denominator;
+
+ r = exif_get_rational (entry->data + 4 * offset, o);
+ primaries.Blue.x = (double) r.numerator / r.denominator;
+
+ r = exif_get_rational (entry->data + 5 * offset, o);
+ primaries.Blue.y = (double) r.numerator / r.denominator;
+
+ primaries.Red.Y = primaries.Green.Y = primaries.Blue.Y = 1.0;
+ } else {
+ eog_debug_message (DEBUG_LCMS, "No primary chromaticities found");
+ break;
+ }
+
+ entry = exif_data_get_entry (exif, EXIF_TAG_GAMMA);
+
+ if (entry) {
+ r = exif_get_rational (entry->data, o);
+ gammaValue = (double) r.numerator / r.denominator;
+ } else {
+ eog_debug_message (DEBUG_LCMS, "No gamma found");
+ gammaValue = 2.2;
+ }
+
+ gamma[0] = gamma[1] = gamma[2] = cmsBuildGamma (NULL, gammaValue);
+
+ profile = cmsCreateRGBProfile (&whitepoint, &primaries, gamma);
+
+ cmsFreeToneCurve(gamma[0]);
+
+ eog_debug_message (DEBUG_LCMS, "JPEG is calibrated");
+
+ break;
+ }
+ }
+
+ exif_data_unref (exif);
+ }
+#endif
+ return profile;
+}
+#endif
+
+static void
+eog_metadata_reader_jpg_init_emr_iface (gpointer g_iface, gpointer iface_data)
+{
+ EogMetadataReaderInterface *iface;
+
+ iface = (EogMetadataReaderInterface*)g_iface;
+
+ iface->consume =
+ (void (*) (EogMetadataReader *self, const guchar *buf, guint len))
+ eog_metadata_reader_jpg_consume;
+ iface->finished =
+ (gboolean (*) (EogMetadataReader *self))
+ eog_metadata_reader_jpg_finished;
+ iface->get_raw_exif =
+ (void (*) (EogMetadataReader *self, guchar **data, guint *len))
+ eog_metadata_reader_jpg_get_exif_chunk;
+#ifdef HAVE_EXIF
+ iface->get_exif_data =
+ (gpointer (*) (EogMetadataReader *self))
+ eog_metadata_reader_jpg_get_exif_data;
+#endif
+#ifdef HAVE_LCMS
+ iface->get_icc_profile =
+ (gpointer (*) (EogMetadataReader *self))
+ eog_metadata_reader_jpg_get_icc_profile;
+#endif
+#ifdef HAVE_EXEMPI
+ iface->get_xmp_ptr =
+ (gpointer (*) (EogMetadataReader *self))
+ eog_metadata_reader_jpg_get_xmp_data;
+#endif
+}
+
diff --git a/src/eog-metadata-reader-jpg.h b/src/eog-metadata-reader-jpg.h
new file mode 100644
index 0000000..e772569
--- /dev/null
+++ b/src/eog-metadata-reader-jpg.h
@@ -0,0 +1,55 @@
+/* Eye Of GNOME -- JPEG Metadata Reader
+ *
+ * Copyright (C) 2008 The Free Software Foundation
+ *
+ * Author: Felix Riemann <friemann svn gnome org>
+ *
+ * Based on the original EogMetadataReader code.
+ *
+ * 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 Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef _EOG_METADATA_READER_JPG_H_
+#define _EOG_METADATA_READER_JPG_H_
+
+G_BEGIN_DECLS
+
+#define EOG_TYPE_METADATA_READER_JPG (eog_metadata_reader_jpg_get_type ())
+#define EOG_METADATA_READER_JPG(o) (G_TYPE_CHECK_INSTANCE_CAST ((o),EOG_TYPE_METADATA_READER_JPG, EogMetadataReaderJpg))
+#define EOG_METADATA_READER_JPG_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), EOG_TYPE_METADATA_READER_JPG, EogMetadataReaderJpgClass))
+#define EOG_IS_METADATA_READER_JPG(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), EOG_TYPE_METADATA_READER_JPG))
+#define EOG_IS_METADATA_READER_JPG_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), EOG_TYPE_METADATA_READER_JPG))
+#define EOG_METADATA_READER_JPG_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), EOG_TYPE_METADATA_READER_JPG, EogMetadataReaderJpgClass))
+
+typedef struct _EogMetadataReaderJpg EogMetadataReaderJpg;
+typedef struct _EogMetadataReaderJpgClass EogMetadataReaderJpgClass;
+typedef struct _EogMetadataReaderJpgPrivate EogMetadataReaderJpgPrivate;
+
+struct _EogMetadataReaderJpg {
+ GObject parent;
+
+ EogMetadataReaderJpgPrivate *priv;
+};
+
+struct _EogMetadataReaderJpgClass {
+ GObjectClass parent_klass;
+};
+
+G_GNUC_INTERNAL
+GType eog_metadata_reader_jpg_get_type (void) G_GNUC_CONST;
+
+G_END_DECLS
+
+#endif /* _EOG_METADATA_READER_JPG_H_ */
diff --git a/src/eog-metadata-reader-png.c b/src/eog-metadata-reader-png.c
new file mode 100644
index 0000000..ec5994d
--- /dev/null
+++ b/src/eog-metadata-reader-png.c
@@ -0,0 +1,646 @@
+/* Eye Of GNOME -- PNG Metadata Reader
+ *
+ * Copyright (C) 2008 The Free Software Foundation
+ *
+ * Author: Felix Riemann <friemann svn gnome org>
+ *
+ * Based on the old EogMetadataReader code.
+ *
+ * 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 Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <zlib.h>
+
+#include "eog-metadata-reader.h"
+#include "eog-metadata-reader-png.h"
+#include "eog-debug.h"
+
+typedef enum {
+ EMR_READ_MAGIC,
+ EMR_READ_SIZE_HIGH_HIGH_BYTE,
+ EMR_READ_SIZE_HIGH_LOW_BYTE,
+ EMR_READ_SIZE_LOW_HIGH_BYTE,
+ EMR_READ_SIZE_LOW_LOW_BYTE,
+ EMR_READ_CHUNK_NAME,
+ EMR_SKIP_BYTES,
+ EMR_CHECK_CRC,
+ EMR_SKIP_CRC,
+ EMR_READ_XMP_ITXT,
+ EMR_READ_ICCP,
+ EMR_READ_SRGB,
+ EMR_READ_CHRM,
+ EMR_READ_GAMA,
+ EMR_FINISHED
+} EogMetadataReaderPngState;
+
+#if 0
+#define IS_FINISHED(priv) (priv->icc_chunk != NULL && \
+ priv->xmp_chunk != NULL)
+#endif
+
+struct _EogMetadataReaderPngPrivate {
+ EogMetadataReaderPngState state;
+
+ /* data fields */
+ guint32 icc_len;
+ gpointer icc_chunk;
+
+ gpointer xmp_chunk;
+ guint32 xmp_len;
+
+ guint32 sRGB_len;
+ gpointer sRGB_chunk;
+
+ gpointer cHRM_chunk;
+ guint32 cHRM_len;
+
+ guint32 gAMA_len;
+ gpointer gAMA_chunk;
+
+ /* management fields */
+ gsize size;
+ gsize bytes_read;
+ guint sub_step;
+ guchar chunk_name[4];
+ gpointer *crc_chunk;
+ guint32 *crc_len;
+ guint32 target_crc;
+ gboolean hasIHDR;
+};
+
+#define EOG_METADATA_READER_PNG_GET_PRIVATE(object) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((object), EOG_TYPE_METADATA_READER_PNG, EogMetadataReaderPngPrivate))
+
+static void
+eog_metadata_reader_png_init_emr_iface (gpointer g_iface, gpointer iface_data);
+
+G_DEFINE_TYPE_WITH_CODE (EogMetadataReaderPng, eog_metadata_reader_png,
+ G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (EOG_TYPE_METADATA_READER,
+ eog_metadata_reader_png_init_emr_iface))
+
+static void
+eog_metadata_reader_png_dispose (GObject *object)
+{
+ EogMetadataReaderPng *emr = EOG_METADATA_READER_PNG (object);
+ EogMetadataReaderPngPrivate *priv = emr->priv;
+
+ g_free (priv->xmp_chunk);
+ priv->xmp_chunk = NULL;
+
+ g_free (priv->icc_chunk);
+ priv->icc_chunk = NULL;
+
+ g_free (priv->sRGB_chunk);
+ priv->sRGB_chunk = NULL;
+
+ g_free (priv->cHRM_chunk);
+ priv->cHRM_chunk = NULL;
+
+ g_free (priv->gAMA_chunk);
+ priv->gAMA_chunk = NULL;
+
+ G_OBJECT_CLASS (eog_metadata_reader_png_parent_class)->dispose (object);
+}
+
+static void
+eog_metadata_reader_png_init (EogMetadataReaderPng *obj)
+{
+ EogMetadataReaderPngPrivate *priv;
+
+ priv = obj->priv = EOG_METADATA_READER_PNG_GET_PRIVATE (obj);
+ priv->icc_chunk = NULL;
+ priv->icc_len = 0;
+ priv->xmp_chunk = NULL;
+ priv->xmp_len = 0;
+ priv->sRGB_chunk = NULL;
+ priv->sRGB_len = 0;
+ priv->cHRM_chunk = NULL;
+ priv->cHRM_len = 0;
+ priv->gAMA_chunk = NULL;
+ priv->gAMA_len = 0;
+
+ priv->sub_step = 0;
+ priv->state = EMR_READ_MAGIC;
+ priv->hasIHDR = FALSE;
+}
+
+static void
+eog_metadata_reader_png_class_init (EogMetadataReaderPngClass *klass)
+{
+ GObjectClass *object_class = (GObjectClass*) klass;
+
+ object_class->dispose = eog_metadata_reader_png_dispose;
+
+ g_type_class_add_private (klass, sizeof (EogMetadataReaderPngPrivate));
+}
+
+static gboolean
+eog_metadata_reader_png_finished (EogMetadataReaderPng *emr)
+{
+ g_return_val_if_fail (EOG_IS_METADATA_READER_PNG (emr), TRUE);
+
+ return (emr->priv->state == EMR_FINISHED);
+}
+
+
+static void
+eog_metadata_reader_png_get_next_block (EogMetadataReaderPngPrivate* priv,
+ guchar *chunk,
+ int* i,
+ const guchar *buf,
+ int len,
+ EogMetadataReaderPngState state)
+{
+ if (*i + priv->size < len) {
+ /* read data in one block */
+ memcpy ((guchar*) (chunk) + priv->bytes_read, &buf[*i], priv->size);
+ priv->state = EMR_CHECK_CRC;
+ *i = *i + priv->size - 1; /* the for-loop consumes the other byte */
+ priv->size = 0;
+ } else {
+ int chunk_len = len - *i;
+ memcpy ((guchar*) (chunk) + priv->bytes_read, &buf[*i], chunk_len);
+ priv->bytes_read += chunk_len; /* bytes already read */
+ priv->size = (*i + priv->size) - len; /* remaining data to read */
+ *i = len - 1;
+ priv->state = state;
+ }
+}
+
+static void
+eog_metadata_reader_png_consume (EogMetadataReaderPng *emr, const guchar *buf, guint len)
+{
+ EogMetadataReaderPngPrivate *priv;
+ int i;
+ guint32 chunk_crc;
+ static const gchar PNGMAGIC[8] = "\x89PNG\x0D\x0A\x1a\x0A";
+
+ g_return_if_fail (EOG_IS_METADATA_READER_PNG (emr));
+
+ priv = emr->priv;
+
+ if (priv->state == EMR_FINISHED) return;
+
+ for (i = 0; (i < len) && (priv->state != EMR_FINISHED); i++) {
+
+ switch (priv->state) {
+ case EMR_READ_MAGIC:
+ /* Check PNG magic string */
+ if (priv->sub_step < 8 &&
+ (gchar)buf[i] == PNGMAGIC[priv->sub_step]) {
+ if (priv->sub_step == 7)
+ priv->state = EMR_READ_SIZE_HIGH_HIGH_BYTE;
+ priv->sub_step++;
+ } else {
+ priv->state = EMR_FINISHED;
+ }
+ break;
+ case EMR_READ_SIZE_HIGH_HIGH_BYTE:
+ /* Read the high byte of the size's high word */
+ priv->size |= (buf[i] & 0xFF) << 24;
+ priv->state = EMR_READ_SIZE_HIGH_LOW_BYTE;
+ break;
+ case EMR_READ_SIZE_HIGH_LOW_BYTE:
+ /* Read the low byte of the size's high word */
+ priv->size |= (buf[i] & 0xFF) << 16;
+ priv->state = EMR_READ_SIZE_LOW_HIGH_BYTE;
+ break;
+ case EMR_READ_SIZE_LOW_HIGH_BYTE:
+ /* Read the high byte of the size's low word */
+ priv->size |= (buf [i] & 0xff) << 8;
+ priv->state = EMR_READ_SIZE_LOW_LOW_BYTE;
+ break;
+ case EMR_READ_SIZE_LOW_LOW_BYTE:
+ /* Read the high byte of the size's low word */
+ priv->size |= (buf [i] & 0xff);
+ /* The maximum chunk length is 2^31-1 */
+ if (G_LIKELY (priv->size <= (guint32) 0x7fffffff)) {
+ priv->state = EMR_READ_CHUNK_NAME;
+ /* Make sure sub_step is 0 before next step */
+ priv->sub_step = 0;
+ } else {
+ priv->state = EMR_FINISHED;
+ eog_debug_message (DEBUG_IMAGE_DATA,
+ "chunk size larger than "
+ "2^31-1; stopping parser");
+ }
+
+ break;
+ case EMR_READ_CHUNK_NAME:
+ /* Read the 4-byte chunk name */
+ if (priv->sub_step > 3)
+ g_assert_not_reached ();
+
+ priv->chunk_name[priv->sub_step] = buf[i];
+
+ if (priv->sub_step++ != 3)
+ break;
+
+ if (G_UNLIKELY (!priv->hasIHDR)) {
+ /* IHDR should be the first chunk in a PNG */
+ if (priv->size == 13
+ && memcmp (priv->chunk_name, "IHDR", 4) == 0){
+ priv->hasIHDR = TRUE;
+ } else {
+ /* Stop parsing if it is not */
+ priv->state = EMR_FINISHED;
+ }
+ }
+
+ /* Try to identify the chunk by its name.
+ * Already do some sanity checks where possible */
+ if (memcmp (priv->chunk_name, "iTXt", 4) == 0 &&
+ priv->size > (22 + 54) && priv->xmp_chunk == NULL) {
+ priv->state = EMR_READ_XMP_ITXT;
+ } else if (memcmp (priv->chunk_name, "iCCP", 4) == 0 &&
+ priv->icc_chunk == NULL) {
+ priv->state = EMR_READ_ICCP;
+ } else if (memcmp (priv->chunk_name, "sRGB", 4) == 0 &&
+ priv->sRGB_chunk == NULL && priv->size == 1) {
+ priv->state = EMR_READ_SRGB;
+ } else if (memcmp (priv->chunk_name, "cHRM", 4) == 0 &&
+ priv->cHRM_chunk == NULL && priv->size == 32) {
+ priv->state = EMR_READ_CHRM;
+ } else if (memcmp (priv->chunk_name, "gAMA", 4) == 0 &&
+ priv->gAMA_chunk == NULL && priv->size == 4) {
+ priv->state = EMR_READ_GAMA;
+ } else if (memcmp (priv->chunk_name, "IEND", 4) == 0) {
+ priv->state = EMR_FINISHED;
+ } else {
+ /* Skip chunk + 4-byte CRC32 value */
+ priv->size += 4;
+ priv->state = EMR_SKIP_BYTES;
+ }
+ priv->sub_step = 0;
+ break;
+ case EMR_SKIP_CRC:
+ /* Skip the 4-byte CRC32 value following every chunk */
+ priv->size = 4;
+ case EMR_SKIP_BYTES:
+ /* Skip chunk and start reading the size of the next one */
+ eog_debug_message (DEBUG_IMAGE_DATA,
+ "Skip bytes: %" G_GSIZE_FORMAT,
+ priv->size);
+
+ if (i + priv->size < len) {
+ i = i + priv->size - 1; /* the for-loop consumes the other byte */
+ priv->size = 0;
+ priv->state = EMR_READ_SIZE_HIGH_HIGH_BYTE;
+ }
+ else {
+ priv->size = (i + priv->size) - len;
+ i = len - 1;
+ }
+ break;
+ case EMR_CHECK_CRC:
+ /* Read the chunks CRC32 value from the file,... */
+ if (priv->sub_step == 0)
+ priv->target_crc = 0;
+
+ priv->target_crc |= buf[i] << ((3 - priv->sub_step) * 8);
+
+ if (priv->sub_step++ != 3)
+ break;
+
+ /* ...generate the chunks CRC32,... */
+ chunk_crc = crc32 (crc32 (0L, Z_NULL, 0), priv->chunk_name, 4);
+ chunk_crc = crc32 (chunk_crc, *priv->crc_chunk, *priv->crc_len);
+
+ eog_debug_message (DEBUG_IMAGE_DATA, "Checking CRC: Chunk: 0x%X - Target: 0x%X", chunk_crc, priv->target_crc);
+
+ /* ...and check if they match. If they don't throw
+ * the chunk away and stop parsing. */
+ if (priv->target_crc == chunk_crc) {
+ priv->state = EMR_READ_SIZE_HIGH_HIGH_BYTE;
+ } else {
+ g_free (*priv->crc_chunk);
+ *priv->crc_chunk = NULL;
+ *priv->crc_len = 0;
+ /* Stop parsing for security reasons */
+ priv->state = EMR_FINISHED;
+ }
+ priv->sub_step = 0;
+ break;
+ case EMR_READ_XMP_ITXT:
+ /* Extract an iTXt chunk possibly containing
+ * an XMP packet */
+ eog_debug_message (DEBUG_IMAGE_DATA,
+ "Read XMP Chunk - size: %"
+ G_GSIZE_FORMAT, priv->size);
+
+ if (priv->xmp_chunk == NULL) {
+ priv->xmp_chunk = g_new0 (guchar, priv->size);
+ priv->xmp_len = priv->size;
+ priv->crc_len = &priv->xmp_len;
+ priv->bytes_read = 0;
+ priv->crc_chunk = &priv->xmp_chunk;
+ }
+ eog_metadata_reader_png_get_next_block (priv,
+ priv->xmp_chunk,
+ &i, buf, len,
+ EMR_READ_XMP_ITXT);
+
+ if (priv->state == EMR_CHECK_CRC) {
+ /* Check if it is actually an XMP chunk.
+ * Throw it away if not.
+ * The check has 4 extra \0's to check
+ * if the chunk is configured correctly. */
+ if (memcmp (priv->xmp_chunk, "XML:com.adobe.xmp\0\0\0\0\0", 22) != 0) {
+ priv->state = EMR_SKIP_CRC;
+ g_free (priv->xmp_chunk);
+ priv->xmp_chunk = NULL;
+ priv->xmp_len = 0;
+ }
+ }
+ break;
+ case EMR_READ_ICCP:
+ /* Extract an iCCP chunk containing a
+ * deflated ICC profile. */
+ eog_debug_message (DEBUG_IMAGE_DATA,
+ "Read ICC Chunk - size: %"
+ G_GSIZE_FORMAT, priv->size);
+
+ if (priv->icc_chunk == NULL) {
+ priv->icc_chunk = g_new0 (guchar, priv->size);
+ priv->icc_len = priv->size;
+ priv->crc_len = &priv->icc_len;
+ priv->bytes_read = 0;
+ priv->crc_chunk = &priv->icc_chunk;
+ }
+
+ eog_metadata_reader_png_get_next_block (priv,
+ priv->icc_chunk,
+ &i, buf, len,
+ EMR_READ_ICCP);
+ break;
+ case EMR_READ_SRGB:
+ /* Extract the sRGB chunk. Marks the image data as
+ * being in sRGB colorspace. */
+ eog_debug_message (DEBUG_IMAGE_DATA,
+ "Read sRGB Chunk - value: %u", *(buf+i));
+
+ if (priv->sRGB_chunk == NULL) {
+ priv->sRGB_chunk = g_new0 (guchar, priv->size);
+ priv->sRGB_len = priv->size;
+ priv->crc_len = &priv->sRGB_len;
+ priv->bytes_read = 0;
+ priv->crc_chunk = &priv->sRGB_chunk;
+ }
+
+ eog_metadata_reader_png_get_next_block (priv,
+ priv->sRGB_chunk,
+ &i, buf, len,
+ EMR_READ_SRGB);
+ break;
+ case EMR_READ_CHRM:
+ /* Extract the cHRM chunk. Contains the coordinates of
+ * the image's whitepoint and primary chromacities. */
+ eog_debug_message (DEBUG_IMAGE_DATA,
+ "Read cHRM Chunk - size: %"
+ G_GSIZE_FORMAT, priv->size);
+
+ if (priv->cHRM_chunk == NULL) {
+ priv->cHRM_chunk = g_new0 (guchar, priv->size);
+ priv->cHRM_len = priv->size;
+ priv->crc_len = &priv->cHRM_len;
+ priv->bytes_read = 0;
+ priv->crc_chunk = &priv->cHRM_chunk;
+ }
+
+ eog_metadata_reader_png_get_next_block (priv,
+ priv->cHRM_chunk,
+ &i, buf, len,
+ EMR_READ_ICCP);
+ break;
+ case EMR_READ_GAMA:
+ /* Extract the gAMA chunk containing the
+ * image's gamma value */
+ eog_debug_message (DEBUG_IMAGE_DATA,
+ "Read gAMA-Chunk - size: %"
+ G_GSIZE_FORMAT, priv->size);
+
+ if (priv->gAMA_chunk == NULL) {
+ priv->gAMA_chunk = g_new0 (guchar, priv->size);
+ priv->gAMA_len = priv->size;
+ priv->crc_len = &priv->gAMA_len;
+ priv->bytes_read = 0;
+ priv->crc_chunk = &priv->gAMA_chunk;
+ }
+
+ eog_metadata_reader_png_get_next_block (priv,
+ priv->gAMA_chunk,
+ &i, buf, len,
+ EMR_READ_ICCP);
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+ }
+}
+
+#ifdef HAVE_EXEMPI
+
+/* skip the chunk ID */
+#define EOG_XMP_OFFSET (22)
+
+static gpointer
+eog_metadata_reader_png_get_xmp_data (EogMetadataReaderPng *emr )
+{
+ EogMetadataReaderPngPrivate *priv;
+ XmpPtr xmp = NULL;
+
+ g_return_val_if_fail (EOG_IS_METADATA_READER_PNG (emr), NULL);
+
+ priv = emr->priv;
+
+ if (priv->xmp_chunk != NULL) {
+ xmp = xmp_new (priv->xmp_chunk+EOG_XMP_OFFSET,
+ priv->xmp_len-EOG_XMP_OFFSET);
+ }
+
+ return (gpointer) xmp;
+}
+#endif
+
+#ifdef HAVE_LCMS
+
+#define EXTRACT_DOUBLE_UINT_BLOCK_OFFSET(chunk,offset,divider) \
+ (double)(GUINT32_FROM_BE(*((guint32*)((chunk)+((offset)*4))))/(double)(divider))
+
+/* This is the amount of memory the inflate output buffer gets increased by
+ * while decompressing the ICC profile */
+#define EOG_ICC_INFLATE_BUFFER_STEP 1024
+
+/* I haven't seen ICC profiles larger than 1MB yet.
+ * A maximum output buffer of 5MB should be enough. */
+#define EOG_ICC_INFLATE_BUFFER_LIMIT (1024*1024*5)
+
+static gpointer
+eog_metadata_reader_png_get_icc_profile (EogMetadataReaderPng *emr)
+{
+ EogMetadataReaderPngPrivate *priv;
+ cmsHPROFILE profile = NULL;
+
+ g_return_val_if_fail (EOG_IS_METADATA_READER_PNG (emr), NULL);
+
+ priv = emr->priv;
+
+ if (priv->icc_chunk) {
+ gpointer outbuf;
+ gsize offset = 0;
+ z_stream zstr;
+ int z_ret;
+
+ /* Use default allocation functions */
+ zstr.zalloc = Z_NULL;
+ zstr.zfree = Z_NULL;
+ zstr.opaque = Z_NULL;
+
+ /* Skip the name of the ICC profile */
+ while (*((guchar*)priv->icc_chunk+offset) != '\0')
+ offset++;
+ /* Ensure the compression method (deflate) */
+ if (*((guchar*)priv->icc_chunk+(++offset)) != '\0')
+ return NULL;
+ ++offset; //offset now points to the start of the deflated data
+
+ /* Prepare the zlib data structure for decompression */
+ zstr.next_in = priv->icc_chunk + offset;
+ zstr.avail_in = priv->icc_len - offset;
+ if (inflateInit (&zstr) != Z_OK) {
+ return NULL;
+ }
+
+ /* Prepare output buffer and make zlib aware of it */
+ outbuf = g_malloc (EOG_ICC_INFLATE_BUFFER_STEP);
+ zstr.next_out = outbuf;
+ zstr.avail_out = EOG_ICC_INFLATE_BUFFER_STEP;
+
+ do {
+ if (zstr.avail_out == 0) {
+ /* The output buffer was not large enough to
+ * hold all the decompressed data. Increase its
+ * size and continue decompression. */
+ gsize new_size = zstr.total_out + EOG_ICC_INFLATE_BUFFER_STEP;
+
+ if (G_UNLIKELY (new_size > EOG_ICC_INFLATE_BUFFER_LIMIT)) {
+ /* Enforce a memory limit for the output
+ * buffer to avoid possible OOM cases */
+ inflateEnd (&zstr);
+ g_free (outbuf);
+ eog_debug_message (DEBUG_IMAGE_DATA, "ICC profile is too large. Ignoring.");
+ return NULL;
+ }
+ outbuf = g_realloc(outbuf, new_size);
+ zstr.avail_out = EOG_ICC_INFLATE_BUFFER_STEP;
+ zstr.next_out = outbuf + zstr.total_out;
+ }
+ z_ret = inflate (&zstr, Z_SYNC_FLUSH);
+ } while (z_ret == Z_OK);
+
+ if (G_UNLIKELY (z_ret != Z_STREAM_END)) {
+ eog_debug_message (DEBUG_IMAGE_DATA, "Error while inflating ICC profile: %s (%d)", zstr.msg, z_ret);
+ inflateEnd (&zstr);
+ g_free (outbuf);
+ return NULL;
+ }
+
+ profile = cmsOpenProfileFromMem(outbuf, zstr.total_out);
+ inflateEnd (&zstr);
+ g_free (outbuf);
+
+ eog_debug_message (DEBUG_LCMS, "PNG has %s ICC profile", profile ? "valid" : "invalid");
+ }
+
+ if (!profile && priv->sRGB_chunk) {
+ eog_debug_message (DEBUG_LCMS, "PNG is sRGB");
+ /* If the file has an sRGB chunk the image data is in the sRGB
+ * colorspace. lcms has a built-in sRGB profile. */
+
+ profile = cmsCreate_sRGBProfile ();
+ }
+
+ if (!profile && priv->cHRM_chunk) {
+ cmsCIExyY whitepoint;
+ cmsCIExyYTRIPLE primaries;
+ cmsToneCurve *gamma[3];
+ double gammaValue = 2.2; // 2.2 should be a sane default gamma
+
+ /* This uglyness extracts the chromacity and whitepoint values
+ * from a PNG's cHRM chunk. These can be accurate up to the
+ * 5th decimal point.
+ * They are saved as integer values multiplied by 100000. */
+
+ eog_debug_message (DEBUG_LCMS, "Trying to calculate color profile");
+
+ whitepoint.x = EXTRACT_DOUBLE_UINT_BLOCK_OFFSET (priv->cHRM_chunk, 0, 100000);
+ whitepoint.y = EXTRACT_DOUBLE_UINT_BLOCK_OFFSET (priv->cHRM_chunk, 1, 100000);
+
+ primaries.Red.x = EXTRACT_DOUBLE_UINT_BLOCK_OFFSET (priv->cHRM_chunk, 2, 100000);
+ primaries.Red.y = EXTRACT_DOUBLE_UINT_BLOCK_OFFSET (priv->cHRM_chunk, 3, 100000);
+ primaries.Green.x = EXTRACT_DOUBLE_UINT_BLOCK_OFFSET (priv->cHRM_chunk, 4, 100000);
+ primaries.Green.y = EXTRACT_DOUBLE_UINT_BLOCK_OFFSET (priv->cHRM_chunk, 5, 100000);
+ primaries.Blue.x = EXTRACT_DOUBLE_UINT_BLOCK_OFFSET (priv->cHRM_chunk, 6, 100000);
+ primaries.Blue.y = EXTRACT_DOUBLE_UINT_BLOCK_OFFSET (priv->cHRM_chunk, 7, 100000);
+
+ primaries.Red.Y = primaries.Green.Y = primaries.Blue.Y = 1.0;
+
+ /* If the gAMA_chunk is present use its value which is saved
+ * the same way as the whitepoint. Use 2.2 as default value if
+ * the chunk is not present. */
+ if (priv->gAMA_chunk)
+ gammaValue = (double) 1.0/EXTRACT_DOUBLE_UINT_BLOCK_OFFSET (priv->gAMA_chunk, 0, 100000);
+
+ gamma[0] = gamma[1] = gamma[2] = cmsBuildGamma (NULL, gammaValue);
+
+ profile = cmsCreateRGBProfile (&whitepoint, &primaries, gamma);
+
+ cmsFreeToneCurve(gamma[0]);
+ }
+
+ return profile;
+}
+#endif
+
+static void
+eog_metadata_reader_png_init_emr_iface (gpointer g_iface, gpointer iface_data)
+{
+ EogMetadataReaderInterface *iface;
+
+ iface = (EogMetadataReaderInterface*) g_iface;
+
+ iface->consume =
+ (void (*) (EogMetadataReader *self, const guchar *buf, guint len))
+ eog_metadata_reader_png_consume;
+ iface->finished =
+ (gboolean (*) (EogMetadataReader *self))
+ eog_metadata_reader_png_finished;
+#ifdef HAVE_LCMS
+ iface->get_icc_profile =
+ (cmsHPROFILE (*) (EogMetadataReader *self))
+ eog_metadata_reader_png_get_icc_profile;
+#endif
+#ifdef HAVE_EXEMPI
+ iface->get_xmp_ptr =
+ (gpointer (*) (EogMetadataReader *self))
+ eog_metadata_reader_png_get_xmp_data;
+#endif
+}
diff --git a/src/eog-metadata-reader-png.h b/src/eog-metadata-reader-png.h
new file mode 100644
index 0000000..da41b40
--- /dev/null
+++ b/src/eog-metadata-reader-png.h
@@ -0,0 +1,55 @@
+/* Eye Of GNOME -- PNG Metadata Reader
+ *
+ * Copyright (C) 2008 The Free Software Foundation
+ *
+ * Author: Felix Riemann <friemann svn gnome org>
+ *
+ * Based on the old EogMetadataReader code.
+ *
+ * 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 Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef _EOG_METADATA_READER_PNG_H_
+#define _EOG_METADATA_READER_PNG_H_
+
+G_BEGIN_DECLS
+
+#define EOG_TYPE_METADATA_READER_PNG (eog_metadata_reader_png_get_type ())
+#define EOG_METADATA_READER_PNG(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), EOG_TYPE_METADATA_READER_PNG, EogMetadataReaderPng))
+#define EOG_METADATA_READER_PNG_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), EOG_TYPE_METADATA_READER_PNG, EogMetadataReaderPngClass))
+#define EOG_IS_METADATA_READER_PNG(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), EOG_TYPE_METADATA_READER_PNG))
+#define EOG_IS_METADATA_READER_PNG_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), EOG_TYPE_METADATA_READER_PNG))
+#define EOG_METADATA_READER_PNG_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), EOG_TYPE_METADATA_READER_PNG, EogMetadataReaderPngClass))
+
+typedef struct _EogMetadataReaderPng EogMetadataReaderPng;
+typedef struct _EogMetadataReaderPngClass EogMetadataReaderPngClass;
+typedef struct _EogMetadataReaderPngPrivate EogMetadataReaderPngPrivate;
+
+struct _EogMetadataReaderPng {
+ GObject parent;
+
+ EogMetadataReaderPngPrivate *priv;
+};
+
+struct _EogMetadataReaderPngClass {
+ GObjectClass parent_klass;
+};
+
+G_GNUC_INTERNAL
+GType eog_metadata_reader_png_get_type (void) G_GNUC_CONST;
+
+G_END_DECLS
+
+#endif /* _EOG_METADATA_READER_PNG_H_ */
diff --git a/src/eog-metadata-reader.c b/src/eog-metadata-reader.c
new file mode 100644
index 0000000..29fb70b
--- /dev/null
+++ b/src/eog-metadata-reader.c
@@ -0,0 +1,132 @@
+/* Eye Of GNOME -- Metadata Reader Interface
+ *
+ * Copyright (C) 2008-2011 The Free Software Foundation
+ *
+ * Author: Felix Riemann <friemann svn gnome org>
+ *
+ * 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 Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "eog-metadata-reader.h"
+#include "eog-metadata-reader-jpg.h"
+#include "eog-metadata-reader-png.h"
+#include "eog-debug.h"
+
+G_DEFINE_INTERFACE (EogMetadataReader, eog_metadata_reader, G_TYPE_INVALID)
+
+EogMetadataReader*
+eog_metadata_reader_new (EogMetadataFileType type)
+{
+ EogMetadataReader *emr;
+
+ switch (type) {
+ case EOG_METADATA_JPEG:
+ emr = EOG_METADATA_READER (g_object_new (EOG_TYPE_METADATA_READER_JPG, NULL));
+ break;
+ case EOG_METADATA_PNG:
+ emr = EOG_METADATA_READER (g_object_new (EOG_TYPE_METADATA_READER_PNG, NULL));
+ break;
+ default:
+ emr = NULL;
+ break;
+ }
+
+ return emr;
+}
+
+gboolean
+eog_metadata_reader_finished (EogMetadataReader *emr)
+{
+ g_return_val_if_fail (EOG_IS_METADATA_READER (emr), TRUE);
+
+ return EOG_METADATA_READER_GET_INTERFACE (emr)->finished (emr);
+}
+
+
+void
+eog_metadata_reader_consume (EogMetadataReader *emr, const guchar *buf, guint len)
+{
+ EOG_METADATA_READER_GET_INTERFACE (emr)->consume (emr, buf, len);
+}
+
+/* Returns the raw exif data. NOTE: The caller of this function becomes
+ * the new owner of this piece of memory and is responsible for freeing it!
+ */
+void
+eog_metadata_reader_get_exif_chunk (EogMetadataReader *emr, guchar **data, guint *len)
+{
+ g_return_if_fail (data != NULL && len != NULL);
+
+ EOG_METADATA_READER_GET_INTERFACE (emr)->get_raw_exif (emr, data, len);
+}
+
+#ifdef HAVE_EXIF
+ExifData*
+eog_metadata_reader_get_exif_data (EogMetadataReader *emr)
+{
+ return EOG_METADATA_READER_GET_INTERFACE (emr)->get_exif_data (emr);
+}
+#endif
+
+#ifdef HAVE_EXEMPI
+XmpPtr
+eog_metadata_reader_get_xmp_data (EogMetadataReader *emr)
+{
+ return EOG_METADATA_READER_GET_INTERFACE (emr)->get_xmp_ptr (emr);
+}
+#endif
+
+#ifdef HAVE_LCMS
+cmsHPROFILE
+eog_metadata_reader_get_icc_profile (EogMetadataReader *emr)
+{
+ return EOG_METADATA_READER_GET_INTERFACE (emr)->get_icc_profile (emr);
+}
+#endif
+
+/* Default vfunc that simply clears the output if not overriden by the
+ implementing class. This mimics the old behavour of get_exif_chunk(). */
+static void
+_eog_metadata_reader_default_get_raw_exif (EogMetadataReader *emr,
+ guchar **data, guint *length)
+{
+ g_return_if_fail (data != NULL && length != NULL);
+
+ *data = NULL;
+ *length = 0;
+}
+
+/* Default vfunc that simply returns NULL if not overriden by the implementing
+ class. Mimics the old fallback behaviour of the getter functions. */
+static gpointer
+_eog_metadata_reader_default_get_null (EogMetadataReader *emr)
+{
+ return NULL;
+}
+
+static void
+eog_metadata_reader_default_init (EogMetadataReaderInterface *iface)
+{
+ /* consume and finished are required to be implemented */
+ /* Not-implemented funcs return NULL by default */
+ iface->get_raw_exif = _eog_metadata_reader_default_get_raw_exif;
+ iface->get_exif_data = _eog_metadata_reader_default_get_null;
+ iface->get_icc_profile = _eog_metadata_reader_default_get_null;
+ iface->get_xmp_ptr = _eog_metadata_reader_default_get_null;
+}
diff --git a/src/eog-metadata-reader.h b/src/eog-metadata-reader.h
new file mode 100644
index 0000000..81e976b
--- /dev/null
+++ b/src/eog-metadata-reader.h
@@ -0,0 +1,112 @@
+/* Eye Of GNOME -- Metadata Reader Interface
+ *
+ * Copyright (C) 2008 The Free Software Foundation
+ *
+ * Author: Felix Riemann <friemann svn gnome org>
+ *
+ * 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 Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef _EOG_METADATA_READER_H_
+#define _EOG_METADATA_READER_H_
+
+#include <glib-object.h>
+#if HAVE_EXIF
+#include "eog-exif-util.h"
+#endif
+#if HAVE_EXEMPI
+#include <exempi/xmp.h>
+#endif
+#if HAVE_LCMS
+#include <lcms2.h>
+#endif
+
+G_BEGIN_DECLS
+
+#define EOG_TYPE_METADATA_READER (eog_metadata_reader_get_type ())
+#define EOG_METADATA_READER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), EOG_TYPE_METADATA_READER, EogMetadataReader))
+#define EOG_IS_METADATA_READER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), EOG_TYPE_METADATA_READER))
+#define EOG_METADATA_READER_GET_INTERFACE(o) (G_TYPE_INSTANCE_GET_INTERFACE ((o), EOG_TYPE_METADATA_READER, EogMetadataReaderInterface))
+
+typedef struct _EogMetadataReader EogMetadataReader;
+typedef struct _EogMetadataReaderInterface EogMetadataReaderInterface;
+
+struct _EogMetadataReaderInterface {
+ GTypeInterface parent;
+
+ void (*consume) (EogMetadataReader *self,
+ const guchar *buf,
+ guint len);
+
+ gboolean (*finished) (EogMetadataReader *self);
+
+ void (*get_raw_exif) (EogMetadataReader *self,
+ guchar **data,
+ guint *len);
+
+ gpointer (*get_exif_data) (EogMetadataReader *self);
+
+ gpointer (*get_icc_profile) (EogMetadataReader *self);
+
+ gpointer (*get_xmp_ptr) (EogMetadataReader *self);
+};
+
+typedef enum {
+ EOG_METADATA_JPEG,
+ EOG_METADATA_PNG
+} EogMetadataFileType;
+
+G_GNUC_INTERNAL
+GType eog_metadata_reader_get_type (void) G_GNUC_CONST;
+
+G_GNUC_INTERNAL
+EogMetadataReader* eog_metadata_reader_new (EogMetadataFileType type);
+
+G_GNUC_INTERNAL
+void eog_metadata_reader_consume (EogMetadataReader *emr,
+ const guchar *buf,
+ guint len);
+
+G_GNUC_INTERNAL
+gboolean eog_metadata_reader_finished (EogMetadataReader *emr);
+
+G_GNUC_INTERNAL
+void eog_metadata_reader_get_exif_chunk (EogMetadataReader *emr,
+ guchar **data,
+ guint *len);
+
+#ifdef HAVE_EXIF
+G_GNUC_INTERNAL
+ExifData* eog_metadata_reader_get_exif_data (EogMetadataReader *emr);
+#endif
+
+#ifdef HAVE_EXEMPI
+G_GNUC_INTERNAL
+XmpPtr eog_metadata_reader_get_xmp_data (EogMetadataReader *emr);
+#endif
+
+#if 0
+gpointer eog_metadata_reader_get_iptc_chunk (EogMetadataReader *emr);
+IptcData* eog_metadata_reader_get_iptc_data (EogMetadataReader *emr);
+#endif
+
+#ifdef HAVE_LCMS
+G_GNUC_INTERNAL
+cmsHPROFILE eog_metadata_reader_get_icc_profile (EogMetadataReader *emr);
+#endif
+
+G_END_DECLS
+
+#endif /* _EOG_METADATA_READER_H_ */
diff --git a/src/eog-print-image-setup.c b/src/eog-print-image-setup.c
new file mode 100644
index 0000000..4c7a726
--- /dev/null
+++ b/src/eog-print-image-setup.c
@@ -0,0 +1,1139 @@
+/* Eye Of GNOME -- Print Dialog Custom Widget
+ *
+ * Copyright (C) 2006-2007 The Free Software Foundation
+ *
+ * Author: Claudio Saavedra <csaavedra alumnos utalca cl>
+ *
+ * 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 Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <gtk/gtk.h>
+#include <gtk/gtkunixprint.h>
+
+#include <glib/gi18n.h>
+#include <glib/gprintf.h>
+
+#ifdef HAVE__NL_MEASUREMENT_MEASUREMENT
+#include <langinfo.h>
+#endif
+
+#include "eog-print-image-setup.h"
+#include "eog-print-preview.h"
+
+/**
+ * SECTION:
+ * @Title: Printing Custom Widget
+ * @Short_Description: a custom widget to setup image prints.
+ * @Stability_Level: Internal
+ * @See_Also: #EogPrintPreview
+ *
+ * This widget is to be used as the custom widget in a #GtkPrintUnixDialog in
+ * EOG. Through it, you can set the position and scaling of a image
+ * interactively.
+ */
+
+#define EOG_PRINT_IMAGE_SETUP_GET_PRIVATE(object) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((object), EOG_TYPE_PRINT_IMAGE_SETUP, EogPrintImageSetupPrivate))
+
+G_DEFINE_TYPE (EogPrintImageSetup, eog_print_image_setup, GTK_TYPE_GRID);
+
+struct _EogPrintImageSetupPrivate {
+ GtkWidget *left;
+ GtkWidget *right;
+ GtkWidget *top;
+ GtkWidget *bottom;
+
+ GtkWidget *center;
+
+ GtkWidget *width;
+ GtkWidget *height;
+
+ GtkWidget *scaling;
+ GtkWidget *unit;
+
+ GtkUnit current_unit;
+
+ EogImage *image;
+ GtkPageSetup *page_setup;
+
+ GtkWidget *preview;
+};
+
+enum {
+ PROP_0,
+ PROP_IMAGE,
+ PROP_PAGE_SETUP
+};
+
+enum {
+ CENTER_NONE,
+ CENTER_HORIZONTAL,
+ CENTER_VERTICAL,
+ CENTER_BOTH
+};
+
+enum {
+ CHANGE_HORIZ,
+ CHANGE_VERT
+};
+
+enum {
+ UNIT_INCH,
+ UNIT_MM
+};
+
+#define FACTOR_INCH_TO_MM 25.4
+#define FACTOR_INCH_TO_PIXEL 72.
+#define FACTOR_MM_TO_INCH 0.03937007874015748
+#define FACTOR_MM_TO_PIXEL 2.834645669
+
+static void eog_print_image_setup_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec);
+static void eog_print_image_setup_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec);
+
+static void on_left_value_changed (GtkSpinButton *spinbutton, gpointer user_data);
+static void on_right_value_changed (GtkSpinButton *spinbutton, gpointer user_data);
+static void on_top_value_changed (GtkSpinButton *spinbutton, gpointer user_data);
+static void on_bottom_value_changed (GtkSpinButton *spinbutton, gpointer user_data);
+
+static void on_width_value_changed (GtkSpinButton *spinbutton, gpointer user_data);
+static void on_height_value_changed (GtkSpinButton *spinbutton, gpointer user_data);
+
+
+static void
+block_handlers (EogPrintImageSetup *setup)
+{
+ EogPrintImageSetupPrivate *priv = setup->priv;
+
+ g_signal_handlers_block_by_func (priv->left, on_left_value_changed, setup);
+ g_signal_handlers_block_by_func (priv->right, on_right_value_changed, setup);
+ g_signal_handlers_block_by_func (priv->width, on_width_value_changed, setup);
+ g_signal_handlers_block_by_func (priv->top, on_top_value_changed, setup);
+ g_signal_handlers_block_by_func (priv->bottom, on_bottom_value_changed, setup);
+ g_signal_handlers_block_by_func (priv->height, on_height_value_changed, setup);
+}
+
+static void
+unblock_handlers (EogPrintImageSetup *setup)
+{
+ EogPrintImageSetupPrivate *priv = setup->priv;
+
+ g_signal_handlers_unblock_by_func (priv->left, on_left_value_changed, setup);
+ g_signal_handlers_unblock_by_func (priv->right, on_right_value_changed, setup);
+ g_signal_handlers_unblock_by_func (priv->width, on_width_value_changed, setup);
+ g_signal_handlers_unblock_by_func (priv->top, on_top_value_changed, setup);
+ g_signal_handlers_unblock_by_func (priv->bottom, on_bottom_value_changed, setup);
+ g_signal_handlers_unblock_by_func (priv->height, on_height_value_changed, setup);
+}
+
+static gdouble
+get_scale_to_px_factor (EogPrintImageSetup *setup)
+{
+ gdouble factor = 0.;
+
+ switch (setup->priv->current_unit) {
+ case GTK_UNIT_MM:
+ factor = FACTOR_MM_TO_PIXEL;
+ break;
+ case GTK_UNIT_INCH:
+ factor = FACTOR_INCH_TO_PIXEL;
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ return factor;
+}
+
+static gdouble
+get_max_percentage (EogPrintImageSetup *setup)
+{
+ EogPrintImageSetupPrivate *priv = setup->priv;
+ gdouble p_width, p_height;
+ gdouble width, height;
+ gint pix_width, pix_height;
+ gdouble perc;
+
+ p_width = gtk_page_setup_get_page_width (priv->page_setup, GTK_UNIT_INCH);
+ p_height = gtk_page_setup_get_page_height (priv->page_setup, GTK_UNIT_INCH);
+
+ eog_image_get_size (priv->image, &pix_width, &pix_height);
+
+ width = (gdouble)pix_width/FACTOR_INCH_TO_PIXEL;
+ height = (gdouble)pix_height/FACTOR_INCH_TO_PIXEL;
+
+ if (p_width > width && p_height > height) {
+ perc = 1.;
+ } else {
+ perc = MIN (p_width/width, p_height/height);
+ }
+
+ return perc;
+}
+
+static void
+center (gdouble page_width,
+ gdouble width,
+ GtkSpinButton *s_left,
+ GtkSpinButton *s_right)
+{
+ gdouble left, right;
+
+ left = (page_width - width)/2;
+ right = page_width - left - width;
+ gtk_spin_button_set_value (s_left, left);
+ gtk_spin_button_set_value (s_right, right);
+}
+
+static void
+on_center_changed (GtkComboBox *combobox,
+ gpointer user_data)
+{
+ EogPrintImageSetup *setup;
+ EogPrintImageSetupPrivate *priv;
+ gint active;
+
+ setup = EOG_PRINT_IMAGE_SETUP (user_data);
+ priv = setup->priv;
+
+ active = gtk_combo_box_get_active (combobox);
+
+ switch (active) {
+ case CENTER_HORIZONTAL:
+ center (gtk_page_setup_get_page_width (priv->page_setup,
+ priv->current_unit),
+ gtk_spin_button_get_value (GTK_SPIN_BUTTON (priv->width)),
+ GTK_SPIN_BUTTON (priv->left),
+ GTK_SPIN_BUTTON (priv->right));
+ break;
+ case CENTER_VERTICAL:
+ center (gtk_page_setup_get_page_height (priv->page_setup,
+ priv->current_unit),
+ gtk_spin_button_get_value (GTK_SPIN_BUTTON (priv->height)),
+ GTK_SPIN_BUTTON (priv->top),
+ GTK_SPIN_BUTTON (priv->bottom));
+ break;
+ case CENTER_BOTH:
+ center (gtk_page_setup_get_page_width (priv->page_setup,
+ priv->current_unit),
+ gtk_spin_button_get_value (GTK_SPIN_BUTTON (priv->width)),
+ GTK_SPIN_BUTTON (priv->left),
+ GTK_SPIN_BUTTON (priv->right));
+ center (gtk_page_setup_get_page_height (priv->page_setup,
+ priv->current_unit),
+ gtk_spin_button_get_value (GTK_SPIN_BUTTON (priv->height)),
+ GTK_SPIN_BUTTON (priv->top),
+ GTK_SPIN_BUTTON (priv->bottom));
+ break;
+ case CENTER_NONE:
+ default:
+ break;
+ }
+
+ gtk_combo_box_set_active (combobox, active);
+}
+
+static void
+update_image_pos_ranges (EogPrintImageSetup *setup,
+ gdouble page_width,
+ gdouble page_height,
+ gdouble width,
+ gdouble height)
+{
+ EogPrintImageSetupPrivate *priv;
+
+ priv = setup->priv;
+
+ gtk_spin_button_set_range (GTK_SPIN_BUTTON (priv->left),
+ 0, page_width - width);
+ gtk_spin_button_set_range (GTK_SPIN_BUTTON (priv->right),
+ 0, page_width - width);
+ gtk_spin_button_set_range (GTK_SPIN_BUTTON (priv->top),
+ 0, page_height - height);
+ gtk_spin_button_set_range (GTK_SPIN_BUTTON (priv->bottom),
+ 0, page_height - height);
+}
+
+static gboolean
+on_scale_changed (GtkRange *range,
+ gpointer user_data)
+{
+ gdouble scale;
+ gdouble width, height;
+ gint pix_width, pix_height;
+ gdouble left, right, top, bottom;
+ gdouble page_width, page_height;
+ EogPrintImageSetupPrivate *priv;
+ EogPrintImageSetup *setup;
+ gdouble factor;
+ EogImage *image;
+
+ setup = EOG_PRINT_IMAGE_SETUP (user_data);
+ priv = setup->priv;
+
+ gtk_combo_box_set_active (GTK_COMBO_BOX (priv->center), CENTER_NONE);
+
+ image = priv->image;
+ eog_image_get_size (image, &pix_width, &pix_height);
+
+ factor = get_scale_to_px_factor (setup);
+
+ width = (gdouble)pix_width/factor;
+ height = (gdouble)pix_height/factor;
+
+ left = gtk_spin_button_get_value (GTK_SPIN_BUTTON (priv->left));
+ top = gtk_spin_button_get_value (GTK_SPIN_BUTTON (priv->top));
+
+ scale = CLAMP (0.01*gtk_range_get_value (range), 0, get_max_percentage (setup));
+
+ eog_print_preview_set_scale (EOG_PRINT_PREVIEW (priv->preview), scale);
+
+ width *= scale;
+ height *= scale;
+
+ page_width = gtk_page_setup_get_page_width (priv->page_setup, priv->current_unit);
+ page_height = gtk_page_setup_get_page_height (priv->page_setup, priv->current_unit);
+
+ update_image_pos_ranges (setup, page_width, page_height, width, height);
+
+ right = page_width - left - width;
+ bottom = page_height - top - height;
+
+ gtk_spin_button_set_value (GTK_SPIN_BUTTON (priv->width), width);
+ gtk_spin_button_set_value (GTK_SPIN_BUTTON (priv->height), height);
+ gtk_spin_button_set_value (GTK_SPIN_BUTTON (priv->right), right);
+ gtk_spin_button_set_value (GTK_SPIN_BUTTON (priv->bottom), bottom);
+
+ return FALSE;
+}
+
+static gchar *
+on_scale_format_value (GtkScale *scale,
+ gdouble value)
+{
+ return g_strdup_printf ("%i%%", (gint)value);
+}
+
+static void
+position_values_changed (EogPrintImageSetup *setup,
+ GtkWidget *w_changed,
+ GtkWidget *w_to_update,
+ GtkWidget *w_size,
+ gdouble total_size,
+ gint change)
+{
+ EogPrintImageSetupPrivate *priv;
+ gdouble changed, to_update, size;
+ gdouble pos;
+
+ priv = setup->priv;
+ size = gtk_spin_button_get_value (GTK_SPIN_BUTTON (w_size));
+ changed = gtk_spin_button_get_value (GTK_SPIN_BUTTON (w_changed));
+
+ to_update = total_size - changed - size;
+ gtk_spin_button_set_value (GTK_SPIN_BUTTON (w_to_update), to_update);
+ gtk_combo_box_set_active (GTK_COMBO_BOX (priv->center), CENTER_NONE);
+
+ switch (change) {
+ case CHANGE_HORIZ:
+ pos = gtk_spin_button_get_value (GTK_SPIN_BUTTON (setup->priv->left));
+ if (setup->priv->current_unit == GTK_UNIT_MM) {
+ pos *= FACTOR_MM_TO_INCH;
+ }
+ eog_print_preview_set_image_position (EOG_PRINT_PREVIEW (priv->preview), pos, -1);
+ break;
+ case CHANGE_VERT:
+ pos = gtk_spin_button_get_value (GTK_SPIN_BUTTON (setup->priv->top));
+ if (setup->priv->current_unit == GTK_UNIT_MM) {
+ pos *= FACTOR_MM_TO_INCH;
+ }
+ eog_print_preview_set_image_position (EOG_PRINT_PREVIEW (priv->preview), -1, pos);
+ break;
+ }
+}
+
+static void
+on_left_value_changed (GtkSpinButton *spinbutton,
+ gpointer user_data)
+{
+ EogPrintImageSetup *setup;
+ EogPrintImageSetupPrivate *priv;
+
+ setup = EOG_PRINT_IMAGE_SETUP (user_data);
+ priv = setup->priv;
+
+ position_values_changed (setup,
+ priv->left, priv->right, priv->width,
+ gtk_page_setup_get_page_width (priv->page_setup,
+ priv->current_unit),
+ CHANGE_HORIZ);
+}
+
+static void
+on_right_value_changed (GtkSpinButton *spinbutton,
+ gpointer user_data)
+{
+ EogPrintImageSetupPrivate *priv;
+
+ priv = EOG_PRINT_IMAGE_SETUP (user_data)->priv;
+
+ position_values_changed (EOG_PRINT_IMAGE_SETUP (user_data),
+ priv->right, priv->left, priv->width,
+ gtk_page_setup_get_page_width (priv->page_setup,
+ priv->current_unit),
+ CHANGE_HORIZ);
+}
+
+static void
+on_top_value_changed (GtkSpinButton *spinbutton,
+ gpointer user_data)
+{
+ EogPrintImageSetupPrivate *priv;
+
+ priv = EOG_PRINT_IMAGE_SETUP (user_data)->priv;
+
+ position_values_changed (EOG_PRINT_IMAGE_SETUP (user_data),
+ priv->top, priv->bottom, priv->height,
+ gtk_page_setup_get_page_height (priv->page_setup,
+ priv->current_unit),
+ CHANGE_VERT);
+}
+
+static void
+on_bottom_value_changed (GtkSpinButton *spinbutton,
+ gpointer user_data)
+{
+ EogPrintImageSetupPrivate *priv;
+
+ priv = EOG_PRINT_IMAGE_SETUP (user_data)->priv;
+
+ position_values_changed (EOG_PRINT_IMAGE_SETUP (user_data),
+ priv->bottom, priv->top, priv->height,
+ gtk_page_setup_get_page_height (priv->page_setup,
+ priv->current_unit),
+ CHANGE_VERT);
+}
+
+static void
+size_changed (EogPrintImageSetup *setup,
+ GtkWidget *w_size_x,
+ GtkWidget *w_size_y,
+ GtkWidget *w_margin_x_1,
+ GtkWidget *w_margin_x_2,
+ GtkWidget *w_margin_y_1,
+ GtkWidget *w_margin_y_2,
+ gdouble page_size_x,
+ gdouble page_size_y,
+ gint change)
+{
+ EogPrintImageSetupPrivate *priv;
+ gdouble margin_x_1, margin_x_2;
+ gdouble margin_y_1, margin_y_2;
+ gdouble orig_size_x = -1, orig_size_y = -1, scale;
+ gdouble size_x, size_y;
+ gint pix_width, pix_height;
+ gdouble factor;
+
+ priv = setup->priv;
+
+ size_x = gtk_spin_button_get_value (GTK_SPIN_BUTTON (w_size_x));
+ margin_x_1 = gtk_spin_button_get_value (GTK_SPIN_BUTTON (w_margin_x_1));
+ margin_y_1 = gtk_spin_button_get_value (GTK_SPIN_BUTTON (w_margin_y_1));
+
+ eog_image_get_size (priv->image, &pix_width, &pix_height);
+
+ factor = get_scale_to_px_factor (setup);
+
+ switch (change) {
+ case CHANGE_HORIZ:
+ orig_size_x = (gdouble) pix_width / factor;
+ orig_size_y = (gdouble) pix_height / factor;
+ break;
+ case CHANGE_VERT:
+ orig_size_y = (gdouble) pix_width / factor;
+ orig_size_x = (gdouble) pix_height / factor;
+ break;
+ }
+
+ scale = CLAMP (size_x / orig_size_x, 0, 1);
+
+ size_y = scale * orig_size_y;
+
+ margin_x_2 = page_size_x - margin_x_1 - size_x;
+ margin_y_2 = page_size_y - margin_y_1 - size_y;
+
+ eog_print_preview_set_scale (EOG_PRINT_PREVIEW (priv->preview), scale);
+
+ switch (change) {
+ case CHANGE_HORIZ:
+ update_image_pos_ranges (setup, page_size_x, page_size_y, size_x, size_y);
+ break;
+ case CHANGE_VERT:
+ update_image_pos_ranges (setup, page_size_y, page_size_x, size_y, size_x);
+ break;
+ }
+
+ gtk_range_set_value (GTK_RANGE (priv->scaling), 100*scale);
+
+ gtk_spin_button_set_value (GTK_SPIN_BUTTON (w_margin_x_2), margin_x_2);
+ gtk_spin_button_set_value (GTK_SPIN_BUTTON (w_size_y), size_y);
+ gtk_spin_button_set_value (GTK_SPIN_BUTTON (w_margin_y_2), margin_y_2);
+
+ gtk_combo_box_set_active (GTK_COMBO_BOX (priv->center), CENTER_NONE);
+}
+
+static void
+on_width_value_changed (GtkSpinButton *spinbutton,
+ gpointer user_data)
+{
+ EogPrintImageSetupPrivate *priv = EOG_PRINT_IMAGE_SETUP (user_data)->priv;
+
+ size_changed (EOG_PRINT_IMAGE_SETUP (user_data),
+ priv->width, priv->height,
+ priv->left, priv->right,
+ priv->top, priv->bottom,
+ gtk_page_setup_get_page_width (priv->page_setup,
+ priv->current_unit),
+ gtk_page_setup_get_page_height (priv->page_setup,
+ priv->current_unit),
+ CHANGE_HORIZ);
+}
+
+static void
+on_height_value_changed (GtkSpinButton *spinbutton,
+ gpointer user_data)
+{
+ EogPrintImageSetupPrivate *priv = EOG_PRINT_IMAGE_SETUP (user_data)->priv;
+
+ size_changed (EOG_PRINT_IMAGE_SETUP (user_data),
+ priv->height, priv->width,
+ priv->top, priv->bottom,
+ priv->left, priv->right,
+ gtk_page_setup_get_page_height (priv->page_setup,
+ priv->current_unit),
+ gtk_page_setup_get_page_width (priv->page_setup,
+ priv->current_unit),
+ CHANGE_VERT);
+}
+
+static void
+change_unit (GtkSpinButton *spinbutton,
+ gdouble factor,
+ gint digits,
+ gdouble step,
+ gdouble page)
+{
+ gdouble value;
+ gdouble range;
+
+ gtk_spin_button_get_range (spinbutton, NULL, &range);
+ range *= factor;
+
+ value = gtk_spin_button_get_value (spinbutton);
+ value *= factor;
+
+ gtk_spin_button_set_range (spinbutton, 0, range);
+ gtk_spin_button_set_value (spinbutton, value);
+ gtk_spin_button_set_digits (spinbutton, digits);
+ gtk_spin_button_set_increments (spinbutton, step, page);
+}
+
+static void
+set_scale_unit (EogPrintImageSetup *setup,
+ GtkUnit unit)
+{
+ EogPrintImageSetupPrivate *priv = setup->priv;
+ gdouble factor;
+ gdouble step, page;
+ gint digits;
+
+ if (G_UNLIKELY (priv->current_unit == unit))
+ return;
+
+ switch (unit) {
+ case GTK_UNIT_MM:
+ factor = FACTOR_INCH_TO_MM;
+ digits = 0;
+ step = 1;
+ page = 10;
+ break;
+ case GTK_UNIT_INCH:
+ factor = FACTOR_MM_TO_INCH;
+ digits = 2;
+ step = 0.01;
+ page = 0.1;
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ block_handlers (setup);
+
+ change_unit (GTK_SPIN_BUTTON (priv->width), factor, digits, step, page);
+ change_unit (GTK_SPIN_BUTTON (priv->height), factor, digits, step, page);
+ change_unit (GTK_SPIN_BUTTON (priv->left), factor, digits, step, page);
+ change_unit (GTK_SPIN_BUTTON (priv->right), factor, digits, step, page);
+ change_unit (GTK_SPIN_BUTTON (priv->top), factor, digits, step, page);
+ change_unit (GTK_SPIN_BUTTON (priv->bottom), factor, digits, step, page);
+
+ unblock_handlers (setup);
+
+ priv->current_unit = unit;
+}
+
+static void
+on_unit_changed (GtkComboBox *combobox,
+ gpointer user_data)
+{
+ GtkUnit unit = GTK_UNIT_INCH;
+
+ switch (gtk_combo_box_get_active (combobox)) {
+ case UNIT_INCH:
+ unit = GTK_UNIT_INCH;
+ break;
+ case UNIT_MM:
+ unit = GTK_UNIT_MM;
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ set_scale_unit (EOG_PRINT_IMAGE_SETUP (user_data), unit);
+}
+
+static void
+on_preview_image_moved (EogPrintPreview *preview,
+ gpointer user_data)
+{
+ EogPrintImageSetupPrivate *priv = EOG_PRINT_IMAGE_SETUP (user_data)->priv;
+ gdouble x, y;
+
+ eog_print_preview_get_image_position (preview, &x, &y);
+
+ if (priv->current_unit == GTK_UNIT_MM) {
+ x *= FACTOR_INCH_TO_MM;
+ y *= FACTOR_INCH_TO_MM;
+ }
+
+ gtk_spin_button_set_value (GTK_SPIN_BUTTON (priv->left), x);
+ gtk_spin_button_set_value (GTK_SPIN_BUTTON (priv->top), y);
+}
+
+static gboolean
+on_preview_image_scrolled (GtkWidget *widget,
+ GdkEventScroll *event,
+ gpointer user_data)
+{
+ EogPrintImageSetupPrivate *priv = EOG_PRINT_IMAGE_SETUP (user_data)->priv;
+ EogPrintPreview *preview = EOG_PRINT_PREVIEW (widget);
+ gfloat scale;
+
+ scale = eog_print_preview_get_scale (preview);
+
+ if (!eog_print_preview_point_in_image_area (preview,
+ event->x, event->y))
+ {
+ return FALSE;
+ }
+
+ switch (event->direction) {
+ case GDK_SCROLL_UP:
+ /* scale up */
+ scale *= 1.1;
+ break;
+ case GDK_SCROLL_DOWN:
+ /* scale down */
+ scale *= 0.9;
+ break;
+ default:
+ return FALSE;
+ break;
+ }
+
+ gtk_range_set_value (GTK_RANGE (priv->scaling), 100*scale);
+
+ return TRUE;
+}
+
+static gboolean
+on_preview_image_key_pressed (GtkWidget *widget,
+ GdkEventKey *event,
+ gpointer user_data)
+{
+ EogPrintImageSetupPrivate *priv = EOG_PRINT_IMAGE_SETUP (user_data)->priv;
+ EogPrintPreview *preview = EOG_PRINT_PREVIEW (widget);
+ gfloat scale;
+
+ scale = eog_print_preview_get_scale (preview);
+
+ switch (event->keyval) {
+ case GDK_KEY_KP_Add:
+ case GDK_KEY_plus:
+ /* scale up */
+ scale *= 1.1;
+ break;
+ case GDK_KEY_KP_Subtract:
+ case GDK_KEY_minus:
+ /* scale down */
+ scale *= 0.9;
+ break;
+ default:
+ return FALSE;
+ break;
+ }
+
+ gtk_range_set_value (GTK_RANGE (priv->scaling), 100*scale);
+
+ return TRUE;
+}
+
+/* Function taken from gtkprintunixdialog.c */
+static GtkWidget *
+wrap_in_frame (const gchar *label,
+ GtkWidget *child)
+{
+ GtkWidget *frame, *alignment, *label_widget;
+ gchar *bold_text;
+
+ label_widget = gtk_label_new ("");
+ gtk_misc_set_alignment (GTK_MISC (label_widget), 0.0, 0.5);
+ gtk_widget_show (label_widget);
+
+ bold_text = g_markup_printf_escaped ("<b>%s</b>", label);
+ gtk_label_set_markup (GTK_LABEL (label_widget), bold_text);
+ g_free (bold_text);
+
+ frame = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
+ gtk_box_pack_start (GTK_BOX (frame), label_widget, FALSE, FALSE, 0);
+
+ alignment = gtk_alignment_new (0.0, 0.0, 1.0, 1.0);
+ gtk_alignment_set_padding (GTK_ALIGNMENT (alignment),
+ 0, 0, 12, 0);
+ gtk_box_pack_start (GTK_BOX (frame), alignment, FALSE, FALSE, 0);
+
+ gtk_container_add (GTK_CONTAINER (alignment), child);
+
+ gtk_widget_show (frame);
+ gtk_widget_show (alignment);
+
+ return frame;
+}
+
+static GtkWidget *
+grid_attach_spin_button_with_label (GtkWidget *grid,
+ const gchar* text_label,
+ gint left, gint top)
+{
+ GtkWidget *label, *spin_button;
+
+ label = gtk_label_new_with_mnemonic (text_label);
+ gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+ spin_button = gtk_spin_button_new_with_range (0, 100, 0.01);
+ gtk_spin_button_set_digits (GTK_SPIN_BUTTON (spin_button), 2);
+ gtk_entry_set_width_chars (GTK_ENTRY (spin_button), 6);
+ gtk_grid_attach (GTK_GRID (grid), label, left, top, 1, 1);
+ gtk_grid_attach_next_to (GTK_GRID (grid), spin_button, label,
+ GTK_POS_RIGHT, 1, 1);
+ gtk_label_set_mnemonic_widget (GTK_LABEL (label), spin_button);
+
+ return spin_button;
+}
+
+
+static void
+eog_print_image_setup_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ EogPrintImageSetup *setup = EOG_PRINT_IMAGE_SETUP (object);
+ EogPrintImageSetupPrivate *priv = setup->priv;
+ GdkPixbuf *pixbuf;
+
+ switch (prop_id) {
+ case PROP_IMAGE:
+ if (priv->image) {
+ g_object_unref (priv->image);
+ }
+ priv->image = EOG_IMAGE (g_value_dup_object (value));
+ if (EOG_IS_IMAGE (priv->image)) {
+ pixbuf = eog_image_get_pixbuf (priv->image);
+ g_object_set (priv->preview, "image",
+ pixbuf, NULL);
+ g_object_unref (pixbuf);
+ }
+ break;
+ case PROP_PAGE_SETUP:
+ priv->page_setup = g_value_get_object (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+eog_print_image_setup_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ EogPrintImageSetup *setup = EOG_PRINT_IMAGE_SETUP (object);
+ EogPrintImageSetupPrivate *priv = setup->priv;
+
+ switch (prop_id) {
+ case PROP_IMAGE:
+ g_value_set_object (value, priv->image);
+ break;
+ case PROP_PAGE_SETUP:
+ g_value_set_object (value, priv->page_setup);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+set_initial_values (EogPrintImageSetup *setup)
+{
+ EogPrintImageSetupPrivate *priv;
+ GtkPageSetup *page_setup;
+ EogImage *image;
+ gdouble page_width, page_height;
+ gint pix_width, pix_height;
+ gdouble factor;
+ gdouble width, height;
+ gdouble max_perc;
+
+ priv = setup->priv;
+ page_setup = priv->page_setup;
+ image = priv->image;
+
+ factor = get_scale_to_px_factor (setup);
+
+ eog_image_get_size (image, &pix_width, &pix_height);
+ width = (gdouble)pix_width/factor;
+ height = (gdouble)pix_height/factor;
+
+ max_perc = get_max_percentage (setup);
+
+ width *= max_perc;
+ height *= max_perc;
+
+ gtk_range_set_range (GTK_RANGE (priv->scaling), 1, 100*max_perc);
+ gtk_range_set_increments (GTK_RANGE (priv->scaling), max_perc, 10*max_perc);
+ gtk_range_set_value (GTK_RANGE (priv->scaling), 100*max_perc);
+
+ eog_print_preview_set_scale (EOG_PRINT_PREVIEW (priv->preview), max_perc);
+ gtk_spin_button_set_range (GTK_SPIN_BUTTON (priv->width), 0, width);
+ gtk_spin_button_set_range (GTK_SPIN_BUTTON (priv->height), 0, height);
+ gtk_spin_button_set_value (GTK_SPIN_BUTTON (priv->width), width);
+ gtk_spin_button_set_value (GTK_SPIN_BUTTON (priv->height), height);
+
+ gtk_combo_box_set_active (GTK_COMBO_BOX (priv->center),
+ CENTER_BOTH);
+
+ center (gtk_page_setup_get_page_width (priv->page_setup, priv->current_unit),
+ gtk_spin_button_get_value (GTK_SPIN_BUTTON (priv->width)),
+ GTK_SPIN_BUTTON (priv->left), GTK_SPIN_BUTTON (priv->right));
+ center (gtk_page_setup_get_page_height (priv->page_setup, priv->current_unit),
+ gtk_spin_button_get_value (GTK_SPIN_BUTTON (priv->height)),
+ GTK_SPIN_BUTTON (priv->top), GTK_SPIN_BUTTON (priv->bottom));
+
+ page_width = gtk_page_setup_get_page_width (page_setup, priv->current_unit);
+ page_height = gtk_page_setup_get_page_height (page_setup, priv->current_unit);
+
+ update_image_pos_ranges (setup, page_width, page_height, width, height);
+
+
+}
+
+static void
+connect_signals (EogPrintImageSetup *setup)
+{
+ EogPrintImageSetupPrivate *priv;
+
+ priv = setup->priv;
+
+ g_signal_connect (G_OBJECT (priv->left), "value-changed",
+ G_CALLBACK (on_left_value_changed), setup);
+ g_signal_connect (G_OBJECT (priv->right), "value-changed",
+ G_CALLBACK (on_right_value_changed), setup);
+ g_signal_connect (G_OBJECT (priv->top), "value-changed",
+ G_CALLBACK (on_top_value_changed), setup);
+ g_signal_connect (G_OBJECT (priv->bottom), "value-changed",
+ G_CALLBACK (on_bottom_value_changed), setup);
+ g_signal_connect (G_OBJECT (priv->width), "value-changed",
+ G_CALLBACK (on_width_value_changed), setup);
+ g_signal_connect (G_OBJECT (priv->height), "value-changed",
+ G_CALLBACK (on_height_value_changed), setup);
+ g_signal_connect (G_OBJECT (priv->scaling), "value-changed",
+ G_CALLBACK (on_scale_changed), setup);
+ g_signal_connect (G_OBJECT (priv->scaling), "format-value",
+ G_CALLBACK (on_scale_format_value), NULL);
+ g_signal_connect (G_OBJECT (priv->preview), "image-moved",
+ G_CALLBACK (on_preview_image_moved), setup);
+ g_signal_connect (G_OBJECT (priv->preview), "scroll-event",
+ G_CALLBACK (on_preview_image_scrolled), setup);
+ g_signal_connect (G_OBJECT (priv->preview), "key-press-event",
+ G_CALLBACK (on_preview_image_key_pressed), setup);
+}
+
+static void
+eog_print_image_setup_class_init (EogPrintImageSetupClass *class)
+{
+ GObjectClass *object_class = (GObjectClass *)class;
+
+ object_class->set_property = eog_print_image_setup_set_property;
+ object_class->get_property = eog_print_image_setup_get_property;
+
+ g_object_class_install_property (object_class, PROP_IMAGE,
+ g_param_spec_object ("image",
+ _("Image"),
+ _("The image whose printing properties will be set up"),
+ EOG_TYPE_IMAGE,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_PAGE_SETUP,
+ g_param_spec_object ("page-setup",
+ _("Page Setup"),
+ _("The information for the page where the image will be printed"),
+ GTK_TYPE_PAGE_SETUP,
+ G_PARAM_READWRITE));
+
+ g_type_class_add_private (class, sizeof (EogPrintImageSetupPrivate));
+}
+
+static void
+eog_print_image_setup_init (EogPrintImageSetup *setup)
+{
+ GtkWidget *frame;
+ GtkWidget *grid;
+ GtkWidget *label;
+ GtkWidget *hscale;
+ GtkWidget *combobox;
+ EogPrintImageSetupPrivate *priv;
+
+#ifdef HAVE__NL_MEASUREMENT_MEASUREMENT
+ gchar *locale_scale = NULL;
+#endif
+
+ priv = setup->priv = EOG_PRINT_IMAGE_SETUP_GET_PRIVATE (setup);
+
+ priv->image = NULL;
+
+ grid = gtk_grid_new ();
+ gtk_grid_set_row_spacing (GTK_GRID (grid), 6);
+ gtk_grid_set_column_spacing (GTK_GRID (grid), 12);
+ frame = wrap_in_frame (_("Position"), grid);
+ gtk_grid_attach (GTK_GRID (setup), frame, 0, 0, 1, 1);
+
+ priv->left = grid_attach_spin_button_with_label (grid,
+ _("_Left:"), 0, 0);
+ priv->right = grid_attach_spin_button_with_label (grid,
+ _("_Right:"), 0, 1);
+ priv->top = grid_attach_spin_button_with_label (grid, _("_Top:"), 2, 0);
+ priv->bottom = grid_attach_spin_button_with_label (grid, _("_Bottom:"),
+ 2, 1);
+
+ label = gtk_label_new_with_mnemonic (_("C_enter:"));
+ gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+
+ combobox = gtk_combo_box_text_new ();
+ gtk_combo_box_text_insert_text (GTK_COMBO_BOX_TEXT (combobox),
+ CENTER_NONE, _("None"));
+ gtk_combo_box_text_insert_text (GTK_COMBO_BOX_TEXT (combobox),
+ CENTER_HORIZONTAL, _("Horizontal"));
+ gtk_combo_box_text_insert_text (GTK_COMBO_BOX_TEXT (combobox),
+ CENTER_VERTICAL, _("Vertical"));
+ gtk_combo_box_text_insert_text (GTK_COMBO_BOX_TEXT (combobox),
+ CENTER_BOTH, _("Both"));
+ gtk_combo_box_set_active (GTK_COMBO_BOX (combobox), CENTER_NONE);
+ /* Attach combobox below right margin spinbutton and span until end */
+ gtk_grid_attach_next_to (GTK_GRID (grid), combobox, priv->right,
+ GTK_POS_BOTTOM, 3, 1);
+ /* Attach the label to the left of the combobox */
+ gtk_grid_attach_next_to (GTK_GRID (grid), label, combobox, GTK_POS_LEFT,
+ 1, 1);
+ gtk_label_set_mnemonic_widget (GTK_LABEL (label), combobox);
+ priv->center = combobox;
+ g_signal_connect (G_OBJECT (combobox), "changed",
+ G_CALLBACK (on_center_changed), setup);
+
+ grid = gtk_grid_new ();
+ gtk_grid_set_row_spacing (GTK_GRID (grid), 6);
+ gtk_grid_set_column_spacing (GTK_GRID (grid), 12);
+ frame = wrap_in_frame (_("Size"), grid);
+ gtk_grid_attach (GTK_GRID (setup), frame, 0, 1, 1, 1);
+
+ priv->width = grid_attach_spin_button_with_label (grid, _("_Width:"),
+ 0, 0);
+ priv->height = grid_attach_spin_button_with_label (grid, _("_Height:"),
+ 2, 0);
+
+ label = gtk_label_new_with_mnemonic (_("_Scaling:"));
+ hscale = gtk_scale_new_with_range (GTK_ORIENTATION_HORIZONTAL, 1, 100, 1);
+ gtk_scale_set_value_pos (GTK_SCALE (hscale), GTK_POS_RIGHT);
+ gtk_range_set_value (GTK_RANGE (hscale), 100);
+ gtk_grid_attach_next_to (GTK_GRID (grid), hscale, priv->width,
+ GTK_POS_BOTTOM, 3, 1);
+ gtk_grid_attach_next_to (GTK_GRID (grid), label, hscale, GTK_POS_LEFT,
+ 1, 1);
+ gtk_label_set_mnemonic_widget (GTK_LABEL (label), hscale);
+ priv->scaling = hscale;
+
+ label = gtk_label_new_with_mnemonic (_("_Unit:"));
+ gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+
+ combobox = gtk_combo_box_text_new ();
+ gtk_combo_box_text_insert_text (GTK_COMBO_BOX_TEXT (combobox), UNIT_MM,
+ _("Millimeters"));
+ gtk_combo_box_text_insert_text (GTK_COMBO_BOX_TEXT (combobox),
+ UNIT_INCH, _("Inches"));
+
+#ifdef HAVE__NL_MEASUREMENT_MEASUREMENT
+ locale_scale = nl_langinfo (_NL_MEASUREMENT_MEASUREMENT);
+ if (locale_scale && locale_scale[0] == 2) {
+ gtk_combo_box_set_active (GTK_COMBO_BOX (combobox), UNIT_INCH);
+ set_scale_unit (setup, GTK_UNIT_INCH);
+ } else
+#endif
+ {
+ gtk_combo_box_set_active (GTK_COMBO_BOX (combobox), UNIT_MM);
+ set_scale_unit (setup, GTK_UNIT_MM);
+ }
+
+ gtk_grid_attach_next_to (GTK_GRID (grid), combobox, hscale,
+ GTK_POS_BOTTOM, 3, 1);
+ gtk_grid_attach_next_to (GTK_GRID (grid), label, combobox, GTK_POS_LEFT,
+ 1, 1);
+
+ gtk_label_set_mnemonic_widget (GTK_LABEL (label), combobox);
+ priv->unit = combobox;
+ g_signal_connect (G_OBJECT (combobox), "changed",
+ G_CALLBACK (on_unit_changed), setup);
+
+ priv->preview = eog_print_preview_new ();
+
+ /* FIXME: This shouldn't be set by hand */
+ gtk_widget_set_size_request (priv->preview, 250, 250);
+
+ frame = wrap_in_frame (_("Preview"), priv->preview);
+ /* The preview widget needs to span the whole grid height */
+ gtk_grid_attach (GTK_GRID (setup), frame, 1, 0, 1, 2);
+
+ gtk_widget_show_all (GTK_WIDGET (setup));
+}
+
+
+/**
+ * eog_print_image_setup_new:
+ * @image: the #EogImage to print
+ * @page_setup: a #GtkPageSetup specifying the page where
+ * the image will be print
+ *
+ * Creates a new #EogPrintImageSetup widget, to be used as a custom
+ * widget in a #GtkPrintUnixDialog. This widgets allows to set
+ * the image position and scale in a page.
+ *
+ * Returns: a new #EogPrintImageSetup
+ **/
+GtkWidget *
+eog_print_image_setup_new (EogImage *image, GtkPageSetup *page_setup)
+{
+ GtkWidget *setup;
+ GtkWidget *preview;
+
+ setup = g_object_new (EOG_TYPE_PRINT_IMAGE_SETUP,
+ "orientation", GTK_ORIENTATION_VERTICAL,
+ "row-spacing", 18,
+ "column-spacing", 18,
+ "border-width", 12,
+ "image", image,
+ "page-setup", page_setup,
+ NULL);
+
+ set_initial_values (EOG_PRINT_IMAGE_SETUP (setup));
+
+ preview = EOG_PRINT_IMAGE_SETUP (setup)->priv->preview;
+ eog_print_preview_set_from_page_setup (EOG_PRINT_PREVIEW (preview),
+ page_setup);
+
+ connect_signals (EOG_PRINT_IMAGE_SETUP (setup));
+
+ return setup;
+}
+
+/**
+ * eog_print_image_setup_get_options:
+ * @setup: a #EogPrintImageSetup
+ * @left: a pointer where to store the image's left position
+ * @top: a pointer where to store the image's top position
+ * @scale: a pointer where to store the image's scale
+ * @unit: a pointer where to store the #GtkUnit used by the @left and @top values.
+ *
+ * Gets the options set by the #EogPrintImageSetup.
+ **/
+void
+eog_print_image_setup_get_options (EogPrintImageSetup *setup,
+ gdouble *left,
+ gdouble *top,
+ gdouble *scale,
+ GtkUnit *unit)
+{
+ EogPrintImageSetupPrivate *priv;
+
+ g_return_if_fail (EOG_IS_PRINT_IMAGE_SETUP (setup));
+
+ priv = setup->priv;
+
+ *left = gtk_spin_button_get_value (GTK_SPIN_BUTTON (priv->left));
+ *top = gtk_spin_button_get_value (GTK_SPIN_BUTTON (priv->top));
+ *scale = gtk_range_get_value (GTK_RANGE (priv->scaling));
+ *unit = priv->current_unit;
+}
+
+void
+eog_print_image_setup_update (GtkPrintOperation *operation,
+ GtkWidget *custom_widget,
+ GtkPageSetup *page_setup,
+ GtkPrintSettings *print_settings,
+ gpointer user_data)
+{
+ GtkWidget *preview;
+ gdouble pos_x;
+ gdouble pos_y;
+ EogPrintImageSetup *setup;
+
+ setup = EOG_PRINT_IMAGE_SETUP (custom_widget);
+
+ setup->priv->page_setup = gtk_page_setup_copy (page_setup);
+
+ set_initial_values (EOG_PRINT_IMAGE_SETUP (setup));
+
+ preview = EOG_PRINT_IMAGE_SETUP (setup)->priv->preview;
+ eog_print_preview_set_from_page_setup (EOG_PRINT_PREVIEW (preview),
+ setup->priv->page_setup);
+
+ pos_x = gtk_spin_button_get_value (GTK_SPIN_BUTTON (setup->priv->left));
+ pos_y = gtk_spin_button_get_value (GTK_SPIN_BUTTON (setup->priv->top));
+ if (setup->priv->current_unit == GTK_UNIT_MM) {
+ pos_x *= FACTOR_MM_TO_INCH;
+ pos_y *= FACTOR_MM_TO_INCH;
+ }
+ eog_print_preview_set_image_position (EOG_PRINT_PREVIEW (setup->priv->preview), pos_x, pos_y);
+}
diff --git a/src/eog-print-image-setup.h b/src/eog-print-image-setup.h
new file mode 100644
index 0000000..bf76171
--- /dev/null
+++ b/src/eog-print-image-setup.h
@@ -0,0 +1,72 @@
+/* Eye Of GNOME -- Print Dialog Custom Widget
+ *
+ * Copyright (C) 2006-2007 The Free Software Foundation
+ *
+ * Author: Claudio Saavedra <csaavedra alumnos utalca cl>
+ *
+ * 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 Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "eog-image.h"
+
+#ifndef EOG_PRINT_IMAGE_SETUP_H
+#define EOG_PRINT_IMAGE_SETUP_H
+
+G_BEGIN_DECLS
+
+typedef struct _EogPrintImageSetup EogPrintImageSetup;
+typedef struct _EogPrintImageSetupClass EogPrintImageSetupClass;
+typedef struct _EogPrintImageSetupPrivate EogPrintImageSetupPrivate;
+
+#define EOG_TYPE_PRINT_IMAGE_SETUP (eog_print_image_setup_get_type ())
+#define EOG_PRINT_IMAGE_SETUP(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EOG_TYPE_PRINT_IMAGE_SETUP, EogPrintImageSetup))
+#define EOG_PRINT_IMAGE_SETUP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EOG_TYPE_PRINT_IMAGE_SETUP, EogPrintImageSetupClass))
+#define EOG_IS_PRINT_IMAGE_SETUP(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EOG_TYPE_PRINT_IMAGE_SETUP))
+#define EOG_IS_PRINT_IMAGE_SETUP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EOG_TYPE_PRINT_IMAGE_SETUP))
+#define EOG_PRINT_IMAGE_SETUP_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EOG_TYPE_PRINT_IMAGE_SETUP, EogPrintImageSetupClass))
+
+struct _EogPrintImageSetup {
+ GtkGrid parent_instance;
+
+ EogPrintImageSetupPrivate *priv;
+};
+
+struct _EogPrintImageSetupClass {
+ GtkGridClass parent_class;
+};
+
+G_GNUC_INTERNAL
+GType eog_print_image_setup_get_type (void) G_GNUC_CONST;
+
+G_GNUC_INTERNAL
+GtkWidget *eog_print_image_setup_new (EogImage *image,
+ GtkPageSetup *page_setup);
+
+G_GNUC_INTERNAL
+void eog_print_image_setup_get_options (EogPrintImageSetup *setup,
+ gdouble *left,
+ gdouble *top,
+ gdouble *scale,
+ GtkUnit *unit);
+G_GNUC_INTERNAL
+void eog_print_image_setup_update (GtkPrintOperation *operation,
+ GtkWidget *custom_widget,
+ GtkPageSetup *page_setup,
+ GtkPrintSettings *print_settings,
+ gpointer user_data);
+
+G_END_DECLS
+
+#endif /* EOG_PRINT_IMAGE_SETUP_H */
diff --git a/src/eog-print.c b/src/eog-print.c
new file mode 100644
index 0000000..6a68c5b
--- /dev/null
+++ b/src/eog-print.c
@@ -0,0 +1,521 @@
+/* Eye of Gnome - Print Operations
+ *
+ * Copyright (C) 2005-2008 The Free Software Foundation
+ *
+ * Author: Claudio Saavedra <csaavedra gnome org>
+ *
+ * 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 Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "config.h"
+
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+#include "eog-image.h"
+#include "eog-print.h"
+#include "eog-print-image-setup.h"
+#include "eog-util.h"
+#include "eog-debug.h"
+
+#ifdef HAVE_RSVG
+#include <librsvg/rsvg-cairo.h>
+#endif
+
+#define EOG_PRINT_SETTINGS_FILE "eog-print-settings.ini"
+#define EOG_PAGE_SETUP_GROUP "Page Setup"
+#define EOG_PRINT_SETTINGS_GROUP "Print Settings"
+
+typedef struct {
+ EogImage *image;
+ gdouble left_margin;
+ gdouble top_margin;
+ gdouble scale_factor;
+ GtkUnit unit;
+} EogPrintData;
+
+/* art_affine_flip modified to work with cairo_matrix_t */
+static void
+_eog_cairo_matrix_flip (cairo_matrix_t *dst, const cairo_matrix_t *src, gboolean horiz, gboolean vert)
+{
+ dst->xx = horiz ? -src->xx : src->xx;
+ dst->yx = horiz ? -src->yx : src->yx;
+ dst->xy = vert ? -src->xy : src->xy;
+ dst->yy = vert ? -src->yy : src->yy;
+ dst->x0 = horiz ? -src->x0 : src->x0;
+ dst->y0 = vert ? -src->y0 : src->y0;
+}
+
+static gboolean
+_cairo_ctx_supports_jpg_metadata (cairo_t *cr)
+{
+ cairo_surface_t *surface = cairo_get_target (cr);
+ cairo_surface_type_t type = cairo_surface_get_type (surface);
+
+ /* Based on cairo-1.10 */
+ return (type == CAIRO_SURFACE_TYPE_PDF || type == CAIRO_SURFACE_TYPE_PS ||
+ type == CAIRO_SURFACE_TYPE_SVG || type == CAIRO_SURFACE_TYPE_WIN32_PRINTING);
+}
+
+static void
+eog_print_draw_page (GtkPrintOperation *operation,
+ GtkPrintContext *context,
+ gint page_nr,
+ gpointer user_data)
+{
+ cairo_t *cr;
+ gdouble dpi_x, dpi_y;
+ gdouble x0, y0;
+ gdouble scale_factor;
+ gdouble p_width, p_height;
+ gint width, height;
+ EogPrintData *data;
+ GtkPageSetup *page_setup;
+
+ eog_debug (DEBUG_PRINTING);
+
+ data = (EogPrintData *) user_data;
+
+ scale_factor = data->scale_factor/100;
+
+ dpi_x = gtk_print_context_get_dpi_x (context);
+ dpi_y = gtk_print_context_get_dpi_y (context);
+
+ switch (data->unit) {
+ case GTK_UNIT_INCH:
+ x0 = data->left_margin * dpi_x;
+ y0 = data->top_margin * dpi_y;
+ break;
+ case GTK_UNIT_MM:
+ x0 = data->left_margin * dpi_x/25.4;
+ y0 = data->top_margin * dpi_y/25.4;
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ cr = gtk_print_context_get_cairo_context (context);
+
+ cairo_translate (cr, x0, y0);
+
+ page_setup = gtk_print_context_get_page_setup (context);
+ p_width = gtk_page_setup_get_page_width (page_setup, GTK_UNIT_POINTS);
+ p_height = gtk_page_setup_get_page_height (page_setup, GTK_UNIT_POINTS);
+
+ eog_image_get_size (data->image, &width, &height);
+
+ /* this is both a workaround for a bug in cairo's PDF backend, and
+ a way to ensure we are not printing outside the page margins */
+ cairo_rectangle (cr, 0, 0, MIN (width*scale_factor, p_width), MIN (height*scale_factor, p_height));
+ cairo_clip (cr);
+
+ cairo_scale (cr, scale_factor, scale_factor);
+
+#ifdef HAVE_RSVG
+ if (eog_image_is_svg (data->image))
+ {
+ RsvgHandle *svg = eog_image_get_svg (data->image);
+
+ rsvg_handle_render_cairo (svg, cr);
+ return;
+ } else
+#endif
+ /* JPEGs can be attached to the cairo surface which simply embeds the JPEG file into the
+ * destination PDF skipping (PNG-)recompression. This should reduce PDF sizes enormously. */
+ if (eog_image_is_jpeg (data->image) && _cairo_ctx_supports_jpg_metadata (cr))
+ {
+ GFile *file;
+ char *img_data;
+ gsize data_len;
+ cairo_surface_t *surface = NULL;
+
+ eog_debug_message (DEBUG_PRINTING, "Attaching image to cairo surface");
+
+ file = eog_image_get_file (data->image);
+ if (g_file_load_contents (file, NULL, &img_data, &data_len, NULL, NULL))
+ {
+ EogTransform *tf = eog_image_get_transform (data->image);
+ EogTransform *auto_tf = eog_image_get_autorotate_transform (data->image);
+ cairo_matrix_t mx, mx2;
+
+ if (!tf && auto_tf) {
+ /* If only autorotation data present,
+ * make it the normal rotation. */
+ tf = auto_tf;
+ auto_tf = NULL;
+ }
+
+ /* Care must be taken with height and width values. They are not the original
+ * values but were affected by the transformation. As the surface needs to be
+ * generated using the original dimensions they might need to be flipped. */
+ if (tf) {
+ if (auto_tf) {
+ /* If we have an autorotation apply
+ * it before the others */
+ tf = eog_transform_compose (auto_tf, tf);
+ }
+
+ switch (eog_transform_get_transform_type (tf)) {
+ case EOG_TRANSFORM_ROT_90:
+ surface = cairo_image_surface_create (
+ CAIRO_FORMAT_RGB24, height, width);
+ cairo_rotate (cr, 90.0 * (G_PI/180.0));
+ cairo_translate (cr, 0.0, -width);
+ break;
+ case EOG_TRANSFORM_ROT_180:
+ surface = cairo_image_surface_create (
+ CAIRO_FORMAT_RGB24, width, height);
+ cairo_rotate (cr, 180.0 * (G_PI/180.0));
+ cairo_translate (cr, -width, -height);
+ break;
+ case EOG_TRANSFORM_ROT_270:
+ surface = cairo_image_surface_create (
+ CAIRO_FORMAT_RGB24, height, width);
+ cairo_rotate (cr, 270.0 * (G_PI/180.0));
+ cairo_translate (cr, -height, 0.0);
+ break;
+ case EOG_TRANSFORM_FLIP_HORIZONTAL:
+ surface = cairo_image_surface_create (
+ CAIRO_FORMAT_RGB24, width, height);
+ cairo_matrix_init_identity (&mx);
+ _eog_cairo_matrix_flip (&mx2, &mx, TRUE, FALSE);
+ cairo_transform (cr, &mx2);
+ cairo_translate (cr, -width, 0.0);
+ break;
+ case EOG_TRANSFORM_FLIP_VERTICAL:
+ surface = cairo_image_surface_create (
+ CAIRO_FORMAT_RGB24, width, height);
+ cairo_matrix_init_identity (&mx);
+ _eog_cairo_matrix_flip (&mx2, &mx, FALSE, TRUE);
+ cairo_transform (cr, &mx2);
+ cairo_translate (cr, 0.0, -height);
+ break;
+ case EOG_TRANSFORM_TRANSPOSE:
+ surface = cairo_image_surface_create (
+ CAIRO_FORMAT_RGB24, height, width);
+ cairo_matrix_init_rotate (&mx, 90.0 * (G_PI/180.0));
+ cairo_matrix_init_identity (&mx2);
+ _eog_cairo_matrix_flip (&mx2, &mx2, TRUE, FALSE);
+ cairo_matrix_multiply (&mx2, &mx, &mx2);
+ cairo_transform (cr, &mx2);
+ break;
+ case EOG_TRANSFORM_TRANSVERSE:
+ surface = cairo_image_surface_create (
+ CAIRO_FORMAT_RGB24, height, width);
+ cairo_matrix_init_rotate (&mx, 90.0 * (G_PI/180.0));
+ cairo_matrix_init_identity (&mx2);
+ _eog_cairo_matrix_flip (&mx2, &mx2, FALSE, TRUE);
+ cairo_matrix_multiply (&mx2, &mx, &mx2);
+ cairo_transform (cr, &mx2);
+ cairo_translate (cr, -height , -width);
+ break;
+ case EOG_TRANSFORM_NONE:
+ default:
+ surface = cairo_image_surface_create (
+ CAIRO_FORMAT_RGB24, width, height);
+ break;
+ }
+ }
+
+ if (!surface)
+ surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24,
+ width, height);
+ cairo_surface_set_mime_data (surface,
+ CAIRO_MIME_TYPE_JPEG,
+ (unsigned char*)img_data, data_len,
+ g_free, img_data);
+ cairo_set_source_surface (cr, surface, 0, 0);
+ cairo_paint (cr);
+ cairo_surface_destroy (surface);
+ g_object_unref (file);
+ return;
+ }
+ g_object_unref (file);
+
+ }
+
+ {
+ GdkPixbuf *pixbuf;
+
+ pixbuf = eog_image_get_pixbuf (data->image);
+ gdk_cairo_set_source_pixbuf (cr, pixbuf, 0, 0);
+ cairo_paint (cr);
+ g_object_unref (pixbuf);
+ }
+}
+
+static GObject *
+eog_print_create_custom_widget (GtkPrintOperation *operation,
+ gpointer user_data)
+{
+ GtkPageSetup *page_setup;
+ EogPrintData *data;
+
+ eog_debug (DEBUG_PRINTING);
+
+ data = (EogPrintData *)user_data;
+
+ page_setup = gtk_print_operation_get_default_page_setup (operation);
+
+ if (page_setup == NULL)
+ page_setup = gtk_page_setup_new ();
+
+ return G_OBJECT (eog_print_image_setup_new (data->image, page_setup));
+}
+
+static void
+eog_print_custom_widget_apply (GtkPrintOperation *operation,
+ GtkWidget *widget,
+ gpointer user_data)
+{
+ EogPrintData *data;
+ gdouble left_margin, top_margin, scale_factor;
+ GtkUnit unit;
+
+ eog_debug (DEBUG_PRINTING);
+
+ data = (EogPrintData *)user_data;
+
+ eog_print_image_setup_get_options (EOG_PRINT_IMAGE_SETUP (widget),
+ &left_margin, &top_margin,
+ &scale_factor, &unit);
+
+ data->left_margin = left_margin;
+ data->top_margin = top_margin;
+ data->scale_factor = scale_factor;
+ data->unit = unit;
+}
+
+static void
+eog_print_end_print (GtkPrintOperation *operation,
+ GtkPrintContext *context,
+ gpointer user_data)
+{
+ EogPrintData *data = (EogPrintData*) user_data;
+
+ eog_debug (DEBUG_PRINTING);
+
+ g_object_unref (data->image);
+ g_slice_free (EogPrintData, data);
+}
+
+GtkPrintOperation *
+eog_print_operation_new (EogImage *image,
+ GtkPrintSettings *print_settings,
+ GtkPageSetup *page_setup)
+{
+ GtkPrintOperation *print;
+ EogPrintData *data;
+ gint width, height;
+
+ eog_debug (DEBUG_PRINTING);
+
+ print = gtk_print_operation_new ();
+
+ data = g_slice_new0 (EogPrintData);
+
+ data->left_margin = 0;
+ data->top_margin = 0;
+ data->scale_factor = 100;
+ data->image = g_object_ref (image);
+ data->unit = GTK_UNIT_INCH;
+
+ eog_image_get_size (image, &width, &height);
+
+ if (page_setup == NULL)
+ page_setup = gtk_page_setup_new ();
+
+ if (height >= width) {
+ gtk_page_setup_set_orientation (page_setup,
+ GTK_PAGE_ORIENTATION_PORTRAIT);
+ } else {
+ gtk_page_setup_set_orientation (page_setup,
+ GTK_PAGE_ORIENTATION_LANDSCAPE);
+ }
+
+ gtk_print_operation_set_print_settings (print, print_settings);
+ gtk_print_operation_set_default_page_setup (print,
+ page_setup);
+ gtk_print_operation_set_n_pages (print, 1);
+ gtk_print_operation_set_job_name (print,
+ eog_image_get_caption (image));
+ gtk_print_operation_set_embed_page_setup (print, TRUE);
+
+ g_signal_connect (print, "draw_page",
+ G_CALLBACK (eog_print_draw_page),
+ data);
+ g_signal_connect (print, "create-custom-widget",
+ G_CALLBACK (eog_print_create_custom_widget),
+ data);
+ g_signal_connect (print, "custom-widget-apply",
+ G_CALLBACK (eog_print_custom_widget_apply),
+ data);
+ g_signal_connect (print, "end-print",
+ G_CALLBACK (eog_print_end_print),
+ data);
+ g_signal_connect (print, "update-custom-widget",
+ G_CALLBACK (eog_print_image_setup_update),
+ data);
+
+ gtk_print_operation_set_custom_tab_label (print, _("Image Settings"));
+
+ return print;
+}
+
+static GKeyFile *
+eog_print_get_key_file (void)
+{
+ GKeyFile *key_file;
+ GError *error = NULL;
+ gchar *filename;
+ GFile *file;
+ const gchar *dot_dir = eog_util_dot_dir ();
+
+ filename = g_build_filename (dot_dir, EOG_PRINT_SETTINGS_FILE, NULL);
+
+ file = g_file_new_for_path (filename);
+ key_file = g_key_file_new ();
+
+ if (g_file_query_exists (file, NULL)) {
+ g_key_file_load_from_file (key_file, filename,
+ G_KEY_FILE_KEEP_COMMENTS | G_KEY_FILE_KEEP_TRANSLATIONS,
+ &error);
+ if (error) {
+ g_warning ("Error loading print settings file: %s", error->message);
+ g_error_free (error);
+ g_object_unref (file);
+ g_free (filename);
+ g_key_file_free (key_file);
+ return NULL;
+ }
+ }
+
+ g_object_unref (file);
+ g_free (filename);
+
+ return key_file;
+}
+
+GtkPageSetup *
+eog_print_get_page_setup (void)
+{
+ GtkPageSetup *page_setup;
+ GKeyFile *key_file;
+ GError *error = NULL;
+
+ key_file = eog_print_get_key_file ();
+
+ if (key_file && g_key_file_has_group (key_file, EOG_PAGE_SETUP_GROUP)) {
+ page_setup = gtk_page_setup_new_from_key_file (key_file, EOG_PAGE_SETUP_GROUP, &error);
+ } else {
+ page_setup = gtk_page_setup_new ();
+ }
+
+ if (error) {
+ page_setup = gtk_page_setup_new ();
+
+ g_warning ("Error loading print settings file: %s", error->message);
+ g_error_free (error);
+ }
+
+ if (key_file)
+ g_key_file_free (key_file);
+
+ return page_setup;
+}
+
+static void
+eog_print_save_key_file (GKeyFile *key_file)
+{
+ gchar *filename;
+ gchar *data;
+ GError *error = NULL;
+ const gchar *dot_dir = eog_util_dot_dir ();
+
+ filename = g_build_filename (dot_dir, EOG_PRINT_SETTINGS_FILE, NULL);
+
+ data = g_key_file_to_data (key_file, NULL, NULL);
+
+ g_file_set_contents (filename, data, -1, &error);
+
+ if (error) {
+ g_warning ("Error saving print settings file: %s", error->message);
+ g_error_free (error);
+ }
+
+ g_free (filename);
+ g_free (data);
+}
+
+void
+eog_print_set_page_setup (GtkPageSetup *page_setup)
+{
+ GKeyFile *key_file;
+
+ key_file = eog_print_get_key_file ();
+
+ if (key_file == NULL) {
+ key_file = g_key_file_new ();
+ }
+
+ gtk_page_setup_to_key_file (page_setup, key_file, EOG_PAGE_SETUP_GROUP);
+ eog_print_save_key_file (key_file);
+
+ g_key_file_free (key_file);
+}
+
+GtkPrintSettings *
+eog_print_get_print_settings (void)
+{
+ GtkPrintSettings *print_settings;
+ GError *error = NULL;
+ GKeyFile *key_file;
+
+ key_file = eog_print_get_key_file ();
+
+ if (key_file && g_key_file_has_group (key_file, EOG_PRINT_SETTINGS_GROUP)) {
+ print_settings = gtk_print_settings_new_from_key_file (key_file, EOG_PRINT_SETTINGS_GROUP, &error);
+ } else {
+ print_settings = gtk_print_settings_new ();
+ }
+
+ if (error) {
+ print_settings = gtk_print_settings_new ();
+
+ g_warning ("Error loading print settings file: %s", error->message);
+ g_error_free (error);
+ }
+
+ if (key_file)
+ g_key_file_free (key_file);
+
+ return print_settings;
+}
+
+void
+eog_print_set_print_settings (GtkPrintSettings *print_settings)
+{
+ GKeyFile *key_file;
+
+ key_file = eog_print_get_key_file ();
+
+ if (key_file == NULL) {
+ key_file = g_key_file_new ();
+ }
+
+ gtk_print_settings_to_key_file (print_settings, key_file, EOG_PRINT_SETTINGS_GROUP);
+ eog_print_save_key_file (key_file);
+
+ g_key_file_free (key_file);
+}
diff --git a/src/eog-print.h b/src/eog-print.h
new file mode 100644
index 0000000..a0fedbb
--- /dev/null
+++ b/src/eog-print.h
@@ -0,0 +1,49 @@
+/* Eye of Gnome - Print Operations
+ *
+ * Copyright (C) 2005-2008 The Free Software Foundation
+ *
+ * Author: Claudio Saavedra <csaavedra gnome org>
+ *
+ * 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 Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __EOG_PRINT_H__
+#define __EOG_PRINT_H__
+
+#include "eog-image.h"
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+G_GNUC_INTERNAL
+GtkPrintOperation* eog_print_operation_new (EogImage *image,
+ GtkPrintSettings *print_settings,
+ GtkPageSetup *page_setup);
+
+G_GNUC_INTERNAL
+GtkPageSetup* eog_print_get_page_setup (void);
+
+G_GNUC_INTERNAL
+void eog_print_set_page_setup (GtkPageSetup *page_setup);
+
+G_GNUC_INTERNAL
+GtkPrintSettings * eog_print_get_print_settings (void);
+
+G_GNUC_INTERNAL
+void eog_print_set_print_settings (GtkPrintSettings *print_settings);
+
+G_END_DECLS
+
+#endif /* __EOG_PRINT_H__ */
diff --git a/src/eog-transform.c b/src/eog-transform.c
new file mode 100644
index 0000000..bcd0d75
--- /dev/null
+++ b/src/eog-transform.c
@@ -0,0 +1,449 @@
+/* Eye Of GNOME -- Affine Transformations
+ *
+ * Copyright (C) 2003-2009 The Free Software Foundation
+ *
+ * Portions based on code from libart_lgpl by Raph Levien.
+ *
+ * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <time.h>
+#include <stdlib.h>
+#include <math.h>
+#include <gtk/gtk.h>
+#include <cairo/cairo.h>
+
+#include "eog-transform.h"
+#include "eog-jobs.h"
+
+/* The number of progress updates per transformation */
+#define EOG_TRANSFORM_N_PROG_UPDATES 20
+
+struct _EogTransformPrivate {
+ cairo_matrix_t affine;
+};
+
+typedef struct {
+ gdouble x;
+ gdouble y;
+} EogPoint;
+
+/* Convert degrees into radians */
+#define EOG_DEG_TO_RAD(degree) ((degree) * (G_PI/180.0))
+
+#define EOG_TRANSFORM_GET_PRIVATE(object) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((object), EOG_TYPE_TRANSFORM, EogTransformPrivate))
+
+G_DEFINE_TYPE (EogTransform, eog_transform, G_TYPE_OBJECT)
+
+static void
+eog_transform_init (EogTransform *trans)
+{
+ trans->priv = EOG_TRANSFORM_GET_PRIVATE (trans);
+}
+
+static void
+eog_transform_class_init (EogTransformClass *klass)
+{
+ g_type_class_add_private (klass, sizeof (EogTransformPrivate));
+}
+
+/**
+ * eog_transform_apply:
+ * @trans: a #EogTransform
+ * @pixbuf: a #GdkPixbuf
+ * @job: a #EogJob
+ *
+ * Applies the transformation in @trans to @pixbuf, setting its progress in @job.
+ *
+ * Returns: (transfer full): A new #GdkPixbuf with the transformation applied.
+ **/
+GdkPixbuf*
+eog_transform_apply (EogTransform *trans, GdkPixbuf *pixbuf, EogJob *job)
+{
+ EogPoint dest_top_left;
+ EogPoint dest_bottom_right;
+ EogPoint vertices[4] = { {0, 0}, {1, 0}, {1, 1}, {0, 1} };
+ double r_det;
+ int inverted [6];
+ EogPoint dest;
+
+ int src_width;
+ int src_height;
+ int src_rowstride;
+ int src_n_channels;
+ guchar *src_buffer;
+
+ GdkPixbuf *dest_pixbuf;
+ int dest_width;
+ int dest_height;
+ int dest_rowstride;
+ int dest_n_channels;
+ guchar *dest_buffer;
+
+ guchar *src_pos;
+ guchar *dest_pos;
+ int dx, dy, sx, sy;
+ int i, x, y;
+
+ int progress_delta;
+
+ g_return_val_if_fail (pixbuf != NULL, NULL);
+
+ g_object_ref (pixbuf);
+
+ src_width = gdk_pixbuf_get_width (pixbuf);
+ src_height = gdk_pixbuf_get_height (pixbuf);
+ src_rowstride = gdk_pixbuf_get_rowstride (pixbuf);
+ src_n_channels = gdk_pixbuf_get_n_channels (pixbuf);
+ src_buffer = gdk_pixbuf_get_pixels (pixbuf);
+
+ /* find out the dimension of the destination pixbuf */
+ dest_top_left.x = 100000;
+ dest_top_left.y = 100000;
+ dest_bottom_right.x = -100000;
+ dest_bottom_right.y = -100000;
+
+ for (i = 0; i < 4; i++) {
+ dest.x = vertices[i].x * (src_width - 1);
+ dest.y = vertices[i].y * (src_height -1);
+
+ cairo_matrix_transform_point (&trans->priv->affine,
+ &dest.x, &dest.y);
+
+ dest_top_left.x = MIN (dest_top_left.x, dest.x);
+ dest_top_left.y = MIN (dest_top_left.y, dest.y);
+
+ dest_bottom_right.x = MAX (dest_bottom_right.x, dest.x);
+ dest_bottom_right.y = MAX (dest_bottom_right.y, dest.y);
+ }
+
+ /* create the resulting pixbuf */
+ dest_width = abs (dest_bottom_right.x - dest_top_left.x + 1);
+ dest_height = abs (dest_bottom_right.y - dest_top_left.y + 1);
+
+ dest_pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
+ gdk_pixbuf_get_has_alpha (pixbuf),
+ gdk_pixbuf_get_bits_per_sample (pixbuf),
+ dest_width,
+ dest_height);
+ dest_rowstride = gdk_pixbuf_get_rowstride (dest_pixbuf);
+ dest_n_channels = gdk_pixbuf_get_n_channels (dest_pixbuf);
+ dest_buffer = gdk_pixbuf_get_pixels (dest_pixbuf);
+
+ /* invert the matrix so that we can compute the source pixel
+ from the target pixel and convert the values to integer
+ ones (faster!) FIXME: Maybe we can do some more
+ improvements by using special mmx/3dnow features if
+ available.
+ */
+ r_det = 1.0 / (trans->priv->affine.xx * trans->priv->affine.yy - trans->priv->affine.yx * trans->priv->affine.xy);
+ inverted[0] = trans->priv->affine.yy * r_det;
+ inverted[1] = -trans->priv->affine.yx * r_det;
+ inverted[2] = -trans->priv->affine.xy * r_det;
+ inverted[3] = trans->priv->affine.xx * r_det;
+ inverted[4] = -trans->priv->affine.x0 * inverted[0] - trans->priv->affine.y0 * inverted[2];
+ inverted[5] = -trans->priv->affine.x0 * inverted[1] - trans->priv->affine.y0 * inverted[3];
+
+ progress_delta = MAX (1, dest_height / EOG_TRANSFORM_N_PROG_UPDATES);
+
+ /*
+ * for every destination pixel (dx,dy) compute the source pixel (sx, sy)
+ * and copy the color values
+ */
+ for (y = 0, dy = dest_top_left.y; y < dest_height; y++, dy++) {
+ for (x = 0, dx = dest_top_left.x; x < dest_width; x++, dx++) {
+
+ sx = dx * inverted[0] + dy * inverted[2] + inverted[4];
+ sy = dx * inverted[1] + dy * inverted[3] + inverted[5];
+
+ if (sx >= 0 && sx < src_width && sy >= 0 && sy < src_height) {
+ src_pos = src_buffer + sy * src_rowstride + sx * src_n_channels;
+ dest_pos = dest_buffer + y * dest_rowstride + x * dest_n_channels;
+
+ for (i = 0; i < src_n_channels; i++) {
+ dest_pos[i] = src_pos[i];
+ }
+ }
+ }
+
+ if (job != NULL && y % progress_delta == 0) {
+ gfloat progress;
+
+ progress = (gfloat) (y + 1.0) / (gfloat) dest_height;
+
+ eog_job_set_progress (job, progress);
+ }
+ }
+
+ g_object_unref (pixbuf);
+
+ if (job != NULL) {
+ eog_job_set_progress (job, 1.0);
+ }
+
+ return dest_pixbuf;
+}
+
+static void
+_eog_cairo_matrix_copy (const cairo_matrix_t *src, cairo_matrix_t *dest)
+{
+ cairo_matrix_init (dest, src->xx, src->yx, src->xy, src->yy, src->x0, src->y0);
+}
+
+#define DOUBLE_EQUAL_MAX_DIFF 1e-6
+#define DOUBLE_EQUAL(a,b) (fabs (a - b) < DOUBLE_EQUAL_MAX_DIFF)
+/* art_affine_equal modified to work with cairo_matrix_t */
+static gboolean
+_eog_cairo_matrix_equal (const cairo_matrix_t *a, const cairo_matrix_t *b)
+{
+ return (DOUBLE_EQUAL (a->xx, b->xx) && DOUBLE_EQUAL (a->yx, b->yx) &&
+ DOUBLE_EQUAL (a->xy, b->xy) && DOUBLE_EQUAL (a->yy, b->yy) &&
+ DOUBLE_EQUAL (a->x0, b->x0) && DOUBLE_EQUAL (a->y0, b->y0) );
+}
+
+/* art_affine_flip modified to work with cairo_matrix_t */
+static void
+_eog_cairo_matrix_flip (cairo_matrix_t *dst, const cairo_matrix_t *src, gboolean horiz, gboolean vert)
+{
+ dst->xx = horiz ? -src->xx : src->xx;
+ dst->yx = horiz ? -src->yx : src->yx;
+ dst->xy = vert ? -src->xy : src->xy;
+ dst->yy = vert ? -src->yy : src->yy;
+ dst->x0 = horiz ? -src->x0 : src->x0;
+ dst->y0 = vert ? -src->y0 : src->y0;
+}
+
+/**
+ * eog_transform_reverse:
+ * @trans: a #EogTransform
+ *
+ * Creates the reverse transformation of @trans
+ *
+ * Returns: (transfer full): a new transformation
+ **/
+EogTransform*
+eog_transform_reverse (EogTransform *trans)
+{
+ EogTransform *reverse;
+
+ g_return_val_if_fail (EOG_IS_TRANSFORM (trans), NULL);
+
+ reverse = EOG_TRANSFORM (g_object_new (EOG_TYPE_TRANSFORM, NULL));
+
+ _eog_cairo_matrix_copy (&trans->priv->affine, &reverse->priv->affine);
+
+ g_return_val_if_fail (cairo_matrix_invert (&reverse->priv->affine) == CAIRO_STATUS_SUCCESS, reverse);
+
+ return reverse;
+}
+
+/**
+ * eog_transform_compose:
+ * @trans: a #EogTransform
+ * @compose: another #EogTransform
+ *
+ *
+ *
+ * Returns: (transfer full): a new transform
+ **/
+EogTransform*
+eog_transform_compose (EogTransform *trans, EogTransform *compose)
+{
+ EogTransform *composition;
+
+ g_return_val_if_fail (EOG_IS_TRANSFORM (trans), NULL);
+ g_return_val_if_fail (EOG_IS_TRANSFORM (compose), NULL);
+
+ composition = EOG_TRANSFORM (g_object_new (EOG_TYPE_TRANSFORM, NULL));
+
+ cairo_matrix_multiply (&composition->priv->affine,
+ &trans->priv->affine,
+ &compose->priv->affine);
+
+ return composition;
+}
+
+gboolean
+eog_transform_is_identity (EogTransform *trans)
+{
+ static const cairo_matrix_t identity = { 1, 0, 0, 1, 0, 0 };
+
+ g_return_val_if_fail (EOG_IS_TRANSFORM (trans), FALSE);
+
+ return _eog_cairo_matrix_equal (&identity, &trans->priv->affine);
+}
+
+EogTransform*
+eog_transform_identity_new (void)
+{
+ EogTransform *trans;
+
+ trans = EOG_TRANSFORM (g_object_new (EOG_TYPE_TRANSFORM, NULL));
+
+ cairo_matrix_init_identity (&trans->priv->affine);
+
+ return trans;
+}
+
+EogTransform*
+eog_transform_rotate_new (int degree)
+{
+ EogTransform *trans;
+
+ trans = EOG_TRANSFORM (g_object_new (EOG_TYPE_TRANSFORM, NULL));
+
+ cairo_matrix_init_rotate (&trans->priv->affine, EOG_DEG_TO_RAD(degree));
+
+ return trans;
+}
+
+EogTransform*
+eog_transform_flip_new (EogTransformType type)
+{
+ EogTransform *trans;
+ gboolean horiz, vert;
+
+ trans = EOG_TRANSFORM (g_object_new (EOG_TYPE_TRANSFORM, NULL));
+
+ cairo_matrix_init_identity (&trans->priv->affine);
+
+ horiz = (type == EOG_TRANSFORM_FLIP_HORIZONTAL);
+ vert = (type == EOG_TRANSFORM_FLIP_VERTICAL);
+
+ _eog_cairo_matrix_flip (&trans->priv->affine,
+ &trans->priv->affine,
+ horiz, vert);
+
+ return trans;
+}
+
+EogTransform*
+eog_transform_new (EogTransformType type)
+{
+ EogTransform *trans = NULL;
+ EogTransform *temp1 = NULL, *temp2 = NULL;
+
+ switch (type) {
+ case EOG_TRANSFORM_NONE:
+ trans = eog_transform_identity_new ();
+ break;
+ case EOG_TRANSFORM_FLIP_HORIZONTAL:
+ trans = eog_transform_flip_new (EOG_TRANSFORM_FLIP_HORIZONTAL);
+ break;
+ case EOG_TRANSFORM_ROT_180:
+ trans = eog_transform_rotate_new (180);
+ break;
+ case EOG_TRANSFORM_FLIP_VERTICAL:
+ trans = eog_transform_flip_new (EOG_TRANSFORM_FLIP_VERTICAL);
+ break;
+ case EOG_TRANSFORM_TRANSPOSE:
+ temp1 = eog_transform_rotate_new (90);
+ temp2 = eog_transform_flip_new (EOG_TRANSFORM_FLIP_HORIZONTAL);
+ trans = eog_transform_compose (temp1, temp2);
+ g_object_unref (temp1);
+ g_object_unref (temp2);
+ break;
+ case EOG_TRANSFORM_ROT_90:
+ trans = eog_transform_rotate_new (90);
+ break;
+ case EOG_TRANSFORM_TRANSVERSE:
+ temp1 = eog_transform_rotate_new (90);
+ temp2 = eog_transform_flip_new (EOG_TRANSFORM_FLIP_VERTICAL);
+ trans = eog_transform_compose (temp1, temp2);
+ g_object_unref (temp1);
+ g_object_unref (temp2);
+ break;
+ case EOG_TRANSFORM_ROT_270:
+ trans = eog_transform_rotate_new (270);
+ break;
+ default:
+ trans = eog_transform_identity_new ();
+ break;
+ }
+
+ return trans;
+}
+
+EogTransformType
+eog_transform_get_transform_type (EogTransform *trans)
+{
+ cairo_matrix_t affine, a1, a2;
+ EogTransformPrivate *priv;
+
+ g_return_val_if_fail (EOG_IS_TRANSFORM (trans), EOG_TRANSFORM_NONE);
+
+ priv = trans->priv;
+
+ cairo_matrix_init_rotate (&affine, EOG_DEG_TO_RAD(90));
+ if (_eog_cairo_matrix_equal (&affine, &priv->affine)) {
+ return EOG_TRANSFORM_ROT_90;
+ }
+
+ cairo_matrix_init_rotate (&affine, EOG_DEG_TO_RAD(180));
+ if (_eog_cairo_matrix_equal (&affine, &priv->affine)) {
+ return EOG_TRANSFORM_ROT_180;
+ }
+
+ cairo_matrix_init_rotate (&affine, EOG_DEG_TO_RAD(270));
+ if (_eog_cairo_matrix_equal (&affine, &priv->affine)) {
+ return EOG_TRANSFORM_ROT_270;
+ }
+
+ cairo_matrix_init_identity (&affine);
+ _eog_cairo_matrix_flip (&affine, &affine, TRUE, FALSE);
+ if (_eog_cairo_matrix_equal (&affine, &priv->affine)) {
+ return EOG_TRANSFORM_FLIP_HORIZONTAL;
+ }
+
+ cairo_matrix_init_identity (&affine);
+ _eog_cairo_matrix_flip (&affine, &affine, FALSE, TRUE);
+ if (_eog_cairo_matrix_equal (&affine, &priv->affine)) {
+ return EOG_TRANSFORM_FLIP_VERTICAL;
+ }
+
+ cairo_matrix_init_rotate (&a1, EOG_DEG_TO_RAD(90));
+ cairo_matrix_init_identity (&a2);
+ _eog_cairo_matrix_flip (&a2, &a2, TRUE, FALSE);
+ cairo_matrix_multiply(&affine, &a1, &a2);
+ if (_eog_cairo_matrix_equal (&affine, &priv->affine)) {
+ return EOG_TRANSFORM_TRANSPOSE;
+ }
+
+ /* A transversion is a 180Â rotation followed by a transposition */
+ /* Reuse the transposition from the previous step for this. */
+ cairo_matrix_init_rotate (&a1, EOG_DEG_TO_RAD(180));
+ cairo_matrix_multiply(&a2, &a1, &affine);
+ if (_eog_cairo_matrix_equal (&a2, &priv->affine)) {
+ return EOG_TRANSFORM_TRANSVERSE;
+ }
+
+ return EOG_TRANSFORM_NONE;
+}
+
+gboolean
+eog_transform_get_affine (EogTransform *trans, cairo_matrix_t *affine)
+{
+ g_return_val_if_fail (EOG_IS_TRANSFORM (trans), FALSE);
+
+ _eog_cairo_matrix_copy (&trans->priv->affine, affine);
+
+ return TRUE;
+}
+
diff --git a/src/eog-transform.h b/src/eog-transform.h
new file mode 100644
index 0000000..4dee8a3
--- /dev/null
+++ b/src/eog-transform.h
@@ -0,0 +1,76 @@
+#ifndef _EOG_TRANSFORM_H_
+#define _EOG_TRANSFORM_H_
+
+#include <cairo.h>
+#include <glib-object.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+G_BEGIN_DECLS
+
+#ifndef __EOG_JOB_DECLR__
+#define __EOG_JOB_DECLR__
+typedef struct _EogJob EogJob;
+#endif
+
+typedef enum {
+ EOG_TRANSFORM_NONE,
+ EOG_TRANSFORM_ROT_90,
+ EOG_TRANSFORM_ROT_180,
+ EOG_TRANSFORM_ROT_270,
+ EOG_TRANSFORM_FLIP_HORIZONTAL,
+ EOG_TRANSFORM_FLIP_VERTICAL,
+ EOG_TRANSFORM_TRANSPOSE,
+ EOG_TRANSFORM_TRANSVERSE
+} EogTransformType;
+
+#define EOG_TYPE_TRANSFORM (eog_transform_get_type ())
+#define EOG_TRANSFORM(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), EOG_TYPE_TRANSFORM, EogTransform))
+#define EOG_TRANSFORM_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), EOG_TYPE_TRANSFORM, EogTransformClass))
+#define EOG_IS_TRANSFORM(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), EOG_TYPE_TRANSFORM))
+#define EOG_IS_TRANSFORM_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), EOG_TYPE_TRANSFORM))
+#define EOG_TRANSFORM_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), EOG_TYPE_TRANSFORM, EogTransformClass))
+
+/* =========================================
+
+ GObjecat wrapper around an affine transformation
+
+ ----------------------------------------*/
+
+typedef struct _EogTransform EogTransform;
+typedef struct _EogTransformClass EogTransformClass;
+typedef struct _EogTransformPrivate EogTransformPrivate;
+
+struct _EogTransform {
+ GObject parent;
+
+ EogTransformPrivate *priv;
+};
+
+struct _EogTransformClass {
+ GObjectClass parent_klass;
+};
+
+GType eog_transform_get_type (void) G_GNUC_CONST;
+
+GdkPixbuf* eog_transform_apply (EogTransform *trans, GdkPixbuf *pixbuf, EogJob *job);
+EogTransform* eog_transform_reverse (EogTransform *trans);
+EogTransform* eog_transform_compose (EogTransform *trans, EogTransform *compose);
+gboolean eog_transform_is_identity (EogTransform *trans);
+
+EogTransform* eog_transform_identity_new (void);
+EogTransform* eog_transform_rotate_new (int degree);
+EogTransform* eog_transform_flip_new (EogTransformType type /* only EOG_TRANSFORM_FLIP_* are valid */);
+#if 0
+EogTransform* eog_transform_scale_new (double sx, double sy);
+#endif
+EogTransform* eog_transform_new (EogTransformType trans);
+
+EogTransformType eog_transform_get_transform_type (EogTransform *trans);
+
+gboolean eog_transform_get_affine (EogTransform *trans, cairo_matrix_t *affine);
+
+G_END_DECLS
+
+#endif /* _EOG_TRANSFORM_H_ */
+
+
diff --git a/src/eog-uri-converter.c b/src/eog-uri-converter.c
new file mode 100644
index 0000000..baa6923
--- /dev/null
+++ b/src/eog-uri-converter.c
@@ -0,0 +1,988 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <math.h>
+#include <string.h>
+#include <glib.h>
+
+#include "eog-uri-converter.h"
+#include "photos-utils.h"
+
+enum {
+ PROP_0,
+ PROP_CONVERT_SPACES,
+ PROP_SPACE_CHARACTER,
+ PROP_COUNTER_START,
+ PROP_COUNTER_N_DIGITS,
+ PROP_N_IMAGES
+};
+
+typedef struct {
+ EogUCType type;
+ union {
+ char *string; /* if type == EOG_UC_STRING */
+ gulong counter; /* if type == EOG_UC_COUNTER */
+ } data;
+} EogUCToken;
+
+
+struct _EogURIConverterPrivate {
+ GFile *base_file;
+ GList *token_list;
+ char *suffix;
+ GdkPixbufFormat *img_format;
+ gboolean requires_exif;
+
+ /* options */
+ gboolean convert_spaces;
+ gchar space_character;
+ gulong counter_start;
+ guint counter_n_digits;
+};
+
+static void eog_uri_converter_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+
+static void eog_uri_converter_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+#define EOG_URI_CONVERTER_GET_PRIVATE(object) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((object), EOG_TYPE_URI_CONVERTER, EogURIConverterPrivate))
+
+G_DEFINE_TYPE (EogURIConverter, eog_uri_converter, G_TYPE_OBJECT)
+
+static void
+free_token (gpointer data)
+{
+ EogUCToken *token = (EogUCToken*) data;
+
+ if (token->type == EOG_UC_STRING) {
+ g_free (token->data.string);
+ }
+
+ g_slice_free (EogUCToken, token);
+}
+
+static void
+eog_uri_converter_dispose (GObject *object)
+{
+ EogURIConverter *instance = EOG_URI_CONVERTER (object);
+ EogURIConverterPrivate *priv;
+
+ priv = instance->priv;
+
+ if (priv->base_file) {
+ g_object_unref (priv->base_file);
+ priv->base_file = NULL;
+ }
+
+ if (priv->token_list) {
+ g_list_foreach (priv->token_list, (GFunc) free_token, NULL);
+ g_list_free (priv->token_list);
+ priv->token_list = NULL;
+ }
+
+ if (priv->suffix) {
+ g_free (priv->suffix);
+ priv->suffix = NULL;
+ }
+
+
+ G_OBJECT_CLASS (eog_uri_converter_parent_class)->dispose (object);
+}
+
+static void
+eog_uri_converter_init (EogURIConverter *obj)
+{
+ EogURIConverterPrivate *priv;
+
+ priv = obj->priv = EOG_URI_CONVERTER_GET_PRIVATE (obj);
+
+ priv->convert_spaces = FALSE;
+ priv->space_character = '_';
+ priv->counter_start = 0;
+ priv->counter_n_digits = 1;
+ priv->requires_exif = FALSE;
+}
+
+static void
+eog_uri_converter_class_init (EogURIConverterClass *klass)
+{
+ GObjectClass *object_class = (GObjectClass*) klass;
+
+ object_class->dispose = eog_uri_converter_dispose;
+
+ /* GObjectClass */
+ object_class->set_property = eog_uri_converter_set_property;
+ object_class->get_property = eog_uri_converter_get_property;
+
+ /* Properties */
+ g_object_class_install_property (
+ object_class,
+ PROP_CONVERT_SPACES,
+ g_param_spec_boolean ("convert-spaces", NULL, NULL,
+ FALSE, G_PARAM_READWRITE));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_SPACE_CHARACTER,
+ g_param_spec_char ("space-character", NULL, NULL,
+ ' ', '~', '_', G_PARAM_READWRITE));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_COUNTER_START,
+ g_param_spec_ulong ("counter-start", NULL, NULL,
+ 0,
+ G_MAXULONG,
+ 1,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_COUNTER_N_DIGITS,
+ g_param_spec_uint ("counter-n-digits", NULL, NULL,
+ 1,
+ G_MAXUINT,
+ 1,
+ G_PARAM_READWRITE));
+
+
+ g_object_class_install_property (
+ object_class,
+ PROP_N_IMAGES,
+ g_param_spec_uint ("n-images", NULL, NULL,
+ 1,
+ G_MAXUINT,
+ 1,
+ G_PARAM_WRITABLE));
+
+ g_type_class_add_private (klass, sizeof (EogURIConverterPrivate));
+}
+
+GQuark
+eog_uc_error_quark (void)
+{
+ static GQuark q = 0;
+ if (q == 0)
+ q = g_quark_from_static_string ("eog-uri-converter-error-quark");
+
+ return q;
+}
+
+
+static void
+eog_uri_converter_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ EogURIConverter *conv;
+ EogURIConverterPrivate *priv;
+
+ g_return_if_fail (EOG_IS_URI_CONVERTER (object));
+
+ conv = EOG_URI_CONVERTER (object);
+ priv = conv->priv;
+
+ switch (property_id)
+ {
+ case PROP_CONVERT_SPACES:
+ priv->convert_spaces = g_value_get_boolean (value);
+ break;
+
+ case PROP_SPACE_CHARACTER:
+ priv->space_character = g_value_get_schar (value);
+ break;
+
+ case PROP_COUNTER_START:
+ {
+ guint new_n_digits;
+
+ priv->counter_start = g_value_get_ulong (value);
+
+ new_n_digits = ceil (log10 (priv->counter_start + pow (10, priv->counter_n_digits) - 1));
+
+ if (new_n_digits != priv->counter_n_digits) {
+ priv->counter_n_digits = ceil (MIN (log10 (G_MAXULONG), new_n_digits));
+ }
+ break;
+ }
+
+ case PROP_COUNTER_N_DIGITS:
+ priv->counter_n_digits = ceil (MIN (log10 (G_MAXULONG), g_value_get_uint (value)));
+ break;
+
+ case PROP_N_IMAGES:
+ priv->counter_n_digits = ceil (MIN (log10 (G_MAXULONG),
+ log10 (priv->counter_start + g_value_get_uint (value))));
+ break;
+
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static void
+eog_uri_converter_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ EogURIConverter *conv;
+ EogURIConverterPrivate *priv;
+
+ g_return_if_fail (EOG_IS_URI_CONVERTER (object));
+
+ conv = EOG_URI_CONVERTER (object);
+ priv = conv->priv;
+
+ switch (property_id)
+ {
+ case PROP_CONVERT_SPACES:
+ g_value_set_boolean (value, priv->convert_spaces);
+ break;
+
+ case PROP_SPACE_CHARACTER:
+ g_value_set_schar (value, priv->space_character);
+ break;
+
+ case PROP_COUNTER_START:
+ g_value_set_ulong (value, priv->counter_start);
+ break;
+
+ case PROP_COUNTER_N_DIGITS:
+ g_value_set_uint (value, priv->counter_n_digits);
+ break;
+
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+/* parser states */
+enum {
+ PARSER_NONE,
+ PARSER_STRING,
+ PARSER_TOKEN
+};
+
+static EogUCToken*
+create_token_string (const char *string, int substr_start, int substr_len)
+{
+ char *start_byte;
+ char *end_byte;
+ int n_bytes;
+ EogUCToken *token;
+
+ if (string == NULL) return NULL;
+ if (substr_len <= 0) return NULL;
+
+ start_byte = g_utf8_offset_to_pointer (string, substr_start);
+ end_byte = g_utf8_offset_to_pointer (string, substr_start + substr_len);
+
+ /* FIXME: is this right? */
+ n_bytes = end_byte - start_byte;
+
+ token = g_slice_new0 (EogUCToken);
+ token->type = EOG_UC_STRING;
+ token->data.string = g_new0 (char, n_bytes);
+ token->data.string = g_utf8_strncpy (token->data.string, start_byte, substr_len);
+
+ return token;
+}
+
+static EogUCToken*
+create_token_counter (int start_counter)
+{
+ EogUCToken *token;
+
+ token = g_slice_new0 (EogUCToken);
+ token->type = EOG_UC_COUNTER;
+ token->data.counter = 0;
+
+ return token;
+}
+
+static EogUCToken*
+create_token_other (EogUCType type)
+{
+ EogUCToken *token;
+
+ token = g_slice_new0 (EogUCToken);
+ token->type = type;
+
+ return token;
+}
+
+static GList*
+eog_uri_converter_parse_string (EogURIConverter *conv, const char *string)
+{
+ EogURIConverterPrivate *priv;
+ GList *list = NULL;
+ gulong len;
+ int i;
+ int state = PARSER_NONE;
+ int start = -1;
+ int substr_len = 0;
+ gunichar c;
+ const char *s;
+ EogUCToken *token;
+
+ g_return_val_if_fail (EOG_IS_URI_CONVERTER (conv), NULL);
+
+ priv = conv->priv;
+
+ if (string == NULL) return NULL;
+
+ if (!g_utf8_validate (string, -1, NULL))
+ return NULL;
+
+ len = g_utf8_strlen (string, -1);
+ s = string;
+
+ for (i = 0; i < len; i++) {
+ c = g_utf8_get_char (s);
+ token = NULL;
+
+ switch (state) {
+ case PARSER_NONE:
+ if (c == '%') {
+ start = -1;
+ state = PARSER_TOKEN;
+ } else {
+ start = i;
+ substr_len = 1;
+ state = PARSER_STRING;
+ }
+ break;
+
+ case PARSER_STRING:
+ if (c == '%') {
+ if (start != -1) {
+ token = create_token_string (string, start, substr_len);
+ }
+
+ state = PARSER_TOKEN;
+ start = -1;
+ } else {
+ substr_len++;
+ }
+ break;
+
+ case PARSER_TOKEN: {
+ EogUCType type = EOG_UC_END;
+
+ if (c == 'f') {
+ type = EOG_UC_FILENAME;
+ }
+ else if (c == 'n') {
+ type = EOG_UC_COUNTER;
+ token = create_token_counter (priv->counter_start);
+ }
+ else if (c == 'c') {
+ type = EOG_UC_COMMENT;
+ }
+ else if (c == 'd') {
+ type = EOG_UC_DATE;
+ }
+ else if (c == 't') {
+ type = EOG_UC_TIME;
+ }
+ else if (c == 'a') {
+ type = EOG_UC_DAY;
+ }
+ else if (c == 'm') {
+ type = EOG_UC_MONTH;
+ }
+ else if (c == 'y') {
+ type = EOG_UC_YEAR;
+ }
+ else if (c == 'h') {
+ type = EOG_UC_HOUR;
+ }
+ else if (c == 'i') {
+ type = EOG_UC_MINUTE;
+ }
+ else if (c == 's') {
+ type = EOG_UC_SECOND;
+ }
+
+ if (type != EOG_UC_END && token == NULL) {
+ token = create_token_other (type);
+ priv->requires_exif = TRUE;
+ }
+ state = PARSER_NONE;
+ break;
+ }
+ default:
+ g_assert_not_reached ();
+ }
+
+
+ if (token != NULL) {
+ list = g_list_append (list, token);
+ }
+
+ s = g_utf8_next_char (s);
+ }
+
+ if (state != PARSER_TOKEN && start >= 0) {
+ /* add remaining chars as string token */
+ list = g_list_append (list, create_token_string (string, start, substr_len));
+ }
+
+ return list;
+}
+
+void
+eog_uri_converter_print_list (EogURIConverter *conv)
+{
+ EogURIConverterPrivate *priv;
+ GList *it;
+
+ g_return_if_fail (EOG_URI_CONVERTER (conv));
+
+ priv = conv->priv;
+
+ for (it = priv->token_list; it != NULL; it = it->next) {
+ EogUCToken *token;
+ char *str;
+
+ token = (EogUCToken*) it->data;
+
+ switch (token->type) {
+ case EOG_UC_STRING:
+ str = g_strdup_printf ("string [%s]", token->data.string);
+ break;
+ case EOG_UC_FILENAME:
+ str = "filename";
+ break;
+ case EOG_UC_COUNTER:
+ str = g_strdup_printf ("counter [%lu]", token->data.counter);
+ break;
+ case EOG_UC_COMMENT:
+ str = "comment";
+ break;
+ case EOG_UC_DATE:
+ str = "date";
+ break;
+ case EOG_UC_TIME:
+ str = "time";
+ break;
+ case EOG_UC_DAY:
+ str = "day";
+ break;
+ case EOG_UC_MONTH:
+ str = "month";
+ break;
+ case EOG_UC_YEAR:
+ str = "year";
+ break;
+ case EOG_UC_HOUR:
+ str = "hour";
+ break;
+ case EOG_UC_MINUTE:
+ str = "minute";
+ break;
+ case EOG_UC_SECOND:
+ str = "second";
+ break;
+ default:
+ str = "unknown";
+ break;
+ }
+
+ g_print ("- %s\n", str);
+
+ if (token->type == EOG_UC_STRING || token->type == EOG_UC_COUNTER) {
+ g_free (str);
+ }
+ }
+}
+
+
+EogURIConverter*
+eog_uri_converter_new (GFile *base_file, GdkPixbufFormat *img_format, const char *format_str)
+{
+ EogURIConverter *conv;
+
+ g_return_val_if_fail (format_str != NULL, NULL);
+
+ conv = g_object_new (EOG_TYPE_URI_CONVERTER, NULL);
+
+ if (base_file != NULL) {
+ conv->priv->base_file = g_object_ref (base_file);
+ }
+ else {
+ conv->priv->base_file = NULL;
+ }
+ conv->priv->img_format = img_format;
+ conv->priv->token_list = eog_uri_converter_parse_string (conv, format_str);
+
+ return conv;
+}
+
+static GFile*
+get_file_directory (EogURIConverter *conv, EogImage *image)
+{
+ GFile *file = NULL;
+ EogURIConverterPrivate *priv;
+
+ g_return_val_if_fail (EOG_IS_URI_CONVERTER (conv), NULL);
+ g_return_val_if_fail (EOG_IS_IMAGE (image), NULL);
+
+ priv = conv->priv;
+
+ if (priv->base_file != NULL) {
+ file = g_object_ref (priv->base_file);
+ }
+ else {
+ GFile *img_file;
+
+ img_file = eog_image_get_file (image);
+ g_assert (img_file != NULL);
+
+ file = g_file_get_parent (img_file);
+
+ g_object_unref (img_file);
+ }
+
+ return file;
+}
+
+static void
+split_filename (GFile *file, char **name, char **suffix)
+{
+ char *basename;
+ char *suffix_start;
+ guint len;
+
+ *name = NULL;
+ *suffix = NULL;
+
+ /* get unescaped string */
+ basename = g_file_get_basename (file);
+
+ /* FIXME: does this work for all locales? */
+ suffix_start = g_utf8_strrchr (basename, -1, '.');
+
+ if (suffix_start == NULL) { /* no suffix found */
+ *name = g_strdup (basename);
+ }
+ else {
+ len = (suffix_start - basename);
+ *name = g_strndup (basename, len);
+
+ len = strlen (basename) - len - 1;
+ *suffix = g_strndup (suffix_start+1, len);
+ }
+
+ g_free (basename);
+}
+
+static GString*
+append_filename (GString *str, EogImage *img)
+{
+ /* appends the name of the original file without
+ filetype suffix */
+ GFile *img_file;
+ char *name;
+ char *suffix;
+ GString *result;
+
+ img_file = eog_image_get_file (img);
+ split_filename (img_file, &name, &suffix);
+
+ result = g_string_append (str, name);
+
+ g_free (name);
+ g_free (suffix);
+
+ g_object_unref (img_file);
+
+ return result;
+}
+
+static GString*
+append_counter (GString *str, gulong counter, EogURIConverter *conv)
+{
+ EogURIConverterPrivate *priv;
+
+ priv = conv->priv;
+
+ g_string_append_printf (str, "%.*lu", priv->counter_n_digits, counter);
+
+ return str;
+}
+
+
+static void
+build_absolute_file (EogURIConverter *conv, EogImage *image, GString *str, /* input */
+ GFile **file, GdkPixbufFormat **format) /* output */
+{
+ GFile *dir_file;
+ EogURIConverterPrivate *priv;
+
+ *file = NULL;
+ if (format != NULL)
+ *format = NULL;
+
+ g_return_if_fail (EOG_IS_URI_CONVERTER (conv));
+ g_return_if_fail (EOG_IS_IMAGE (image));
+ g_return_if_fail (file != NULL);
+ g_return_if_fail (str != NULL);
+
+ priv = conv->priv;
+
+ dir_file = get_file_directory (conv, image);
+ g_assert (dir_file != NULL);
+
+ if (priv->img_format == NULL) {
+ /* use same file type/suffix */
+ char *name;
+ char *old_suffix;
+ GFile *img_file;
+
+ img_file = eog_image_get_file (image);
+ split_filename (img_file, &name, &old_suffix);
+
+ g_assert (old_suffix != NULL);
+
+ g_string_append_unichar (str, '.');
+ g_string_append (str, old_suffix);
+
+ if (format != NULL)
+ *format = photos_utils_get_pixbuf_format_by_suffix (old_suffix);
+
+ g_object_unref (img_file);
+ } else {
+ if (priv->suffix == NULL)
+ priv->suffix = photos_utils_get_pixbuf_common_suffix (priv->img_format);
+
+ g_string_append_unichar (str, '.');
+ g_string_append (str, priv->suffix);
+
+ if (format != NULL)
+ *format = priv->img_format;
+ }
+
+ *file = g_file_get_child (dir_file, str->str);
+
+ g_object_unref (dir_file);
+}
+
+
+static GString*
+replace_remove_chars (GString *str, gboolean convert_spaces, gunichar space_char)
+{
+ GString *result;
+ guint len;
+ char *s;
+ int i;
+ gunichar c;
+
+ g_return_val_if_fail (str != NULL, NULL);
+
+ if (!g_utf8_validate (str->str, -1, NULL))
+ return NULL;
+
+ result = g_string_new (NULL);
+
+ len = g_utf8_strlen (str->str, -1);
+ s = str->str;
+
+ for (i = 0; i < len; i++, s = g_utf8_next_char (s)) {
+ c = g_utf8_get_char (s);
+
+ if (c == '/') {
+ continue;
+ }
+ else if (g_unichar_isspace (c) && convert_spaces) {
+ result = g_string_append_unichar (result, space_char);
+ }
+ else {
+ result = g_string_append_unichar (result, c);
+ }
+ }
+
+ /* ensure maximum length of 250 characters */
+ len = MIN (result->len, 250);
+ result = g_string_truncate (result, len);
+
+ return result;
+}
+
+/*
+ * This function converts the uri of the EogImage object, according to the
+ * EogUCToken list. The absolute uri (converted filename appended to base uri)
+ * is returned in uri and the image format will be in the format pointer.
+ */
+gboolean
+eog_uri_converter_do (EogURIConverter *conv, EogImage *image,
+ GFile **file, GdkPixbufFormat **format, GError **error)
+{
+ EogURIConverterPrivate *priv;
+ GList *it;
+ GString *str;
+ GString *repl_str;
+
+ g_return_val_if_fail (EOG_IS_URI_CONVERTER (conv), FALSE);
+
+ priv = conv->priv;
+
+ *file = NULL;
+ if (format != NULL)
+ *format = NULL;
+
+ str = g_string_new ("");
+
+ for (it = priv->token_list; it != NULL; it = it->next) {
+ EogUCToken *token = (EogUCToken*) it->data;
+
+ switch (token->type) {
+ case EOG_UC_STRING:
+ str = g_string_append (str, token->data.string);
+ break;
+
+ case EOG_UC_FILENAME:
+ str = append_filename (str, image);
+ break;
+
+ case EOG_UC_COUNTER: {
+ if (token->data.counter < priv->counter_start)
+ token->data.counter = priv->counter_start;
+
+ str = append_counter (str, token->data.counter++, conv);
+ break;
+ }
+#if 0
+ case EOG_UC_COMMENT:
+ str = g_string_append_printf ();
+ str = "comment";
+ break;
+ case EOG_UC_DATE:
+ str = "date";
+ break;
+ case EOG_UC_TIME:
+ str = "time";
+ break;
+ case EOG_UC_DAY:
+ str = "day";
+ break;
+ case EOG_UC_MONTH:
+ str = "month";
+ break;
+ case EOG_UC_YEAR:
+ str = "year";
+ break;
+ case EOG_UC_HOUR:
+ str = "hour";
+ break;
+ case EOG_UC_MINUTE:
+ str = "minute";
+ break;
+ case EOG_UC_SECOND:
+ str = "second";
+ break;
+#endif
+ default:
+ /* skip all others */
+
+ break;
+ }
+ }
+
+ repl_str = replace_remove_chars (str, priv->convert_spaces, priv->space_character);
+
+ if (repl_str->len > 0) {
+ build_absolute_file (conv, image, repl_str, file, format);
+ }
+
+ g_string_free (repl_str, TRUE);
+ g_string_free (str, TRUE);
+
+
+ return (*file != NULL);
+}
+
+
+char*
+eog_uri_converter_preview (const char *format_str, EogImage *img, GdkPixbufFormat *format,
+ gulong counter, guint n_images,
+ gboolean convert_spaces, gunichar space_char)
+{
+ GString *str;
+ GString *repl_str;
+ guint n_digits;
+ guint len;
+ int i;
+ const char *s;
+ gunichar c;
+ char *filename;
+ gboolean token_next;
+
+ g_return_val_if_fail (format_str != NULL, NULL);
+ g_return_val_if_fail (EOG_IS_IMAGE (img), NULL);
+
+ if (n_images == 0) return NULL;
+
+ n_digits = ceil (MIN (log10 (G_MAXULONG), MAX (log10 (counter), log10 (n_images))));
+
+ str = g_string_new ("");
+
+ if (!g_utf8_validate (format_str, -1, NULL))
+ return NULL;
+
+ len = g_utf8_strlen (format_str, -1);
+ s = format_str;
+ token_next = FALSE;
+
+ for (i = 0; i < len; i++, s = g_utf8_next_char (s)) {
+ c = g_utf8_get_char (s);
+
+ if (token_next) {
+ if (c == 'f') {
+ str = append_filename (str, img);
+ }
+ else if (c == 'n') {
+ g_string_append_printf (str, "%.*lu",
+ n_digits ,counter);
+
+ }
+#if 0 /* ignore the rest for now */
+ else if (c == 'c') {
+ type = EOG_UC_COMMENT;
+ }
+ else if (c == 'd') {
+ type = EOG_UC_DATE;
+ }
+ else if (c == 't') {
+ type = EOG_UC_TIME;
+ }
+ else if (c == 'a') {
+ type = EOG_UC_DAY;
+ }
+ else if (c == 'm') {
+ type = EOG_UC_MONTH;
+ }
+ else if (c == 'y') {
+ type = EOG_UC_YEAR;
+ }
+ else if (c == 'h') {
+ type = EOG_UC_HOUR;
+ }
+ else if (c == 'i') {
+ type = EOG_UC_MINUTE;
+ }
+ else if (c == 's') {
+ type = EOG_UC_SECOND;
+ }
+#endif
+ token_next = FALSE;
+ }
+ else if (c == '%') {
+ token_next = TRUE;
+ }
+ else {
+ str = g_string_append_unichar (str, c);
+ }
+ }
+
+
+ filename = NULL;
+ repl_str = replace_remove_chars (str, convert_spaces, space_char);
+
+ if (repl_str->len > 0) {
+ if (format == NULL) {
+ /* use same file type/suffix */
+ char *name;
+ char *old_suffix;
+ GFile *img_file;
+
+ img_file = eog_image_get_file (img);
+ split_filename (img_file, &name, &old_suffix);
+
+ g_assert (old_suffix != NULL);
+
+ g_string_append_unichar (repl_str, '.');
+ g_string_append (repl_str, old_suffix);
+
+ g_free (old_suffix);
+ g_free (name);
+ g_object_unref (img_file);
+ }
+ else {
+ char *suffix = photos_utils_get_pixbuf_common_suffix (format);
+
+ g_string_append_unichar (repl_str, '.');
+ g_string_append (repl_str, suffix);
+
+ g_free (suffix);
+ }
+
+ filename = repl_str->str;
+ }
+
+ g_string_free (repl_str, FALSE);
+ g_string_free (str, TRUE);
+
+ return filename;
+}
+
+gboolean
+eog_uri_converter_requires_exif (EogURIConverter *converter)
+{
+ g_return_val_if_fail (EOG_IS_URI_CONVERTER (converter), FALSE);
+
+ return converter->priv->requires_exif;
+}
+
+gboolean
+eog_uri_converter_check (EogURIConverter *converter, GList *img_list, GError **error)
+{
+ GList *it;
+ GList *file_list = NULL;
+ gboolean all_different = TRUE;
+
+ g_return_val_if_fail (EOG_IS_URI_CONVERTER (converter), FALSE);
+
+ /* convert all image uris */
+ for (it = img_list; it != NULL; it = it->next) {
+ gboolean result;
+ GFile *file;
+ GError *conv_error = NULL;
+
+ result = eog_uri_converter_do (converter, EOG_IMAGE (it->data),
+ &file, NULL, &conv_error);
+
+ if (result) {
+ file_list = g_list_prepend (file_list, file);
+ }
+ }
+
+ /* check for all different uris */
+ for (it = file_list; it != NULL && all_different; it = it->next) {
+ GList *p;
+ GFile *file;
+
+ file = (GFile*) it->data;
+
+ for (p = it->next; p != NULL && all_different; p = p->next) {
+ all_different = !g_file_equal (file, (GFile*) p->data);
+ }
+ }
+
+ if (!all_different) {
+ g_set_error (error, EOG_UC_ERROR,
+ EOG_UC_ERROR_EQUAL_FILENAMES,
+ _("At least two file names are equal."));
+ }
+
+ return all_different;
+}
diff --git a/src/eog-uri-converter.h b/src/eog-uri-converter.h
new file mode 100644
index 0000000..21b5505
--- /dev/null
+++ b/src/eog-uri-converter.h
@@ -0,0 +1,107 @@
+#ifndef _EOG_URI_CONVERTER_H_
+#define _EOG_URI_CONVERTER_H_
+
+#include <glib-object.h>
+#include <glib/gi18n.h>
+#include "eog-image.h"
+
+G_BEGIN_DECLS
+
+#define EOG_TYPE_URI_CONVERTER (eog_uri_converter_get_type ())
+#define EOG_URI_CONVERTER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), EOG_TYPE_URI_CONVERTER, EogURIConverter))
+#define EOG_URI_CONVERTER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), EOG_TYPE_URI_CONVERTER, EogURIConverterClass))
+#define EOG_IS_URI_CONVERTER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), EOG_TYPE_URI_CONVERTER))
+#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;
+
+typedef enum {
+ EOG_UC_STRING,
+ EOG_UC_FILENAME,
+ EOG_UC_COUNTER,
+ EOG_UC_COMMENT,
+ EOG_UC_DATE,
+ EOG_UC_TIME,
+ EOG_UC_DAY,
+ EOG_UC_MONTH,
+ EOG_UC_YEAR,
+ EOG_UC_HOUR,
+ EOG_UC_MINUTE,
+ EOG_UC_SECOND,
+ EOG_UC_END
+} EogUCType;
+
+typedef struct {
+ char *description;
+ char *rep;
+ gboolean req_exif;
+} EogUCInfo;
+
+typedef enum {
+ EOG_UC_ERROR_INVALID_UNICODE,
+ EOG_UC_ERROR_INVALID_CHARACTER,
+ EOG_UC_ERROR_EQUAL_FILENAMES,
+ EOG_UC_ERROR_UNKNOWN
+} EogUCError;
+
+#define EOG_UC_ERROR eog_uc_error_quark ()
+
+
+struct _EogURIConverter {
+ GObject parent;
+
+ EogURIConverterPrivate *priv;
+};
+
+struct _EogURIConverterClass {
+ GObjectClass parent_klass;
+};
+
+G_GNUC_INTERNAL
+GType eog_uri_converter_get_type (void) G_GNUC_CONST;
+
+G_GNUC_INTERNAL
+GQuark eog_uc_error_quark (void);
+
+G_GNUC_INTERNAL
+EogURIConverter* eog_uri_converter_new (GFile *base_file,
+ GdkPixbufFormat *img_format,
+ const char *format_string);
+
+G_GNUC_INTERNAL
+gboolean eog_uri_converter_check (EogURIConverter *converter,
+ GList *img_list,
+ GError **error);
+
+G_GNUC_INTERNAL
+gboolean eog_uri_converter_requires_exif (EogURIConverter *converter);
+
+G_GNUC_INTERNAL
+gboolean eog_uri_converter_do (EogURIConverter *converter,
+ EogImage *image,
+ GFile **file,
+ GdkPixbufFormat **format,
+ GError **error);
+
+G_GNUC_INTERNAL
+char* eog_uri_converter_preview (const char *format_str,
+ EogImage *img,
+ GdkPixbufFormat *format,
+ gulong counter,
+ guint n_images,
+ gboolean convert_spaces,
+ gunichar space_char);
+
+/* for debugging purpose only */
+G_GNUC_INTERNAL
+void eog_uri_converter_print_list (EogURIConverter *conv);
+
+G_END_DECLS
+
+#endif /* _EOG_URI_CONVERTER_H_ */
diff --git a/src/eog-util.c b/src/eog-util.c
new file mode 100644
index 0000000..0460aa1
--- /dev/null
+++ b/src/eog-util.c
@@ -0,0 +1,506 @@
+/* Eye Of Gnome - General Utilities
+ *
+ * Copyright (C) 2006 The Free Software Foundation
+ *
+ * Author: Lucas Rocha <lucasr gnome org>
+ *
+ * Based on code by:
+ * - Jens Finke <jens gnome org>
+ *
+ * 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 Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <sys/time.h>
+#ifdef HAVE_STRPTIME
+#define _XOPEN_SOURCE
+#endif /* HAVE_STRPTIME */
+
+#include <time.h>
+
+#include "eog-util.h"
+#include "eog-debug.h"
+
+#include <errno.h>
+#include <string.h>
+#include <glib.h>
+#include <glib/gprintf.h>
+#include <gtk/gtk.h>
+#include <gio/gio.h>
+#include <glib/gi18n.h>
+
+void
+eog_util_show_help (const gchar *section, GtkWindow *parent)
+{
+ GError *error = NULL;
+ gchar *uri = NULL;
+
+ if (section)
+ uri = g_strdup_printf ("help:eog/%s", section);
+
+ gtk_show_uri (NULL, ((uri != NULL) ? uri : "help:eog"),
+ gtk_get_current_event_time (), &error);
+
+ g_free (uri);
+
+ if (error) {
+ GtkWidget *dialog;
+
+ dialog = gtk_message_dialog_new (parent,
+ 0,
+ GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_OK,
+ _("Could not display help for Image Viewer"));
+
+ gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
+ "%s", error->message);
+
+ g_signal_connect_swapped (dialog, "response",
+ G_CALLBACK (gtk_widget_destroy),
+ dialog);
+ gtk_widget_show (dialog);
+
+ g_error_free (error);
+ }
+}
+
+gchar *
+eog_util_make_valid_utf8 (const gchar *str)
+{
+ GString *string;
+ const char *remainder, *invalid;
+ int remaining_bytes, valid_bytes;
+
+ string = NULL;
+ remainder = str;
+ remaining_bytes = strlen (str);
+
+ while (remaining_bytes != 0) {
+ if (g_utf8_validate (remainder, remaining_bytes, &invalid)) {
+ break;
+ }
+
+ valid_bytes = invalid - remainder;
+
+ if (string == NULL) {
+ string = g_string_sized_new (remaining_bytes);
+ }
+
+ g_string_append_len (string, remainder, valid_bytes);
+ g_string_append_c (string, '?');
+
+ remaining_bytes -= valid_bytes + 1;
+ remainder = invalid + 1;
+ }
+
+ if (string == NULL) {
+ return g_strdup (str);
+ }
+
+ g_string_append (string, remainder);
+ g_string_append (string, _(" (invalid Unicode)"));
+
+ g_assert (g_utf8_validate (string->str, -1, NULL));
+
+ return g_string_free (string, FALSE);
+}
+
+GSList*
+eog_util_parse_uri_string_list_to_file_list (const gchar *uri_list)
+{
+ GSList* file_list = NULL;
+ gsize i = 0;
+ gchar **uris;
+
+ uris = g_uri_list_extract_uris (uri_list);
+
+ while (uris[i] != NULL) {
+ file_list = g_slist_append (file_list, g_file_new_for_uri (uris[i]));
+ i++;
+ }
+
+ g_strfreev (uris);
+
+ return file_list;
+}
+
+GSList*
+eog_util_string_list_to_file_list (GSList *string_list)
+{
+ GSList *it = NULL;
+ GSList *file_list = NULL;
+
+ for (it = string_list; it != NULL; it = it->next) {
+ char *uri_str;
+
+ uri_str = (gchar *) it->data;
+
+ file_list = g_slist_prepend (file_list,
+ g_file_new_for_uri (uri_str));
+ }
+
+ return g_slist_reverse (file_list);
+}
+
+GSList*
+eog_util_strings_to_file_list (gchar **strings)
+{
+ int i;
+ GSList *file_list = NULL;
+
+ for (i = 0; strings[i]; i++) {
+ file_list = g_slist_prepend (file_list,
+ g_file_new_for_uri (strings[i]));
+ }
+
+ return g_slist_reverse (file_list);
+}
+
+GSList*
+eog_util_string_array_to_list (const gchar **files, gboolean create_uri)
+{
+ gint i;
+ GSList *list = NULL;
+
+ if (files == NULL) return list;
+
+ for (i = 0; files[i]; i++) {
+ char *str;
+
+ if (create_uri) {
+ GFile *file;
+
+ file = g_file_new_for_commandline_arg (files[i]);
+ str = g_file_get_uri (file);
+
+ g_object_unref (file);
+ } else {
+ str = g_strdup (files[i]);
+ }
+
+ if (str) {
+ list = g_slist_prepend (list, g_strdup (str));
+ g_free (str);
+ }
+ }
+
+ return g_slist_reverse (list);
+}
+
+gchar **
+eog_util_string_array_make_absolute (gchar **files)
+{
+ int i;
+ int size;
+ gchar **abs_files;
+ GFile *file;
+
+ if (files == NULL)
+ return NULL;
+
+ size = g_strv_length (files);
+
+ /* Ensure new list is NULL-terminated */
+ abs_files = g_new0 (gchar *, size+1);
+
+ for (i = 0; i < size; i++) {
+ file = g_file_new_for_commandline_arg (files[i]);
+ abs_files[i] = g_file_get_uri (file);
+
+ g_object_unref (file);
+ }
+
+ return abs_files;
+}
+
+static gchar *dot_dir = NULL;
+static void migrate_config_folder (const gchar* new_folder);
+
+static gboolean
+ensure_dir_exists (const char *dir)
+{
+ if (g_file_test (dir, G_FILE_TEST_IS_DIR))
+ return TRUE;
+
+ if (g_mkdir_with_parents (dir, 0700) == 0) {
+ /* If the folder is created try migrating from the old folder */
+ migrate_config_folder (dir);
+ return TRUE;
+ }
+
+ if (errno == EEXIST)
+ return g_file_test (dir, G_FILE_TEST_IS_DIR);
+
+ g_warning ("Failed to create directory %s: %s", dir, strerror (errno));
+ return FALSE;
+}
+
+const gchar *
+eog_util_dot_dir (void)
+{
+ if (dot_dir == NULL) {
+ gboolean exists;
+
+ dot_dir = g_build_filename (g_get_user_config_dir (),
+ "eog", NULL);
+
+ exists = ensure_dir_exists (dot_dir);
+ if (G_UNLIKELY (!exists)) {
+ static gboolean printed_warning = FALSE;
+
+ if (!printed_warning) {
+ g_warning ("EOG could not save some of your preferences in its settings directory due to a file with the same name (%s) blocking its creation. Please remove that file, or move it away.", dot_dir);
+ printed_warning = TRUE;
+ }
+ g_free (dot_dir);
+ dot_dir = NULL;
+ return NULL;
+ }
+ }
+
+ return dot_dir;
+}
+
+static void migrate_config_file (const gchar *old_filename, const gchar* new_filename)
+{
+ GFile *old_file, *new_file;
+ GError *error = NULL;
+
+ if (!g_file_test (old_filename, G_FILE_TEST_IS_REGULAR))
+ return;
+
+ old_file = g_file_new_for_path (old_filename);
+ new_file = g_file_new_for_path (new_filename);
+
+ if (!g_file_move (old_file, new_file, G_FILE_COPY_NONE, NULL, NULL, NULL, &error)) {
+ g_warning ("Could not migrate config file %s: %s\n", old_filename, error->message);
+ g_error_free (error);
+ }
+ g_object_unref (new_file);
+ g_object_unref (old_file);
+}
+
+static void migrate_config_folder (const gchar* new_dir)
+{
+ gchar* old_dir = g_build_filename (g_get_home_dir (), ".gnome2",
+ "eog", NULL);
+ gchar* old_filename = NULL;
+ gchar* new_filename = NULL;
+ GError *error = NULL;
+ GFile *dir_file = NULL;
+ gsize i;
+ static const gchar *old_files[] = { "eog-print-settings.ini",
+ "eog_toolbar.xml",
+ NULL };
+
+ if(!g_file_test (old_dir, G_FILE_TEST_IS_DIR)) {
+ /* Nothing to migrate */
+ g_free (old_dir);
+ return;
+ }
+
+ eog_debug (DEBUG_PREFERENCES);
+
+ for (i = 0; old_files[i] != NULL; i++) {
+ old_filename = g_build_filename (old_dir,
+ old_files[i], NULL);
+ new_filename = g_build_filename (new_dir,
+ old_files[i], NULL);
+
+ migrate_config_file (old_filename, new_filename);
+
+ g_free (new_filename);
+ g_free (old_filename);
+ }
+
+ /* Migrate accels file */
+ old_filename = g_build_filename (g_get_home_dir (), ".gnome2",
+ "accels", "eog", NULL);
+ /* move file to ~/.config/eog/accels if its not already there */
+ new_filename = g_build_filename (new_dir, "accels", NULL);
+
+ migrate_config_file (old_filename, new_filename);
+
+ g_free (new_filename);
+ g_free (old_filename);
+
+ dir_file = g_file_new_for_path (old_dir);
+ if (!g_file_delete (dir_file, NULL, &error)) {
+ g_warning ("An error occured while deleting the old config folder %s: %s\n", old_dir, error->message);
+ g_error_free (error);
+ }
+ g_object_unref (dir_file);
+ g_free(old_dir);
+}
+
+/* Based on eel_filename_strip_extension() */
+
+/**
+ * eog_util_filename_get_extension:
+ * @filename: a filename
+ *
+ * Returns a reasonably good guess of the file extension of @filename.
+ *
+ * Returns: a newly allocated string with the file extension of @filename.
+ **/
+char *
+eog_util_filename_get_extension (const char * filename)
+{
+ char *begin, *begin2;
+
+ if (filename == NULL) {
+ return NULL;
+ }
+
+ begin = strrchr (filename, '.');
+
+ if (begin && begin != filename) {
+ if (strcmp (begin, ".gz") == 0 ||
+ strcmp (begin, ".bz2") == 0 ||
+ strcmp (begin, ".sit") == 0 ||
+ strcmp (begin, ".Z") == 0) {
+ begin2 = begin - 1;
+ while (begin2 > filename &&
+ *begin2 != '.') {
+ begin2--;
+ }
+ if (begin2 != filename) {
+ begin = begin2;
+ }
+ }
+ begin ++;
+ } else {
+ return NULL;
+ }
+
+ return g_strdup (begin);
+}
+
+
+/**
+ * eog_util_file_is_persistent:
+ * @file: a #GFile
+ *
+ * Checks whether @file is a non-removable local mount.
+ *
+ * Returns: %TRUE if @file is in a non-removable mount,
+ * %FALSE otherwise or when it is remote.
+ **/
+gboolean
+eog_util_file_is_persistent (GFile *file)
+{
+ GMount *mount;
+
+ if (!g_file_is_native (file))
+ return FALSE;
+
+ mount = g_file_find_enclosing_mount (file, NULL, NULL);
+ if (mount) {
+ if (g_mount_can_unmount (mount)) {
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static void
+_eog_util_show_file_in_filemanager_fallback (GFile *file, GdkScreen *screen)
+{
+ gchar *uri = NULL;
+ GError *error = NULL;
+ guint32 timestamp = gtk_get_current_event_time ();
+
+ if (g_file_query_file_type (file, 0, NULL) == G_FILE_TYPE_DIRECTORY) {
+ uri = g_file_get_uri (file);
+ } else {
+ /* If input file is not a directory we must open it's
+ folder/parent to avoid opening the file itself */
+ GFile *parent_file;
+
+ parent_file = g_file_get_parent (file);
+ if (G_LIKELY (parent_file))
+ uri = g_file_get_uri (parent_file);
+ g_object_unref (parent_file);
+ }
+
+ if (uri && !gtk_show_uri (screen, uri, timestamp, &error)) {
+ g_warning ("Couldn't show containing folder \"%s\": %s", uri,
+ error->message);
+ g_error_free (error);
+ }
+
+ g_free (uri);
+}
+
+void
+eog_util_show_file_in_filemanager (GFile *file, GdkScreen *screen)
+{
+ GDBusProxy *proxy;
+ gboolean done = FALSE;
+
+ g_return_if_fail (file != NULL);
+
+ proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION,
+ G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS |
+ G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
+ NULL, "org.freedesktop.FileManager1",
+ "/org/freedesktop/FileManager1",
+ "org.freedesktop.FileManager1",
+ NULL, NULL);
+
+ if (proxy) {
+ gchar *uri = g_file_get_uri (file);
+ gchar *startup_id;
+ GVariant *params, *result;
+ GVariantBuilder builder;
+
+ g_variant_builder_init (&builder,
+ G_VARIANT_TYPE ("as"));
+ g_variant_builder_add (&builder, "s", uri);
+
+ /* This seems to be the expected format, as other values
+ cause the filemanager window not to get focus. */
+ startup_id = g_strdup_printf("_TIME%u",
+ gtk_get_current_event_time());
+
+ /* params is floating! */
+ params = g_variant_new ("(ass)", &builder, startup_id);
+
+ g_free (startup_id);
+ g_variant_builder_clear (&builder);
+
+ /* Floating params-GVariant is consumed here */
+ result = g_dbus_proxy_call_sync (proxy, "ShowItems",
+ params, G_DBUS_CALL_FLAGS_NONE,
+ -1, NULL, NULL);
+
+ /* Receiving a non-NULL result counts as a successful call. */
+ if (G_LIKELY (result != NULL)) {
+ done = TRUE;
+ g_variant_unref (result);
+ }
+
+ g_free (uri);
+ g_object_unref (proxy);
+ }
+
+ /* Fallback to gtk_show_uri() if launch over DBus is not possible */
+ if (!done)
+ _eog_util_show_file_in_filemanager_fallback (file, screen);
+}
diff --git a/src/eog-util.h b/src/eog-util.h
new file mode 100644
index 0000000..fe8d58e
--- /dev/null
+++ b/src/eog-util.h
@@ -0,0 +1,74 @@
+/* Eye Of Gnome - General Utilities
+ *
+ * Copyright (C) 2006 The Free Software Foundation
+ *
+ * Author: Lucas Rocha <lucasr gnome org>
+ *
+ * Based on code by:
+ * - Jens Finke <jens gnome org>
+ *
+ * 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 Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __EOG_UTIL_H__
+#define __EOG_UTIL_H__
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+G_GNUC_INTERNAL
+void eog_util_show_help (const gchar *section,
+ GtkWindow *parent);
+
+G_GNUC_INTERNAL
+gchar *eog_util_make_valid_utf8 (const gchar *name);
+
+G_GNUC_INTERNAL
+GSList *eog_util_parse_uri_string_list_to_file_list (const gchar *uri_list);
+
+G_GNUC_INTERNAL
+GSList *eog_util_string_list_to_file_list (GSList *string_list);
+
+G_GNUC_INTERNAL
+GSList *eog_util_strings_to_file_list (gchar **strings);
+
+G_GNUC_INTERNAL
+GSList *eog_util_string_array_to_list (const gchar **files,
+ gboolean create_uri);
+
+G_GNUC_INTERNAL
+gchar **eog_util_string_array_make_absolute (gchar **files);
+
+G_GNUC_INTERNAL
+gboolean eog_util_launch_desktop_file (const gchar *filename,
+ guint32 user_time);
+
+G_GNUC_INTERNAL
+const gchar *eog_util_dot_dir (void);
+
+G_GNUC_INTERNAL
+char * eog_util_filename_get_extension (const char * filename);
+
+G_GNUC_INTERNAL
+gboolean eog_util_file_is_persistent (GFile *file);
+
+G_GNUC_INTERNAL
+void eog_util_show_file_in_filemanager (GFile *file,
+ GdkScreen *screen);
+
+G_END_DECLS
+
+#endif /* __EOG_UTIL_H__ */
diff --git a/src/photos-marshalers.list b/src/photos-marshalers.list
index 438c55c..fad4924 100644
--- a/src/photos-marshalers.list
+++ b/src/photos-marshalers.list
@@ -1 +1,2 @@
VOID:ENUM,ENUM
+VOID:INT,INT
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]