/* Copyright (C) 2004 Red Hat, Inc * Copyright (c) 2007 Novell, Inc. * Copyright (c) 2017 Thomas Bechtold * * 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 . * * Author: Alexander Larsson * XMP support by Hubert Figuiere */ #include "nautilus-image-properties-page.h" #include #include #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); }