summaryrefslogtreecommitdiffstats
path: root/extensions/image-properties/nautilus-image-properties-page.c
diff options
context:
space:
mode:
Diffstat (limited to 'extensions/image-properties/nautilus-image-properties-page.c')
-rw-r--r--extensions/image-properties/nautilus-image-properties-page.c515
1 files changed, 515 insertions, 0 deletions
diff --git a/extensions/image-properties/nautilus-image-properties-page.c b/extensions/image-properties/nautilus-image-properties-page.c
new file mode 100644
index 0000000..bca0b56
--- /dev/null
+++ b/extensions/image-properties/nautilus-image-properties-page.c
@@ -0,0 +1,515 @@
+/* Copyright (C) 2004 Red Hat, Inc
+ * Copyright (c) 2007 Novell, Inc.
+ * Copyright (c) 2017 Thomas Bechtold <thomasbechtold@jpberlin.de>
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Alexander Larsson <alexl@redhat.com>
+ * XMP support by Hubert Figuiere <hfiguiere@novell.com>
+ */
+
+#include "nautilus-image-properties-page.h"
+
+#include <gexiv2/gexiv2.h>
+#include <glib/gi18n.h>
+
+#define LOAD_BUFFER_SIZE 8192
+
+struct _NautilusImagesPropertiesPage
+{
+ GtkGrid parent;
+
+ GCancellable *cancellable;
+ GtkWidget *grid;
+ GdkPixbufLoader *loader;
+ gboolean got_size;
+ gboolean pixbuf_still_loading;
+ unsigned char buffer[LOAD_BUFFER_SIZE];
+ int width;
+ int height;
+
+ GExiv2Metadata *md;
+ gboolean md_ready;
+};
+
+G_DEFINE_TYPE (NautilusImagesPropertiesPage,
+ nautilus_image_properties_page,
+ GTK_TYPE_GRID);
+
+static void
+finalize (GObject *object)
+{
+ NautilusImagesPropertiesPage *page;
+
+ page = NAUTILUS_IMAGE_PROPERTIES_PAGE (object);
+
+ if (page->cancellable != NULL)
+ {
+ g_cancellable_cancel (page->cancellable);
+ g_clear_object (&page->cancellable);
+ }
+
+ G_OBJECT_CLASS (nautilus_image_properties_page_parent_class)->finalize (object);
+}
+
+static void
+nautilus_image_properties_page_class_init (NautilusImagesPropertiesPageClass *klass)
+{
+ GObjectClass *object_class;
+
+ object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = finalize;
+}
+
+static void
+append_item (NautilusImagesPropertiesPage *page,
+ const char *name,
+ const char *value)
+{
+ GtkWidget *name_label;
+ PangoAttrList *attrs;
+
+ name_label = gtk_label_new (name);
+ attrs = pango_attr_list_new ();
+
+ pango_attr_list_insert (attrs, pango_attr_weight_new (PANGO_WEIGHT_BOLD));
+ gtk_label_set_attributes (GTK_LABEL (name_label), attrs);
+ pango_attr_list_unref (attrs);
+ gtk_container_add (GTK_CONTAINER (page->grid), name_label);
+ gtk_widget_set_halign (name_label, GTK_ALIGN_START);
+ gtk_widget_show (name_label);
+
+ if (value != NULL)
+ {
+ GtkWidget *value_label;
+
+ value_label = gtk_label_new (value);
+
+ gtk_label_set_line_wrap (GTK_LABEL (value_label), TRUE);
+ gtk_grid_attach_next_to (GTK_GRID (page->grid), value_label,
+ name_label, GTK_POS_RIGHT,
+ 1, 1);
+ gtk_widget_set_halign (value_label, GTK_ALIGN_START);
+ gtk_widget_set_hexpand (value_label, TRUE);
+ gtk_widget_show (value_label);
+ }
+}
+
+static void
+nautilus_image_properties_page_init (NautilusImagesPropertiesPage *self)
+{
+ GtkWidget *scrolled_window;
+
+ scrolled_window = gtk_scrolled_window_new (NULL, NULL);
+
+ gtk_widget_set_vexpand (GTK_WIDGET (scrolled_window), TRUE);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
+ GTK_POLICY_NEVER,
+ GTK_POLICY_AUTOMATIC);
+
+ gtk_container_add (GTK_CONTAINER (self), scrolled_window);
+
+ self->grid = gtk_grid_new ();
+
+ gtk_orientable_set_orientation (GTK_ORIENTABLE (self->grid), GTK_ORIENTATION_VERTICAL);
+ gtk_grid_set_row_spacing (GTK_GRID (self->grid), 6);
+ gtk_grid_set_column_spacing (GTK_GRID (self->grid), 18);
+ append_item (self, _("Loading…"), NULL);
+ gtk_container_add (GTK_CONTAINER (scrolled_window), self->grid);
+
+ gtk_widget_show_all (GTK_WIDGET (self));
+}
+
+static void
+append_basic_info (NautilusImagesPropertiesPage *page)
+{
+ GdkPixbufFormat *format;
+ g_autofree char *name = NULL;
+ g_autofree char *desc = NULL;
+ g_autofree char *value = NULL;
+
+ format = gdk_pixbuf_loader_get_format (page->loader);
+ name = gdk_pixbuf_format_get_name (format);
+ desc = gdk_pixbuf_format_get_description (format);
+ value = g_strdup_printf ("%s (%s)", name, desc);
+
+ append_item (page, _("Image Type"), value);
+
+ g_free (value);
+ value = g_strdup_printf (ngettext ("%d pixel",
+ "%d pixels",
+ page->width),
+ page->width);
+
+ append_item (page, _("Width"), value);
+
+ g_free (value);
+ value = g_strdup_printf (ngettext ("%d pixel",
+ "%d pixels",
+ page->height),
+ page->height);
+
+ append_item (page, _("Height"), value);
+}
+
+static void
+append_gexiv2_tag (NautilusImagesPropertiesPage *page,
+ const char **tag_names,
+ const char *description)
+{
+ g_assert (tag_names != NULL);
+
+ for (const char **i = tag_names; *i != NULL; i++)
+ {
+ if (gexiv2_metadata_has_tag (page->md, *i))
+ {
+ g_autofree char *tag_value = NULL;
+
+ tag_value = gexiv2_metadata_get_tag_interpreted_string (page->md, *i);
+
+ if (description == NULL)
+ {
+ description = gexiv2_metadata_get_tag_description (*i);
+ }
+
+ /* don't add empty tags - try next one */
+ if (strlen (tag_value) > 0)
+ {
+ append_item (page, description, tag_value);
+ break;
+ }
+ }
+ }
+}
+
+static void
+append_gexiv2_info (NautilusImagesPropertiesPage *page)
+{
+ double longitude;
+ double latitude;
+ double altitude;
+
+ /* define tags and its alternatives */
+ const char *title[] = { "Xmp.dc.title", NULL };
+ const char *camera_brand[] = { "Exif.Image.Make", NULL };
+ const char *camera_model[] = { "Exif.Image.Model", "Exif.Image.UniqueCameraModel", NULL };
+ const char *created_on[] = { "Exif.Photo.DateTimeOriginal", "Xmp.xmp.CreateDate", "Exif.Image.DateTime", NULL };
+ const char *exposure_time[] = { "Exif.Photo.ExposureTime", NULL };
+ const char *aperture_value[] = { "Exif.Photo.ApertureValue", NULL };
+ const char *iso_speed_ratings[] = { "Exif.Photo.ISOSpeedRatings", "Xmp.exifEX.ISOSpeed", NULL };
+ const char *flash[] = { "Exif.Photo.Flash", NULL };
+ const char *metering_mode[] = { "Exif.Photo.MeteringMode", NULL };
+ const char *exposure_mode[] = { "Exif.Photo.ExposureMode", NULL };
+ const char *focal_length[] = { "Exif.Photo.FocalLength", NULL };
+ const char *software[] = { "Exif.Image.Software", NULL };
+ const char *description[] = { "Xmp.dc.description", "Exif.Photo.UserComment", NULL };
+ const char *subject[] = { "Xmp.dc.subject", NULL };
+ const char *creator[] = { "Xmp.dc.creator", "Exif.Image.Artist", NULL };
+ const char *rights[] = { "Xmp.dc.rights", NULL };
+ const char *rating[] = { "Xmp.xmp.Rating", NULL };
+
+ if (!page->md_ready)
+ {
+ return;
+ }
+
+ append_gexiv2_tag (page, camera_brand, _("Camera Brand"));
+ append_gexiv2_tag (page, camera_model, _("Camera Model"));
+ append_gexiv2_tag (page, exposure_time, _("Exposure Time"));
+ append_gexiv2_tag (page, exposure_mode, _("Exposure Program"));
+ append_gexiv2_tag (page, aperture_value, _("Aperture Value"));
+ append_gexiv2_tag (page, iso_speed_ratings, _("ISO Speed Rating"));
+ append_gexiv2_tag (page, flash, _("Flash Fired"));
+ append_gexiv2_tag (page, metering_mode, _("Metering Mode"));
+ append_gexiv2_tag (page, focal_length, _("Focal Length"));
+ append_gexiv2_tag (page, software, _("Software"));
+ append_gexiv2_tag (page, title, _("Title"));
+ append_gexiv2_tag (page, description, _("Description"));
+ append_gexiv2_tag (page, subject, _("Keywords"));
+ append_gexiv2_tag (page, creator, _("Creator"));
+ append_gexiv2_tag (page, created_on, _("Created On"));
+ append_gexiv2_tag (page, rights, _("Copyright"));
+ append_gexiv2_tag (page, rating, _("Rating"));
+
+ if (gexiv2_metadata_get_gps_info (page->md, &longitude, &latitude, &altitude))
+ {
+ g_autofree char *gps_coords = NULL;
+
+ /* Translators: These are the coordinates of a position where a picture was taken. */
+ gps_coords = g_strdup_printf (_("%f N / %f W (%.0f m)"), latitude, longitude, altitude);
+
+ append_item (page, _("Coordinates"), gps_coords);
+ }
+}
+
+static void
+load_finished (NautilusImagesPropertiesPage *page)
+{
+ GtkWidget *label;
+
+ label = gtk_grid_get_child_at (GTK_GRID (page->grid), 0, 0);
+ gtk_container_remove (GTK_CONTAINER (page->grid), label);
+
+ if (page->loader != NULL)
+ {
+ gdk_pixbuf_loader_close (page->loader, NULL);
+ }
+
+ if (page->got_size)
+ {
+ append_basic_info (page);
+ append_gexiv2_info (page);
+ }
+ else
+ {
+ append_item (page, _("Failed to load image information"), NULL);
+ }
+
+ if (page->loader != NULL)
+ {
+ g_object_unref (page->loader);
+ page->loader = NULL;
+ }
+ page->md_ready = FALSE;
+ g_clear_object (&page->md);
+}
+
+static void
+file_close_callback (GObject *object,
+ GAsyncResult *res,
+ gpointer data)
+{
+ NautilusImagesPropertiesPage *page;
+ GInputStream *stream;
+
+ page = data;
+ stream = G_INPUT_STREAM (object);
+
+ g_input_stream_close_finish (stream, res, NULL);
+
+ g_clear_object (&page->cancellable);
+}
+
+static void
+file_read_callback (GObject *object,
+ GAsyncResult *res,
+ gpointer data)
+{
+ NautilusImagesPropertiesPage *page;
+ GInputStream *stream;
+ g_autoptr (GError) error = NULL;
+ gssize count_read;
+ gboolean done_reading;
+
+ page = data;
+ stream = G_INPUT_STREAM (object);
+ count_read = g_input_stream_read_finish (stream, res, &error);
+ done_reading = FALSE;
+
+ if (count_read > 0)
+ {
+ g_assert (count_read <= sizeof (page->buffer));
+
+ if (page->pixbuf_still_loading)
+ {
+ if (!gdk_pixbuf_loader_write (page->loader,
+ page->buffer,
+ count_read,
+ NULL))
+ {
+ page->pixbuf_still_loading = FALSE;
+ }
+ }
+
+ if (page->pixbuf_still_loading)
+ {
+ g_input_stream_read_async (G_INPUT_STREAM (stream),
+ page->buffer,
+ sizeof (page->buffer),
+ G_PRIORITY_DEFAULT,
+ page->cancellable,
+ file_read_callback,
+ page);
+ }
+ else
+ {
+ done_reading = TRUE;
+ }
+ }
+ else
+ {
+ /* either EOF, cancelled or an error occurred */
+ done_reading = TRUE;
+ }
+
+ if (error != NULL)
+ {
+ g_autofree char *uri = NULL;
+
+ uri = g_file_get_uri (G_FILE (object));
+
+ g_warning ("Error reading %s: %s", uri, error->message);
+ }
+
+ if (done_reading)
+ {
+ load_finished (page);
+ g_input_stream_close_async (stream,
+ G_PRIORITY_DEFAULT,
+ page->cancellable,
+ file_close_callback,
+ page);
+ }
+}
+
+static void
+size_prepared_callback (GdkPixbufLoader *loader,
+ int width,
+ int height,
+ gpointer callback_data)
+{
+ NautilusImagesPropertiesPage *page;
+
+ page = callback_data;
+
+ page->height = height;
+ page->width = width;
+ page->got_size = TRUE;
+ page->pixbuf_still_loading = FALSE;
+}
+
+typedef struct
+{
+ NautilusImagesPropertiesPage *page;
+ NautilusFileInfo *file_info;
+} FileOpenData;
+
+static void
+file_open_callback (GObject *object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ g_autofree FileOpenData *data = NULL;
+ NautilusImagesPropertiesPage *page;
+ GFile *file;
+ g_autofree char *uri = NULL;
+ g_autoptr (GError) error = NULL;
+ g_autoptr (GFileInputStream) stream = NULL;
+
+ data = user_data;
+ page = data->page;
+ file = G_FILE (object);
+ uri = g_file_get_uri (file);
+ stream = g_file_read_finish (file, res, &error);
+ if (stream != NULL)
+ {
+ g_autofree char *mime_type = NULL;
+
+ mime_type = nautilus_file_info_get_mime_type (data->file_info);
+
+ page->loader = gdk_pixbuf_loader_new_with_mime_type (mime_type, &error);
+ if (error != NULL)
+ {
+ g_warning ("Error creating loader for %s: %s", uri, error->message);
+ }
+ page->pixbuf_still_loading = TRUE;
+ page->width = 0;
+ page->height = 0;
+
+ g_signal_connect (page->loader,
+ "size-prepared",
+ G_CALLBACK (size_prepared_callback),
+ page);
+
+ g_input_stream_read_async (G_INPUT_STREAM (stream),
+ page->buffer,
+ sizeof (page->buffer),
+ G_PRIORITY_DEFAULT,
+ page->cancellable,
+ file_read_callback,
+ page);
+ }
+ else
+ {
+ g_warning ("Error reading %s: %s", uri, error->message);
+ load_finished (page);
+ }
+}
+
+void
+nautilus_image_properties_page_load_from_file_info (NautilusImagesPropertiesPage *self,
+ NautilusFileInfo *file_info)
+{
+ g_autofree char *uri = NULL;
+ g_autoptr (GFile) file = NULL;
+ g_autofree char *path = NULL;
+ FileOpenData *data;
+
+ g_return_if_fail (NAUTILUS_IS_IMAGE_PROPERTIES_PAGE (self));
+ g_return_if_fail (file_info != NULL);
+
+ self->cancellable = g_cancellable_new ();
+
+ uri = nautilus_file_info_get_uri (file_info);
+ file = g_file_new_for_uri (uri);
+ path = g_file_get_path (file);
+
+ /* gexiv2 metadata init */
+ self->md_ready = gexiv2_initialize ();
+ if (!self->md_ready)
+ {
+ g_warning ("Unable to initialize gexiv2");
+ }
+ else
+ {
+ self->md = gexiv2_metadata_new ();
+ if (path != NULL)
+ {
+ g_autoptr (GError) error = NULL;
+
+ if (!gexiv2_metadata_open_path (self->md, path, &error))
+ {
+ g_warning ("gexiv2 metadata not supported for '%s': %s", path, error->message);
+ self->md_ready = FALSE;
+ }
+ }
+ else
+ {
+ self->md_ready = FALSE;
+ }
+ }
+
+ data = g_new0 (FileOpenData, 1);
+
+ data->page = self;
+ data->file_info = file_info;
+
+ g_file_read_async (file,
+ G_PRIORITY_DEFAULT,
+ self->cancellable,
+ file_open_callback,
+ data);
+}
+
+NautilusImagesPropertiesPage *
+nautilus_image_properties_page_new (void)
+{
+ return g_object_new (NAUTILUS_TYPE_IMAGE_PROPERTIES_PAGE,
+ "margin-bottom", 6,
+ "margin-end", 12,
+ "margin-start", 12,
+ "margin-top", 6,
+ NULL);
+}