diff options
Diffstat (limited to 'extensions/image-properties/nautilus-image-properties-page.c')
-rw-r--r-- | extensions/image-properties/nautilus-image-properties-page.c | 515 |
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); +} |