diff options
Diffstat (limited to 'src/caja-image-properties-page.c')
-rw-r--r-- | src/caja-image-properties-page.c | 745 |
1 files changed, 745 insertions, 0 deletions
diff --git a/src/caja-image-properties-page.c b/src/caja-image-properties-page.c new file mode 100644 index 00000000..119f262b --- /dev/null +++ b/src/caja-image-properties-page.c @@ -0,0 +1,745 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* + * Copyright (C) 2004 Red Hat, Inc + * Copyright (c) 2007 Novell, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <[email protected]> + * XMP support by Hubert Figuiere <[email protected]> + */ + +#include <config.h> +#include "caja-image-properties-page.h" + +#include <gtk/gtk.h> +#include <glib/gi18n.h> +#include <gio/gio.h> +#include <eel/eel-vfs-extensions.h> +#include <libcaja-extension/caja-property-page-provider.h> +#include <libcaja-private/caja-module.h> +#include <string.h> + +#ifdef HAVE_EXIF +#include <libexif/exif-data.h> +#include <libexif/exif-ifd.h> +#include <libexif/exif-loader.h> +#endif +#ifdef HAVE_EXEMPI +#include <exempi/xmp.h> +#include <exempi/xmpconsts.h> +#endif + +#define LOAD_BUFFER_SIZE 8192 + +struct CajaImagePropertiesPageDetails +{ + GCancellable *cancellable; + GtkWidget *vbox; + GtkWidget *loading_label; + GdkPixbufLoader *loader; + gboolean got_size; + gboolean pixbuf_still_loading; + char buffer[LOAD_BUFFER_SIZE]; + int width; + int height; +#ifdef HAVE_EXIF + ExifLoader *exifldr; +#endif /*HAVE_EXIF*/ +#ifdef HAVE_EXEMPI + XmpPtr xmp; +#endif +}; + +#ifdef HAVE_EXIF +struct ExifAttribute +{ + ExifTag tag; + char *value; + gboolean found; +}; +#endif /*HAVE_EXIF*/ + +enum +{ + PROP_URI +}; + +typedef struct +{ + GObject parent; +} CajaImagePropertiesPageProvider; + +typedef struct +{ + GObjectClass parent; +} CajaImagePropertiesPageProviderClass; + + +static GType caja_image_properties_page_provider_get_type (void); +static void property_page_provider_iface_init (CajaPropertyPageProviderIface *iface); + + +G_DEFINE_TYPE (CajaImagePropertiesPage, caja_image_properties_page, GTK_TYPE_VBOX); + +G_DEFINE_TYPE_WITH_CODE (CajaImagePropertiesPageProvider, caja_image_properties_page_provider, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (CAJA_TYPE_PROPERTY_PAGE_PROVIDER, + property_page_provider_iface_init)); + +static void +caja_image_properties_page_finalize (GObject *object) +{ + CajaImagePropertiesPage *page; + + page = CAJA_IMAGE_PROPERTIES_PAGE (object); + + if (page->details->cancellable) + { + g_cancellable_cancel (page->details->cancellable); + g_object_unref (page->details->cancellable); + page->details->cancellable = NULL; + } + + G_OBJECT_CLASS (caja_image_properties_page_parent_class)->finalize (object); +} + +static void +file_close_callback (GObject *object, + GAsyncResult *res, + gpointer data) +{ + CajaImagePropertiesPage *page; + GInputStream *stream; + + page = CAJA_IMAGE_PROPERTIES_PAGE (data); + stream = G_INPUT_STREAM (object); + + g_input_stream_close_finish (stream, res, NULL); + + g_object_unref (page->details->cancellable); + page->details->cancellable = NULL; +} + +static GtkWidget * +append_label (GtkWidget *vbox, + const char *str) +{ + GtkWidget *label; + + label = gtk_label_new (NULL); + gtk_label_set_markup (GTK_LABEL (label), str); + gtk_misc_set_alignment (GTK_MISC (label), 0, 0); + gtk_label_set_selectable (GTK_LABEL (label), TRUE); + + /* setting can_focus to FALSE will allow to make the label + * selectable but without the cursor showing. + */ + gtk_widget_set_can_focus (label, FALSE); + + gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0); + gtk_widget_show (label); + + return label; +} + +static GtkWidget * +append_label_take_str (GtkWidget *vbox, + char *str) +{ + GtkWidget *retval; + + retval = append_label (vbox, str); + g_free (str); + + return retval; +} + +#ifdef HAVE_EXIF +static char * +exif_string_to_utf8 (const char *exif_str) +{ + char *utf8_str; + + if (g_utf8_validate (exif_str, -1, NULL)) + { + return g_strdup (exif_str); + } + + utf8_str = g_locale_to_utf8 (exif_str, -1, NULL, NULL, NULL); + if (utf8_str != NULL) + { + return utf8_str; + } + + return eel_make_valid_utf8 (exif_str); +} + +static void +exif_content_callback (ExifContent *content, gpointer data) +{ + struct ExifAttribute *attribute; +#ifndef HAVE_OLD_EXIF + char b[1024]; +#endif + + attribute = (struct ExifAttribute *)data; + if (attribute->found) + { + return; + } + +#ifdef HAVE_OLD_EXIF + attribute->value = g_strdup (exif_content_get_value (content, attribute->tag)); +#else + attribute->value = g_strdup (exif_content_get_value (content, attribute->tag, b, sizeof(b))); +#endif + if (attribute->value != NULL) + { + attribute->found = TRUE; + } +} + +static char * +exifdata_get_tag_name_utf8 (ExifTag tag) +{ + return exif_string_to_utf8 (exif_tag_get_name (tag)); +} + +static char * +exifdata_get_tag_value_utf8 (ExifData *data, ExifTag tag) +{ + struct ExifAttribute attribute; + char *utf8_value; + + attribute.tag = tag; + attribute.value = NULL; + attribute.found = FALSE; + + exif_data_foreach_content (data, exif_content_callback, &attribute); + + if (attribute.found) + { + utf8_value = exif_string_to_utf8 (attribute.value); + g_free (attribute.value); + } + else + { + utf8_value = NULL; + } + + return utf8_value; +} + +static gboolean +append_tag_value_pair (CajaImagePropertiesPage *page, + ExifData *data, + ExifTag tag, + char *description) +{ + char *utf_attribute; + char *utf_value; + + utf_attribute = exifdata_get_tag_name_utf8 (tag); + utf_value = exifdata_get_tag_value_utf8 (data, tag); + + if ((utf_attribute == NULL) || (utf_value == NULL)) + { + g_free (utf_attribute); + g_free (utf_value); + return FALSE; + } + + append_label_take_str + (page->details->vbox, + g_strdup_printf ("<b>%s:</b> %s", + description ? description : utf_attribute, + utf_value)); + + g_free (utf_attribute); + g_free (utf_value); + return TRUE; +} + +static void +append_exifdata_string (ExifData *exifdata, CajaImagePropertiesPage *page) +{ + if (exifdata && exifdata->ifd[0] && exifdata->ifd[0]->count) + { + append_tag_value_pair (page, exifdata, EXIF_TAG_MAKE, _("Camera Brand")); + append_tag_value_pair (page, exifdata, EXIF_TAG_MODEL, _("Camera Model")); + + /* Choose which date to show in order of relevance */ + if (!append_tag_value_pair (page, exifdata, EXIF_TAG_DATE_TIME_ORIGINAL, _("Date Taken"))) + { + if (!append_tag_value_pair (page, exifdata, EXIF_TAG_DATE_TIME_DIGITIZED, _("Date Digitized"))) + { + append_tag_value_pair (page, exifdata, EXIF_TAG_DATE_TIME, _("Date Modified")); + } + } + + append_tag_value_pair (page, exifdata, EXIF_TAG_EXPOSURE_TIME, _("Exposure Time")); + append_tag_value_pair (page, exifdata, EXIF_TAG_APERTURE_VALUE, _("Aperture Value")); + append_tag_value_pair (page, exifdata, EXIF_TAG_ISO_SPEED_RATINGS, _("ISO Speed Rating")); + append_tag_value_pair (page, exifdata, EXIF_TAG_FLASH,_("Flash Fired")); + append_tag_value_pair (page, exifdata, EXIF_TAG_METERING_MODE, _("Metering Mode")); + append_tag_value_pair (page, exifdata, EXIF_TAG_EXPOSURE_PROGRAM, _("Exposure Program")); + append_tag_value_pair (page, exifdata, EXIF_TAG_FOCAL_LENGTH,_("Focal Length")); + append_tag_value_pair (page, exifdata, EXIF_TAG_SOFTWARE, _("Software")); + } +} +#endif /*HAVE_EXIF*/ + +#ifdef HAVE_EXEMPI +static void +append_xmp_value_pair (CajaImagePropertiesPage *page, + XmpPtr xmp, + const char *ns, + const char *propname, + char *descr) +{ + uint32_t options; + XmpStringPtr value; + + value = xmp_string_new(); +#ifdef HAVE_EXEMPI_NEW_API + if (xmp_get_property (xmp, ns, propname, value, &options)) + { +#else + if (xmp_get_property_and_bits (xmp, ns, propname, value, &options)) + { +#endif + if (XMP_IS_PROP_SIMPLE (options)) + { + append_label_take_str + (page->details->vbox, + g_strdup_printf ("<b>%s:</b> %s", + descr, xmp_string_cstr (value))); + } + else if (XMP_IS_PROP_ARRAY (options)) + { + XmpIteratorPtr iter; + + iter = xmp_iterator_new (xmp, ns, propname, XMP_ITER_JUSTLEAFNODES); + if (iter) + { + GString *str; + gboolean first = TRUE; + + str = g_string_new (NULL); + + g_string_append_printf (str, "<b>%s:</b> ", + descr); + while (xmp_iterator_next (iter, NULL, NULL, value, &options) + && !XMP_IS_PROP_QUALIFIER(options)) + { + if (!first) + { + g_string_append_printf (str, ", "); + } + else + { + first = FALSE; + } + g_string_append_printf (str, + "%s", + xmp_string_cstr(value)); + } + xmp_iterator_free(iter); + append_label_take_str (page->details->vbox, + g_string_free (str, FALSE)); + } + } + } + xmp_string_free(value); +} + +static void +append_xmpdata_string (XmpPtr xmp, CajaImagePropertiesPage *page) +{ + if (xmp != NULL) + { + append_xmp_value_pair (page, xmp, NS_IPTC4XMP, "Location", _("Location")); + append_xmp_value_pair (page, xmp, NS_DC, "description", _("Description")); + append_xmp_value_pair (page, xmp, NS_DC, "subject", _("Keywords")); + append_xmp_value_pair (page, xmp, NS_DC, "creator", _("Creator")); + append_xmp_value_pair (page, xmp, NS_DC, "rights", _("Copyright")); + append_xmp_value_pair (page, xmp, NS_XAP,"Rating", _("Rating")); + /* TODO add CC licenses */ + } +} +#endif + +static void +load_finished (CajaImagePropertiesPage *page) +{ + GdkPixbufFormat *format; + char *name, *desc; + + gtk_widget_destroy (page->details->loading_label); + + if (page->details->got_size) + { +#ifdef HAVE_EXIF + ExifData *exif_data; +#endif + + format = gdk_pixbuf_loader_get_format (page->details->loader); + + name = gdk_pixbuf_format_get_name (format); + desc = gdk_pixbuf_format_get_description (format); + append_label_take_str + (page->details->vbox, + g_strdup_printf ("<b>%s</b> %s (%s)", + _("Image Type:"), name, desc)); + append_label_take_str + (page->details->vbox, + g_strdup_printf (ngettext ("<b>Width:</b> %d pixel", + "<b>Width:</b> %d pixels", + page->details->width), + page->details->width)); + append_label_take_str + (page->details->vbox, + g_strdup_printf (ngettext ("<b>Height:</b> %d pixel", + "<b>Height:</b> %d pixels", + page->details->height), + page->details->height)); + g_free (name); + g_free (desc); + +#ifdef HAVE_EXIF + exif_data = exif_loader_get_data (page->details->exifldr); + append_exifdata_string (exif_data, page); + exif_data_unref (exif_data); +#endif /*HAVE_EXIF*/ +#ifdef HAVE_EXEMPI + append_xmpdata_string (page->details->xmp, page); +#endif /*HAVE EXEMPI*/ + } + else + { + append_label (page->details->vbox, + _("Failed to load image information")); + } + + if (page->details->loader != NULL) + { + gdk_pixbuf_loader_close (page->details->loader, NULL); + g_object_unref (page->details->loader); + page->details->loader = NULL; + } +#ifdef HAVE_EXIF + if (page->details->exifldr != NULL) + { + exif_loader_unref (page->details->exifldr); + page->details->exifldr = NULL; + } +#endif /*HAVE_EXIF*/ +#ifdef HAVE_EXEMPI + if (page->details->xmp != NULL) + { + xmp_free(page->details->xmp); + page->details->xmp = NULL; + } +#endif +} + +static void +file_read_callback (GObject *object, + GAsyncResult *res, + gpointer data) +{ + CajaImagePropertiesPage *page; + GInputStream *stream; + gssize count_read; + GError *error; + int exif_still_loading; + gboolean done_reading; + + page = CAJA_IMAGE_PROPERTIES_PAGE (data); + stream = G_INPUT_STREAM (object); + + error = NULL; + done_reading = FALSE; + count_read = g_input_stream_read_finish (stream, res, &error); + + if (count_read > 0) + { + + g_assert (count_read <= sizeof(page->details->buffer)); + +#ifdef HAVE_EXIF + exif_still_loading = exif_loader_write (page->details->exifldr, + page->details->buffer, + count_read); +#else + exif_still_loading = 0; +#endif + + if (page->details->pixbuf_still_loading) + { + if (!gdk_pixbuf_loader_write (page->details->loader, + page->details->buffer, + count_read, + NULL)) + { + page->details->pixbuf_still_loading = FALSE; + } + } + + if (page->details->pixbuf_still_loading || + (exif_still_loading == 1)) + { + g_input_stream_read_async (G_INPUT_STREAM (stream), + page->details->buffer, + sizeof (page->details->buffer), + 0, + page->details->cancellable, + file_read_callback, + page); + } + else + { + done_reading = TRUE; + } + } + else + { + /* either EOF, cancelled or an error occurred */ + done_reading = TRUE; + } + + if (done_reading) + { + load_finished (page); + g_input_stream_close_async (stream, + 0, + page->details->cancellable, + file_close_callback, + page); + } +} + +static void +size_prepared_callback (GdkPixbufLoader *loader, + int width, + int height, + gpointer callback_data) +{ + CajaImagePropertiesPage *page; + + page = CAJA_IMAGE_PROPERTIES_PAGE (callback_data); + + page->details->height = height; + page->details->width = width; + page->details->got_size = TRUE; + page->details->pixbuf_still_loading = FALSE; +} + +static void +file_open_callback (GObject *object, + GAsyncResult *res, + gpointer data) +{ + CajaImagePropertiesPage *page; + GFile *file; + GFileInputStream *stream; + GError *error; + + page = CAJA_IMAGE_PROPERTIES_PAGE (data); + file = G_FILE (object); + + error = NULL; + stream = g_file_read_finish (file, res, &error); + if (stream) + { + page->details->loader = gdk_pixbuf_loader_new (); + page->details->pixbuf_still_loading = TRUE; + page->details->width = 0; + page->details->height = 0; +#ifdef HAVE_EXIF + page->details->exifldr = exif_loader_new (); +#endif /*HAVE_EXIF*/ + + g_signal_connect (page->details->loader, + "size_prepared", + G_CALLBACK (size_prepared_callback), + page); + + g_input_stream_read_async (G_INPUT_STREAM (stream), + page->details->buffer, + sizeof (page->details->buffer), + 0, + page->details->cancellable, + file_read_callback, + page); + + g_object_unref (stream); + } +} + +static void +load_location (CajaImagePropertiesPage *page, + const char *location) +{ + GFile *file; + + g_assert (CAJA_IS_IMAGE_PROPERTIES_PAGE (page)); + g_assert (location != NULL); + + page->details->cancellable = g_cancellable_new (); + file = g_file_new_for_uri (location); + +#ifdef HAVE_EXEMPI + { + /* Current Exempi does not support setting custom IO to be able to use Mate-vfs */ + /* So it will only work with local files. Future version might remove this limitation */ + XmpFilePtr xf; + char *localname; + + localname = g_filename_from_uri (location, NULL, NULL); + if (localname) + { + xf = xmp_files_open_new (localname, 0); + page->details->xmp = xmp_files_get_new_xmp (xf); /* only load when loading */ + xmp_files_close (xf, 0); + g_free (localname); + } + else + { + page->details->xmp = NULL; + } + } +#endif /*HAVE_EXEMPI*/ + + g_file_read_async (file, + 0, + page->details->cancellable, + file_open_callback, + page); + + g_object_unref (file); +} + +static void +caja_image_properties_page_class_init (CajaImagePropertiesPageClass *class) +{ + GObjectClass *object_class; + + object_class = G_OBJECT_CLASS (class); + + object_class->finalize = caja_image_properties_page_finalize; + + g_type_class_add_private (object_class, sizeof(CajaImagePropertiesPageDetails)); +} + +static void +caja_image_properties_page_init (CajaImagePropertiesPage *page) +{ + page->details = G_TYPE_INSTANCE_GET_PRIVATE (page, + CAJA_TYPE_IMAGE_PROPERTIES_PAGE, + CajaImagePropertiesPageDetails); + + gtk_box_set_homogeneous (GTK_BOX (page), FALSE); + gtk_box_set_spacing (GTK_BOX (page), 2); + gtk_container_set_border_width (GTK_CONTAINER (page), 6); + + page->details->vbox = gtk_vbox_new (FALSE, 6); + page->details->loading_label = + append_label (page->details->vbox,_("loading...")); + gtk_box_pack_start (GTK_BOX (page), + page->details->vbox, + FALSE, TRUE, 2); + + gtk_widget_show_all (GTK_WIDGET (page)); +} + +static GList * +get_property_pages (CajaPropertyPageProvider *provider, + GList *files) +{ + GList *pages; + CajaPropertyPage *real_page; + CajaFileInfo *file; + char *uri; + CajaImagePropertiesPage *page; + + /* Only show the property page if 1 file is selected */ + if (!files || files->next != NULL) + { + return NULL; + } + + file = CAJA_FILE_INFO (files->data); + + if (! + (caja_file_info_is_mime_type (file, "image/x-bmp") || + caja_file_info_is_mime_type (file, "image/x-ico") || + caja_file_info_is_mime_type (file, "image/jpeg") || + caja_file_info_is_mime_type (file, "image/gif") || + caja_file_info_is_mime_type (file, "image/png") || + caja_file_info_is_mime_type (file, "image/pnm") || + caja_file_info_is_mime_type (file, "image/ras") || + caja_file_info_is_mime_type (file, "image/tga") || + caja_file_info_is_mime_type (file, "image/tiff") || + caja_file_info_is_mime_type (file, "image/wbmp") || + caja_file_info_is_mime_type (file, "image/x-xbitmap") || + caja_file_info_is_mime_type (file, "image/x-xpixmap"))) + { + return NULL; + } + + pages = NULL; + + uri = caja_file_info_get_uri (file); + + page = g_object_new (caja_image_properties_page_get_type (), NULL); + load_location (page, uri); + + g_free (uri); + + real_page = caja_property_page_new + ("CajaImagePropertiesPage::property_page", + gtk_label_new (_("Image")), + GTK_WIDGET (page)); + pages = g_list_append (pages, real_page); + + return pages; +} + +static void +property_page_provider_iface_init (CajaPropertyPageProviderIface *iface) +{ + iface->get_pages = get_property_pages; +} + + +static void +caja_image_properties_page_provider_init (CajaImagePropertiesPageProvider *sidebar) +{ +} + +static void +caja_image_properties_page_provider_class_init (CajaImagePropertiesPageProviderClass *class) +{ +} + +void +caja_image_properties_page_register (void) +{ + caja_module_add_type (caja_image_properties_page_provider_get_type ()); +} + |