]> www.fi.muni.cz Git - evince.git/commitdiff
Add image handling support. Fixes bugs #310008 and #325047. Images
authorCarlos Garcia Campos <carlosgc@gnome.org>
Sun, 7 Jan 2007 16:28:00 +0000 (16:28 +0000)
committerCarlos Garcia Campos <carlosgc@src.gnome.org>
Sun, 7 Jan 2007 16:28:00 +0000 (16:28 +0000)
2007-01-07  Carlos Garcia Campos  <carlosgc@gnome.org>
* configure.ac:
* data/evince-ui.xml:
* pdf/ev-poppler.cc: (pdf_document_images_get_images),
(pdf_document_document_images_iface_init):
* backend/Makefile.am:
* backend/ev-document-images.[ch]:
* backend/ev-image.[ch]:
* lib/ev-file-helpers.[ch]: (ev_tmp_filename):
* shell/ev-jobs.[ch]: (ev_job_render_new), (ev_job_render_run),
(ev_job_xfer_run):
* shell/ev-pixbuf-cache.[ch]: (dispose_cache_job_info),
(move_one_job), (copy_job_to_job_info), (add_job_if_needed),
(ev_pixbuf_cache_get_image_mapping):
* shell/ev-window.c: (view_menu_link_popup), (view_menu_image_popup),
(view_menu_popup_cb), (ev_window_dispose),
(image_save_dialog_response_cb), (ev_view_popup_cmd_save_image_as),
(ev_view_popup_cmd_copy_image):
* shell/ev-view-private.h:
* shell/ev-view.c: (ev_view_get_image_at_location),
(ev_view_do_popup_menu), (ev_view_popup_menu),
(ev_view_button_press_event), (ev_view_drag_data_get),
(ev_view_drag_motion), (ev_view_drag_data_received),
(ev_view_motion_notify_event), (ev_view_button_release_event),
(ev_view_finalize), (ev_view_class_init):
Add image handling support. Fixes bugs #310008 and #325047. Images
selection is not supported yet.

svn path=/trunk/; revision=2194

18 files changed:
ChangeLog
backend/Makefile.am
backend/ev-document-images.c [new file with mode: 0644]
backend/ev-document-images.h [new file with mode: 0644]
backend/ev-image.c [new file with mode: 0644]
backend/ev-image.h [new file with mode: 0644]
configure.ac
data/evince-ui.xml
lib/ev-file-helpers.c
lib/ev-file-helpers.h
pdf/ev-poppler.cc
shell/ev-jobs.c
shell/ev-jobs.h
shell/ev-pixbuf-cache.c
shell/ev-pixbuf-cache.h
shell/ev-view-private.h
shell/ev-view.c
shell/ev-window.c

index b37077a999aa83b1f3d8a42d69931f1998812793..a4d8056bc8170d046f2b17705cf21a07c642421c 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,33 @@
+2007-01-07  Carlos Garcia Campos  <carlosgc@gnome.org>
+
+       * configure.ac:
+       * data/evince-ui.xml:
+       * pdf/ev-poppler.cc: (pdf_document_images_get_images),
+       (pdf_document_document_images_iface_init):
+       * backend/Makefile.am:
+       * backend/ev-document-images.[ch]:
+       * backend/ev-image.[ch]:
+       * lib/ev-file-helpers.[ch]: (ev_tmp_filename):
+       * shell/ev-jobs.[ch]: (ev_job_render_new), (ev_job_render_run),
+       (ev_job_xfer_run):
+       * shell/ev-pixbuf-cache.[ch]: (dispose_cache_job_info),
+       (move_one_job), (copy_job_to_job_info), (add_job_if_needed),
+       (ev_pixbuf_cache_get_image_mapping):
+       * shell/ev-window.c: (view_menu_link_popup), (view_menu_image_popup),
+       (view_menu_popup_cb), (ev_window_dispose),
+       (image_save_dialog_response_cb), (ev_view_popup_cmd_save_image_as),
+       (ev_view_popup_cmd_copy_image):
+       * shell/ev-view-private.h:
+       * shell/ev-view.c: (ev_view_get_image_at_location),
+       (ev_view_do_popup_menu), (ev_view_popup_menu),
+       (ev_view_button_press_event), (ev_view_drag_data_get),
+       (ev_view_drag_motion), (ev_view_drag_data_received),
+       (ev_view_motion_notify_event), (ev_view_button_release_event),
+       (ev_view_finalize), (ev_view_class_init):
+
+       Add image handling support. Fixes bugs #310008 and #325047. Images
+       selection is not supported yet. 
+
 2007-01-07  Carlos Garcia Campos  <carlosgc@gnome.org>
 
        * shell/ev-window.c: (drag_data_received_cb), (ev_window_init):
index 54dbd2cabcf3db0bbb4a9370406df94258291a8d..a09734d9cd99f7beb526ac5df3e2e51f55a6a7f5 100644 (file)
@@ -28,6 +28,8 @@ libevbackend_la_SOURCES=                      \
        ev-link-action.h                        \
        ev-link-dest.c                          \
        ev-link-dest.h                          \
+       ev-image.c                              \
+       ev-image.h                              \
        ev-document.c                           \
        ev-document.h                           \
        ev-document-factory.c                   \
@@ -38,6 +40,8 @@ libevbackend_la_SOURCES=                      \
        ev-document-fonts.h                     \
        ev-document-links.c                     \
        ev-document-links.h                     \
+       ev-document-images.c                    \
+       ev-document-images.h                    \
        ev-document-security.c                  \
        ev-document-security.h                  \
        ev-document-find.c                      \
diff --git a/backend/ev-document-images.c b/backend/ev-document-images.c
new file mode 100644 (file)
index 0000000..117b104
--- /dev/null
@@ -0,0 +1,55 @@
+/* ev-document-images.c
+ *  this file is part of evince, a gnome document_links viewer
+ * 
+ * Copyright (C) 2006 Carlos Garcia Campos <carlosgc@gnome.org>
+ *
+ * Evince 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.
+ *
+ * Evince 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 "ev-document-images.h"
+
+GType
+ev_document_images_get_type (void)
+{
+       static GType type = 0;
+
+       if (G_UNLIKELY (type == 0)) {
+               const GTypeInfo our_info = {
+                       sizeof (EvDocumentImagesIface),
+                       NULL,
+                       NULL,
+               };
+
+               type = g_type_register_static (G_TYPE_INTERFACE,
+                                              "EvDocumentImages",
+                                              &our_info, (GTypeFlags)0);
+       }
+
+       return type;
+}
+
+GList *
+ev_document_images_get_images (EvDocumentImages *document_images,
+                              gint              page)
+{
+       EvDocumentImagesIface *iface = EV_DOCUMENT_IMAGES_GET_IFACE (document_images);
+       GList *retval;
+
+       retval = iface->get_images (document_images, page);
+
+       return retval;
+}
+
+
diff --git a/backend/ev-document-images.h b/backend/ev-document-images.h
new file mode 100644 (file)
index 0000000..28eee46
--- /dev/null
@@ -0,0 +1,55 @@
+/* ev-document-images.h
+ *  this file is part of evince, a gnome document viewer
+ * 
+ * Copyright (C) 2006 Carlos Garcia Campos <carlosgc@gnome.org>
+ *
+ * Evince 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.
+ *
+ * Evince 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 EV_DOCUMENT_IMAGES_H
+#define EV_DOCUMENT_IMAGES_H
+
+#include <glib-object.h>
+#include <glib.h>
+
+#include "ev-document.h"
+
+G_BEGIN_DECLS
+
+#define EV_TYPE_DOCUMENT_IMAGES            (ev_document_images_get_type ())
+#define EV_DOCUMENT_IMAGES(o)              (G_TYPE_CHECK_INSTANCE_CAST ((o), EV_TYPE_DOCUMENT_IMAGES, EvDocumentImages))
+#define EV_DOCUMENT_IMAGES_IFACE(k)        (G_TYPE_CHECK_CLASS_CAST((k), EV_TYPE_DOCUMENT_IMAGES, EvDocumentImagesIface))
+#define EV_IS_DOCUMENT_IMAGES(o)           (G_TYPE_CHECK_INSTANCE_TYPE ((o), EV_TYPE_DOCUMENT_IMAGES))
+#define EV_IS_DOCUMENT_IMAGES_IFACE(k)     (G_TYPE_CHECK_CLASS_TYPE ((k), EV_TYPE_DOCUMENT_IMAGES))
+#define EV_DOCUMENT_IMAGES_GET_IFACE(inst) (G_TYPE_INSTANCE_GET_INTERFACE ((inst), EV_TYPE_DOCUMENT_IMAGES, EvDocumentImagesIface))
+
+typedef struct _EvDocumentImages      EvDocumentImages;
+typedef struct _EvDocumentImagesIface EvDocumentImagesIface;
+
+struct _EvDocumentImagesIface {
+        GTypeInterface base_iface;
+
+        /* Methods  */
+        GList *(* get_images) (EvDocumentImages *document_images,
+                               gint              page);
+};
+
+GType  ev_document_images_get_type   (void) G_GNUC_CONST;
+GList *ev_document_images_get_images (EvDocumentImages *document_images,
+                                      gint              page);
+
+G_END_DECLS
+
+#endif /* EV_DOCUMENT_IMAGES_H */
diff --git a/backend/ev-image.c b/backend/ev-image.c
new file mode 100644 (file)
index 0000000..f906b00
--- /dev/null
@@ -0,0 +1,167 @@
+/* this file is part of evince, a gnome document viewer
+ *
+ *  Copyright (C) 2006 Carlos Garcia Campos <carlosgc@gnome.org>
+ *
+ * Evince 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.
+ *
+ * Evince 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 <glib/gstdio.h>
+#include "ev-file-helpers.h"
+#include "ev-image.h"
+
+struct _EvImagePrivate {
+       GdkPixbuf *pixbuf;
+       gchar     *tmp_uri;
+};
+
+#define EV_IMAGE_GET_PRIVATE(object) \
+                (G_TYPE_INSTANCE_GET_PRIVATE ((object), EV_TYPE_IMAGE, EvImagePrivate))
+
+G_DEFINE_TYPE (EvImage, ev_image, G_TYPE_OBJECT)
+
+static void
+ev_image_finalize (GObject *object)
+{
+       EvImage *image = EV_IMAGE (object);
+
+       if (image->priv->pixbuf) {
+               g_object_unref (image->priv->pixbuf);
+               image->priv->pixbuf = NULL;
+       }
+
+       if (image->priv->tmp_uri) {
+               g_unlink (image->priv->tmp_uri);
+               g_free (image->priv->tmp_uri);
+               image->priv->tmp_uri = NULL;
+       }
+
+       (* G_OBJECT_CLASS (ev_image_parent_class)->finalize) (object);
+}
+
+static void
+ev_image_class_init (EvImageClass *klass)
+{
+       GObjectClass *g_object_class;
+
+       g_object_class = G_OBJECT_CLASS (klass);
+
+       g_type_class_add_private (g_object_class, sizeof (EvImagePrivate));
+
+       g_object_class->finalize = ev_image_finalize;
+}
+
+static void
+ev_image_init (EvImage *image)
+{
+       image->priv = EV_IMAGE_GET_PRIVATE (image);
+}
+
+EvImage *
+ev_image_new_from_pixbuf (GdkPixbuf *pixbuf)
+{
+       EvImage *image;
+
+       g_return_val_if_fail (GDK_IS_PIXBUF (pixbuf), NULL);
+
+       image = EV_IMAGE (g_object_new (EV_TYPE_IMAGE, NULL));
+       image->priv->pixbuf = g_object_ref (pixbuf);
+
+       return image;
+}
+
+GdkPixbuf *
+ev_image_get_pixbuf (EvImage *image)
+{
+       g_return_val_if_fail (EV_IS_IMAGE (image), NULL);
+       g_return_val_if_fail (GDK_IS_PIXBUF (image->priv->pixbuf), NULL);
+
+       return image->priv->pixbuf;
+}
+
+const gchar *
+ev_image_save_tmp (EvImage *image)
+{
+       GError *error = NULL;
+       
+       g_return_val_if_fail (EV_IS_IMAGE (image), NULL);
+       g_return_val_if_fail (GDK_IS_PIXBUF (image->priv->pixbuf), NULL);
+
+       if (image->priv->tmp_uri)
+               return image->priv->tmp_uri;
+
+       image->priv->tmp_uri = ev_tmp_filename ("image");
+       gdk_pixbuf_save (image->priv->pixbuf,
+                        image->priv->tmp_uri, "png", &error,
+                        "compression", "3", NULL);
+       if (!error)
+               return image->priv->tmp_uri;
+
+       /* Erro saving image */
+       g_warning (error->message);
+       g_error_free (error);
+       g_free (image->priv->tmp_uri);
+       image->priv->tmp_uri = NULL;
+
+       return NULL;
+}
+
+const gchar *
+ev_image_get_tmp_uri (EvImage *image)
+{
+       g_return_val_if_fail (EV_IS_IMAGE (image), NULL);
+
+       return image->priv->tmp_uri;
+}
+
+/* EvImageMapping */
+static void
+ev_image_mapping_free_foreach (EvImageMapping *mapping)
+{
+       g_object_unref (mapping->image);
+       g_free (mapping);
+}
+
+void
+ev_image_mapping_free (GList *image_mapping)
+{
+       if (!image_mapping)
+               return;
+
+       g_list_foreach (image_mapping, (GFunc) ev_image_mapping_free_foreach, NULL);
+       g_list_free (image_mapping);
+}
+
+EvImage *
+ev_image_mapping_find (GList   *image_mapping,
+                      gdouble  x,
+                      gdouble  y)
+{
+       GList *list;
+
+       for (list = image_mapping; list; list = list->next) {
+               EvImageMapping *mapping = list->data;
+
+               if ((x >= mapping->x1) &&
+                   (y >= mapping->y1) &&
+                   (x <= mapping->x2) &&
+                   (y <= mapping->y2)) {
+                       return mapping->image;
+               }
+       }
+
+       return NULL;
+}
+
+
diff --git a/backend/ev-image.h b/backend/ev-image.h
new file mode 100644 (file)
index 0000000..6688e7a
--- /dev/null
@@ -0,0 +1,74 @@
+/* this file is part of evince, a gnome document viewer
+ *
+ *  Copyright (C) 2006 Carlos Garcia Campos <carlosgc@gnome.org>
+ *
+ * Evince 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.
+ *
+ * Evince 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 __EV_IMAGE_H__
+#define __EV_IMAGE_H__
+
+#include <glib-object.h>
+#include <gdk/gdkpixbuf.h>
+
+G_BEGIN_DECLS
+
+typedef struct _EvImage        EvImage;
+typedef struct _EvImageClass   EvImageClass;
+typedef struct _EvImagePrivate EvImagePrivate;
+
+#define EV_TYPE_IMAGE              (ev_image_get_type())
+#define EV_IMAGE(object)           (G_TYPE_CHECK_INSTANCE_CAST((object), EV_TYPE_IMAGE, EvImage))
+#define EV_IMAGE_CLASS(klass)      (G_TYPE_CHECK_CLASS_CAST((klass), EV_TYPE_IMAGE, EvImageClass))
+#define EV_IS_IMAGE(object)        (G_TYPE_CHECK_INSTANCE_TYPE((object), EV_TYPE_IMAGE))
+#define EV_IS_IMAGE_CLASS(klass)   (G_TYPE_CHECK_CLASS_TYPE((klass), EV_TYPE_IMAGE))
+#define EV_IMAGE_GET_CLASS(object) (G_TYPE_INSTANCE_GET_CLASS((object), EV_TYPE_IMAGE, EvImageClass))
+
+struct _EvImage {
+       GObject base_instance;
+       
+       EvImagePrivate *priv;
+};
+
+struct _EvImageClass {
+       GObjectClass base_class;
+};
+
+GType        ev_image_get_type        (void) G_GNUC_CONST;
+EvImage     *ev_image_new_from_pixbuf (GdkPixbuf *pixbuf);
+
+GdkPixbuf   *ev_image_get_pixbuf      (EvImage   *image);
+const gchar *ev_image_save_tmp        (EvImage   *image);
+const gchar *ev_image_get_tmp_uri     (EvImage   *image);
+
+
+/* Image Mapping stuff */
+typedef struct _EvImageMapping EvImageMapping;
+struct _EvImageMapping {
+       EvImage *image;
+       gdouble x1;
+       gdouble y1;
+       gdouble x2;
+       gdouble y2;
+};
+
+void     ev_image_mapping_free (GList   *image_mapping);
+EvImage *ev_image_mapping_find (GList   *image_mapping,
+                               gdouble  x,
+                               gdouble  y);
+
+G_END_DECLS
+
+#endif /* __EV_IMAGE_H__ */
index cfd8d66cdefd83023395ac5523c0c6ba96921f06..aa7fd7d028b59072c5aa5d6f24d2763ad11f21d9 100644 (file)
@@ -212,6 +212,7 @@ if test "x$enable_pdf" = "xyes"; then
            LIBS="$LIBS $FRONTEND_LIBS"
            AC_CHECK_FUNCS(poppler_page_render)
            AC_CHECK_FUNCS(poppler_page_get_duration)
+           AC_CHECK_FUNCS(poppler_page_get_image_mapping)
            LIBS=$evince_save_LIBS
 
            PKG_CHECK_MODULES(CAIRO_PDF, cairo-pdf, enable_cairo_pdf=yes, enable_cairo_pdf=no)
index f7d411e3fa0cd9b0169980cddb6296da21ab6c7a..fba8ac229af94677a5a6073fc4c01bc451746927 100644 (file)
@@ -73,6 +73,9 @@
     <separator/>
     <menuitem name="EditCopy" action="EditCopy"/>
     <menuitem name="EditSelectAllPopup" action="EditSelectAll"/>
+    <separator/>
+    <menuitem name="SaveImageAs" action="SaveImageAs"/>
+    <menuitem name="CopyImage" action="CopyImage"/>
   </popup>
 
   <popup name="AttachmentPopup" action="AttachmentPopupAction">
index 9763831d783bab59e3fafdebf48d14d8f1872bed..044aeaeee2c4a5bf164b4ecec032d41c7ca65c73 100644 (file)
@@ -113,7 +113,7 @@ ev_file_helpers_shutdown (void)
 }
 
 gchar * 
-ev_tmp_filename (void)
+ev_tmp_filename (const gchar *prefix)
 {
        gchar *basename;
        gchar *filename = NULL;
@@ -122,7 +122,9 @@ ev_tmp_filename (void)
                if (filename != NULL)
                        g_free (filename);
                        
-               basename = g_strdup_printf ("document-%d", count ++);
+               basename = g_strdup_printf ("%s-%d",
+                                           prefix ? prefix : "document",
+                                           count ++);
                
                filename = g_build_filename (ev_tmp_dir (),
                                             basename, NULL);
index 69ff83ddbf4497c97536909b956895737e4627ee..4e75a14cf8de7a22968db4ca5aac1fad7b858cd1 100644 (file)
@@ -33,7 +33,7 @@ void         ev_file_helpers_init     (void);
 
 void         ev_file_helpers_shutdown (void);
 
-gchar*       ev_tmp_filename          (void);
+gchar*       ev_tmp_filename          (const char *prefix);
 
 gboolean     ev_xfer_uri_simple       (const char *from,
                                       const char *to,
index 479a741bb887e4bffb3de39df86f1151d75e3235..2068998ecded64b76070ec760f8db0379cbd074b 100644 (file)
 #include "ev-document-find.h"
 #include "ev-document-misc.h"
 #include "ev-document-links.h"
+#include "ev-document-images.h"
 #include "ev-document-fonts.h"
 #include "ev-document-security.h"
 #include "ev-document-thumbnails.h"
 #include "ev-document-transition.h"
 #include "ev-selection.h"
 #include "ev-attachment.h"
+#include "ev-image.h"
 
 typedef struct {
        PdfDocument *document;
@@ -83,6 +85,7 @@ static void pdf_document_document_iface_init            (EvDocumentIface
 static void pdf_document_security_iface_init            (EvDocumentSecurityIface   *iface);
 static void pdf_document_document_thumbnails_iface_init (EvDocumentThumbnailsIface *iface);
 static void pdf_document_document_links_iface_init      (EvDocumentLinksIface      *iface);
+static void pdf_document_document_images_iface_init     (EvDocumentImagesIface     *iface);
 static void pdf_document_document_fonts_iface_init      (EvDocumentFontsIface      *iface);
 static void pdf_document_find_iface_init                (EvDocumentFindIface       *iface);
 static void pdf_document_file_exporter_iface_init       (EvFileExporterIface       *iface);
@@ -113,6 +116,8 @@ G_DEFINE_TYPE_WITH_CODE (PdfDocument, pdf_document, G_TYPE_OBJECT,
                                                        pdf_document_document_thumbnails_iface_init);
                                 G_IMPLEMENT_INTERFACE (EV_TYPE_DOCUMENT_LINKS,
                                                        pdf_document_document_links_iface_init);
+                                G_IMPLEMENT_INTERFACE (EV_TYPE_DOCUMENT_IMAGES,
+                                                       pdf_document_document_images_iface_init);
                                 G_IMPLEMENT_INTERFACE (EV_TYPE_DOCUMENT_FONTS,
                                                        pdf_document_document_fonts_iface_init);
                                 G_IMPLEMENT_INTERFACE (EV_TYPE_DOCUMENT_FIND,
@@ -1123,6 +1128,50 @@ pdf_document_document_links_iface_init (EvDocumentLinksIface *iface)
        iface->find_link_dest = pdf_document_links_find_link_dest;
 }
 
+static GList *
+pdf_document_images_get_images (EvDocumentImages *document_images,
+                               gint              page)
+{
+       GList *retval = NULL;
+#ifdef HAVE_POPPLER_PAGE_GET_IMAGE_MAPPING
+       PdfDocument *pdf_document;
+       PopplerPage *poppler_page;
+       GList *mapping_list;
+       GList *list;
+
+       pdf_document = PDF_DOCUMENT (document_images);
+       poppler_page = poppler_document_get_page (pdf_document->document, page);
+       mapping_list = poppler_page_get_image_mapping (poppler_page);
+
+       for (list = mapping_list; list; list = list->next) {
+               PopplerImageMapping *image_mapping;
+               EvImageMapping *ev_image_mapping;
+
+               image_mapping = (PopplerImageMapping *)list->data;
+
+               ev_image_mapping = g_new (EvImageMapping, 1);
+               
+               ev_image_mapping->image = ev_image_new_from_pixbuf (image_mapping->image);
+               ev_image_mapping->x1 = image_mapping->area.x1;
+               ev_image_mapping->x2 = image_mapping->area.x2;
+               ev_image_mapping->y1 = image_mapping->area.y1;
+               ev_image_mapping->y2 = image_mapping->area.y2;
+
+               retval = g_list_prepend (retval, ev_image_mapping);
+       }
+
+       poppler_page_free_image_mapping (mapping_list);
+       g_object_unref (poppler_page);
+#endif /* HAVE_POPPLER_PAGE_GET_IMAGE_MAPPING */
+       return retval;
+}
+
+static void
+pdf_document_document_images_iface_init (EvDocumentImagesIface *iface)
+{
+       iface->get_images = pdf_document_images_get_images;
+}
+
 static GdkPixbuf *
 make_thumbnail_for_size (PdfDocument   *pdf_document,
                         gint           page,
index e54812c27d131a295956efcb35cc7003ddf973e1..21beaeab0f053e9597f521a78263f6473b9cd852 100644 (file)
@@ -2,6 +2,7 @@
 #include "ev-job-queue.h"
 #include "ev-document-thumbnails.h"
 #include "ev-document-links.h"
+#include "ev-document-images.h"
 #include "ev-document-factory.h"
 #include "ev-file-helpers.h"
 #include "ev-document-fonts.h"
@@ -260,6 +261,7 @@ ev_job_render_new (EvDocument      *document,
                   GdkColor        *text,
                   GdkColor        *base,
                   gboolean         include_links,
+                  gboolean         include_images,
                   gboolean         include_text,
                   gboolean         include_selection)
 {
@@ -278,6 +280,7 @@ ev_job_render_new (EvDocument      *document,
        job->text = *text;
        job->base = *base;
        job->include_links = include_links;
+       job->include_images = include_images;
        job->include_text = include_text;
        job->include_selection = include_selection;
 
@@ -323,6 +326,10 @@ ev_job_render_run (EvJobRender *job)
                        job->link_mapping =
                                ev_document_links_get_links (EV_DOCUMENT_LINKS (EV_JOB (job)->document),
                                                             job->rc->page);
+               if (job->include_images && EV_IS_DOCUMENT_IMAGES (EV_JOB (job)->document))
+                       job->image_mapping =
+                               ev_document_images_get_images (EV_DOCUMENT_IMAGES (EV_JOB (job)->document),
+                                                              job->rc->page);
                if (job->include_text && EV_IS_SELECTION (EV_JOB (job)->document))
                        job->text_mapping =
                                ev_selection_get_selection_map (EV_SELECTION (EV_JOB (job)->document),
@@ -507,7 +514,7 @@ ev_job_xfer_run (EvJobXfer *job)
                /* We'd like to keep extension of source uri since
                 * it helps to resolve some mime types, say cbz */
                
-               tmp_name = ev_tmp_filename ();
+               tmp_name = ev_tmp_filename (NULL);
                base_name = gnome_vfs_uri_extract_short_name (source_uri);
                job->local_uri = g_strconcat ("file:", tmp_name, "-", base_name, NULL);
                g_free (base_name);
index 46a793862d50588e306433831ffad2ec3c76afea..bf37b673ea75597d9ab304c97c29dd29dca866ab 100644 (file)
@@ -125,6 +125,7 @@ struct _EvJobRender
 
        GList *link_mapping;
        GdkRegion *text_mapping;
+       GList *image_mapping;
 
        GdkPixbuf *selection;
        GdkRegion *selection_region;
@@ -135,6 +136,7 @@ struct _EvJobRender
        gint include_links : 1;
        gint include_text : 1;
        gint include_selection : 1;
+       gint include_images : 1;
 };
 
 struct _EvJobRenderClass
@@ -224,6 +226,7 @@ EvJob          *ev_job_render_new         (EvDocument      *document,
                                           GdkColor        *text,
                                           GdkColor        *base,
                                           gboolean         include_links,
+                                          gboolean         include_images,
                                           gboolean         include_text,
                                           gboolean         include_selection);
 void            ev_job_render_run         (EvJobRender     *thumbnail);
index 630bdde5bfbebc70cd552ff7681e4c33eb23034b..885c11a5f3c8d6c4fbf3d8221a4c3f35abbf937b 100644 (file)
@@ -2,6 +2,8 @@
 #include "ev-job-queue.h"
 #include "ev-page-cache.h"
 #include "ev-selection.h"
+#include "ev-document-images.h"
+#include "ev-image.h"
 
 typedef struct _CacheJobInfo
 {
@@ -11,6 +13,7 @@ typedef struct _CacheJobInfo
        /* Data we get from rendering */
        GdkPixbuf *pixbuf;
        GList *link_mapping;
+       GList *image_mapping;
        GdkRegion *text_mapping;
        
        /* Selection data. 
@@ -152,6 +155,10 @@ dispose_cache_job_info (CacheJobInfo *job_info,
                ev_link_mapping_free (job_info->link_mapping);
                job_info->link_mapping = NULL;
        }
+       if (job_info->image_mapping) {
+               ev_image_mapping_free (job_info->image_mapping);
+               job_info->image_mapping = NULL;
+       }
        if (job_info->text_mapping) {
                gdk_region_destroy (job_info->text_mapping);
                job_info->text_mapping = NULL;
@@ -312,6 +319,7 @@ move_one_job (CacheJobInfo  *job_info,
        job_info->job = NULL;
        job_info->pixbuf = NULL;
        job_info->link_mapping = NULL;
+       job_info->image_mapping = NULL;
 
        if (new_priority != priority && target_page->job) {
                ev_job_queue_update_job (target_page->job, new_priority);
@@ -415,15 +423,19 @@ copy_job_to_job_info (EvJobRender   *job_render,
                job_info->link_mapping = job_render->link_mapping;
        }
 
+       if (job_render->include_images) {
+               if (job_info->image_mapping)
+                       ev_image_mapping_free (job_info->image_mapping);
+               job_info->image_mapping = job_render->image_mapping;
+       }
+
        if (job_render->include_text) {
                if (job_info->text_mapping)
                        gdk_region_destroy (job_info->text_mapping);
                job_info->text_mapping = job_render->text_mapping;
        }
-       
 
        if (job_render->include_selection) {
-
                if (job_info->selection) {
                        g_object_unref (G_OBJECT (job_info->selection));
                        job_info->selection = NULL;
@@ -447,10 +459,9 @@ copy_job_to_job_info (EvJobRender   *job_render,
                g_object_unref (G_OBJECT (job_info->job));
                job_info->job = NULL;
        }
-
 }
 
-static CacheJobInfo*
+static CacheJobInfo *
 find_job_cache (EvPixbufCache *pixbuf_cache,
                int            page)
 {
@@ -528,6 +539,7 @@ add_job_if_needed (EvPixbufCache *pixbuf_cache,
        gboolean include_links = FALSE;
        gboolean include_text = FALSE;
        gboolean include_selection = FALSE;
+       gboolean include_images = FALSE;
        int width, height;
        GdkColor *text, *base;
 
@@ -554,6 +566,8 @@ add_job_if_needed (EvPixbufCache *pixbuf_cache,
        /* Figure out what else we need for this job */
        if (job_info->link_mapping == NULL)
                include_links = TRUE;
+       if (job_info->image_mapping == NULL)
+               include_images = TRUE;
        if (job_info->text_mapping == NULL)
                include_text = TRUE;
        if (new_selection_pixbuf_needed (pixbuf_cache, job_info, page, scale)) {
@@ -570,6 +584,7 @@ add_job_if_needed (EvPixbufCache *pixbuf_cache,
                                           &(job_info->target_points),
                                           text, base,
                                           include_links,
+                                          include_images,
                                           include_text,
                                           include_selection);
        ev_job_queue_add_job (job_info->job, priority);
@@ -690,6 +705,28 @@ ev_pixbuf_cache_get_link_mapping (EvPixbufCache *pixbuf_cache,
        return job_info->link_mapping;
 }
 
+GList *
+ev_pixbuf_cache_get_image_mapping (EvPixbufCache *pixbuf_cache,
+                                  gint           page)
+{
+       CacheJobInfo *job_info;
+
+       if (!EV_IS_DOCUMENT_IMAGES (pixbuf_cache->document))
+               return NULL;
+       
+       job_info = find_job_cache (pixbuf_cache, page);
+       if (job_info == NULL)
+               return NULL;
+
+       /* We don't need to wait for the idle to handle the callback */
+       if (job_info->job &&
+           EV_JOB (job_info->job)->finished) {
+               copy_job_to_job_info (EV_JOB_RENDER (job_info->job), job_info, pixbuf_cache);
+       }
+
+       return job_info->image_mapping;
+}
+
 static gboolean
 new_selection_pixbuf_needed (EvPixbufCache *pixbuf_cache,
                             CacheJobInfo  *job_info,
@@ -729,8 +766,8 @@ clear_selection_if_needed (EvPixbufCache *pixbuf_cache,
 }
 
 GdkRegion *
-ev_pixbuf_cache_get_text_mapping      (EvPixbufCache *pixbuf_cache,
-                                      gint           page)
+ev_pixbuf_cache_get_text_mapping (EvPixbufCache *pixbuf_cache,
+                                 gint           page)
 {
        CacheJobInfo *job_info;
 
index c956832dff9d5033f50c5631144400628ea1ca6b..6f96dc18f91efe633c673ec380c8da3b62cc30f6 100644 (file)
@@ -48,7 +48,7 @@ typedef struct {
 typedef struct _EvPixbufCache       EvPixbufCache;
 typedef struct _EvPixbufCacheClass  EvPixbufCacheClass;
 
-GType          ev_pixbuf_cache_get_type         (void) G_GNUC_CONST;
+GType          ev_pixbuf_cache_get_type             (void) G_GNUC_CONST;
 EvPixbufCache *ev_pixbuf_cache_new                  (GtkWidget     *view,
                                                     EvDocument    *document);
 void           ev_pixbuf_cache_set_page_range       (EvPixbufCache *pixbuf_cache,
@@ -61,6 +61,8 @@ GdkPixbuf     *ev_pixbuf_cache_get_pixbuf           (EvPixbufCache *pixbuf_cache
                                                     gint           page);
 GList         *ev_pixbuf_cache_get_link_mapping     (EvPixbufCache *pixbuf_cache,
                                                     gint           page);
+GList         *ev_pixbuf_cache_get_image_mapping    (EvPixbufCache *pixbuf_cache,
+                                                    gint           page);
 GdkRegion     *ev_pixbuf_cache_get_text_mapping     (EvPixbufCache *pixbuf_cache,
                                                     gint           page);
 void           ev_pixbuf_cache_clear                (EvPixbufCache *pixbuf_cache);
index f1f5ad57300e4eb0ec02eadff25f9608d2e1b03f..ec1fa020f16bc8a3ea384011ae0a5ecebc4a14ae 100644 (file)
@@ -24,6 +24,7 @@
 #include "ev-view.h"
 #include "ev-pixbuf-cache.h"
 #include "ev-page-cache.h"
+#include "ev-image.h"
 
 /* Information for middle clicking and moving around the doc */
 typedef struct {
@@ -41,6 +42,13 @@ typedef struct {
        GList *selections;
 } SelectionInfo;
 
+/* Information for handling images DND */
+typedef struct {
+       gboolean in_drag;
+       GdkPoint start;
+       EvImage *image;
+} ImageDNDInfo;
+
 typedef enum {
        SCROLL_TO_KEEP_POSITION,
        SCROLL_TO_PAGE_POSITION,
@@ -127,6 +135,9 @@ struct _EvView {
        EvViewSelectionMode selection_mode;
        SelectionInfo selection_info;
 
+       /* Image DND */
+       ImageDNDInfo image_dnd_info;
+
        /* Links */
        GtkWidget *link_tooltip;
        EvLink *hovered_link;
index f353f4739862440afea1867b4fda71bff77bf03d..1e156d5019006250242ec225b64753b3dc51752d 100644 (file)
@@ -35,6 +35,7 @@
 #include "ev-utils.h"
 #include "ev-selection.h"
 #include "ev-document-links.h"
+#include "ev-document-images.h"
 #include "ev-document-find.h"
 #include "ev-document-transition.h"
 #include "ev-document-misc.h"
@@ -72,6 +73,12 @@ enum {
        N_SIGNALS,
 };
 
+enum {
+       TARGET_DND_URI,
+       TARGET_DND_TEXT,
+       TARGET_DND_IMAGE
+};
+
 enum {
        TARGET_STRING,
        TARGET_TEXT,
@@ -1113,6 +1120,9 @@ ev_view_get_link_at_location (EvView  *view,
        gint x_offset = 0, y_offset = 0;
        gint x_new = 0, y_new = 0;
        GList *link_mapping;
+
+       if (!EV_IS_DOCUMENT_LINKS (view->document))
+               return NULL;
        
        x += view->scroll_x;
        y += view->scroll_y;
@@ -1508,6 +1518,40 @@ handle_link_over_xy (EvView *view, gint x, gint y)
        return;
 }
 
+/*** Images ***/
+static EvImage *
+ev_view_get_image_at_location (EvView  *view,
+                              gdouble  x,
+                              gdouble  y)
+{
+       gint page = -1;
+       gint x_offset = 0, y_offset = 0;
+       gint x_new = 0, y_new = 0;
+       GList *image_mapping;
+
+       if (!EV_IS_DOCUMENT_IMAGES (view->document))
+               return NULL;
+
+       x += view->scroll_x;
+       y += view->scroll_y;
+
+       find_page_at_location (view, x, y, &page, &x_offset, &y_offset);
+
+       if (page == -1)
+               return NULL;
+
+       if (get_doc_point_from_offset (view, page, x_offset,
+                                      y_offset, &x_new, &y_new) == FALSE)
+               return NULL;
+
+       image_mapping = ev_pixbuf_cache_get_image_mapping (view->pixbuf_cache, page);
+
+       if (image_mapping)
+               return ev_image_mapping_find (image_mapping, x_new, y_new);
+       else
+               return NULL;
+}
+
 /*** GtkWidget implementation ***/
 
 static void
@@ -1899,17 +1943,38 @@ ev_view_expose_event (GtkWidget      *widget,
        return FALSE;
 }
 
+static gboolean
+ev_view_do_popup_menu (EvView *view,
+                      gdouble x,
+                      gdouble y)
+{
+       EvLink  *link;
+       EvImage *image;
+
+       image = ev_view_get_image_at_location (view, x, y);
+       if (image) {
+               g_signal_emit (view, signals[SIGNAL_POPUP_MENU], 0, image);
+               return TRUE;
+       }
+
+       link = ev_view_get_link_at_location (view, x, y);
+       if (link) {
+               g_signal_emit (view, signals[SIGNAL_POPUP_MENU], 0, link);
+               return TRUE;
+       }
+
+       g_signal_emit (view, signals[SIGNAL_POPUP_MENU], 0, NULL);
+
+       return TRUE;
+}
+
 static gboolean
 ev_view_popup_menu (GtkWidget *widget)
 {
-    gint x, y;
-    EvLink *link;
-    EvView *view = EV_VIEW (widget);
-    
-    gtk_widget_get_pointer (widget, &x, &y);
-    link = ev_view_get_link_at_location (view, x, y);
-    g_signal_emit (view, signals[SIGNAL_POPUP_MENU], 0, link);
-    return TRUE;
+       gint x, y;
+       
+       gtk_widget_get_pointer (widget, &x, &y);
+       return ev_view_do_popup_menu (EV_VIEW (widget), x, y);
 }
 
 static gboolean
@@ -1917,7 +1982,6 @@ ev_view_button_press_event (GtkWidget      *widget,
                            GdkEventButton *event)
 {
        EvView *view = EV_VIEW (widget);
-       EvLink *link;
        
        if (!GTK_WIDGET_HAS_FOCUS (widget)) {
                gtk_widget_grab_focus (widget);
@@ -1927,7 +1991,9 @@ ev_view_button_press_event (GtkWidget      *widget,
        view->selection_info.in_drag = FALSE;
        
        switch (event->button) {
-               case 1:
+               case 1: {
+                       EvImage *image;
+
                        if (view->selection_info.selections) {
                                if (location_in_selected_text (view,
                                                               event->x + view->scroll_x,
@@ -1938,11 +2004,19 @@ ev_view_button_press_event (GtkWidget      *widget,
                                }
                                
                                gtk_widget_queue_draw (widget);
+                       } else if ((image = ev_view_get_image_at_location (view, event->x, event->y))) {
+                               if (view->image_dnd_info.image)
+                                       g_object_unref (view->image_dnd_info.image);
+                               view->image_dnd_info.image = g_object_ref (image);
+                               view->image_dnd_info.in_drag = TRUE;
+
+                               view->image_dnd_info.start.x = event->x + view->scroll_x;
+                               view->image_dnd_info.start.y = event->y + view->scroll_y;
                        }
 
                        view->selection_info.start.x = event->x + view->scroll_x;
                        view->selection_info.start.y = event->y + view->scroll_y;
-                       
+               }                       
                        return TRUE;
                case 2:
                        /* use root coordinates as reference point because
@@ -1956,9 +2030,7 @@ ev_view_button_press_event (GtkWidget      *widget,
 
                        return TRUE;
                case 3:
-                       link = ev_view_get_link_at_location (view, event->x, event->y);
-                       g_signal_emit (view, signals[SIGNAL_POPUP_MENU], 0, link);
-                       return TRUE;
+                       return ev_view_do_popup_menu (view, event->x, event->y);
        }
        
        return FALSE;
@@ -1974,18 +2046,62 @@ ev_view_drag_data_get (GtkWidget        *widget,
 {
        EvView *view = EV_VIEW (widget);
 
-       if (view->selection_info.selections &&
-           ev_document_can_get_text (view->document)) {
-               gchar *text;
+       switch (info) {
+               case TARGET_DND_TEXT:
+                       if (view->selection_info.selections &&
+                           ev_document_can_get_text (view->document)) {
+                               gchar *text;
 
-               text = get_selected_text (view);
+                               text = get_selected_text (view);
+                               
+                               gtk_selection_data_set_text (selection_data,
+                                                            text,
+                                                            strlen (text));
+                               
+                               g_free (text);
+                       }
+                       break;
+               case TARGET_DND_IMAGE:
+                       if (view->image_dnd_info.image) {
+                               GdkPixbuf *pixbuf;
+
+                               pixbuf = ev_image_get_pixbuf (view->image_dnd_info.image);
+                               gtk_selection_data_set_pixbuf (selection_data, pixbuf);
+                       }
+                       break;
+               case TARGET_DND_URI:
+                       if (view->image_dnd_info.image) {
+                               const gchar *tmp_uri;
+                               gchar      **uris;
 
-               gtk_selection_data_set_text (selection_data, text, strlen (text));
+                               tmp_uri = ev_image_save_tmp (view->image_dnd_info.image);
 
-               g_free (text);
+                               uris = g_new0 (gchar *, 2);
+                               uris[0] = (gchar *)tmp_uri;
+                               
+                               gtk_selection_data_set_uris (selection_data, uris);
+
+                               /* g_free instead of g_strfreev since tmp_uri is const */ 
+                               g_free (uris);
+                       }
        }
 }
 
+static gboolean
+ev_view_drag_motion (GtkWidget      *widget,
+                    GdkDragContext *context,
+                    gint            x,
+                    gint            y,
+                    guint           time)
+{
+       if (gtk_drag_get_source_widget (context) == widget)
+               gdk_drag_status (context, 0, time);
+       else
+               gdk_drag_status (context, context->suggested_action, time);
+       
+       return TRUE;
+}
+                    
 static void
 ev_view_drag_data_received (GtkWidget          *widget,
                            GdkDragContext     *context,
@@ -2086,19 +2202,38 @@ ev_view_motion_notify_event (GtkWidget      *widget,
                                              view->selection_info.start.x,
                                              view->selection_info.start.y,
                                              x, y)) {
-                       GdkDragContext *context;
                        GtkTargetList *target_list = gtk_target_list_new (NULL, 0);
 
-                       gtk_target_list_add_text_targets (target_list, 0);
+                       gtk_target_list_add_text_targets (target_list, TARGET_DND_TEXT);
 
-                       context = gtk_drag_begin (widget, target_list,
-                                                 GDK_ACTION_COPY,
-                                                 1, (GdkEvent *)event);
+                       gtk_drag_begin (widget, target_list,
+                                       GDK_ACTION_COPY,
+                                       1, (GdkEvent *)event);
 
                        view->selection_info.in_drag = FALSE;
 
                        gtk_target_list_unref (target_list);
 
+                       return TRUE;
+               }
+       } else if (view->image_dnd_info.in_drag) {
+               if (gtk_drag_check_threshold (widget,
+                                             view->selection_info.start.x,
+                                             view->selection_info.start.y,
+                                             x, y)) {
+                       GtkTargetList *target_list = gtk_target_list_new (NULL, 0);
+
+                       gtk_target_list_add_uri_targets (target_list, TARGET_DND_URI);
+                       gtk_target_list_add_image_targets (target_list, TARGET_DND_IMAGE, TRUE);
+
+                       gtk_drag_begin (widget, target_list,
+                                       GDK_ACTION_COPY,
+                                       1, (GdkEvent *)event);
+
+                       view->image_dnd_info.in_drag = FALSE;
+
+                       gtk_target_list_unref (target_list);
+
                        return TRUE;
                }
        }
@@ -2191,6 +2326,7 @@ ev_view_button_release_event (GtkWidget      *widget,
 
        view->pressed_button = -1;
        view->drag_info.in_drag = FALSE;
+       view->image_dnd_info.in_drag = FALSE;
 
        if (view->selection_scroll_id) {
            g_source_remove (view->selection_scroll_id);
@@ -2818,6 +2954,10 @@ ev_view_finalize (GObject *object)
 
        clear_selection (view);
 
+       if (view->image_dnd_info.image)
+               g_object_unref (view->image_dnd_info.image);
+       view->image_dnd_info.image = NULL;
+
        G_OBJECT_CLASS (ev_view_parent_class)->finalize (object);
 }
 
@@ -3003,6 +3143,7 @@ ev_view_class_init (EvViewClass *class)
        widget_class->leave_notify_event = ev_view_leave_notify_event;
        widget_class->style_set = ev_view_style_set;
        widget_class->drag_data_get = ev_view_drag_data_get;
+       widget_class->drag_motion = ev_view_drag_motion;
        widget_class->drag_data_received = ev_view_drag_data_received;
        widget_class->popup_menu = ev_view_popup_menu;
        gtk_object_class->destroy = ev_view_destroy;
index ec979874bdba626c640959973a1e75d2e7f18358..c0adf8e9f9a08e672c493394571050d230bd4a91 100644 (file)
@@ -70,6 +70,7 @@
 #include "ev-utils.h"
 #include "ev-debug.h"
 #include "ev-history.h"
+#include "ev-image.h"
 
 #ifdef WITH_GNOME_PRINT
 #include "ev-print-job.h"
@@ -151,9 +152,10 @@ struct _EvWindowPrivate {
        GtkWidget *fullscreen_popup;
        guint      fullscreen_timeout_id;
 
-       /* Popup link */
+       /* Popup view */
        GtkWidget *view_popup;
        EvLink    *link;
+       EvImage   *image;
 
        /* Popup attachment */
        GtkWidget    *attachment_popup;
@@ -242,6 +244,10 @@ static void     ev_view_popup_cmd_open_link_new_window  (GtkAction        *actio
                                                         EvWindow         *window);
 static void     ev_view_popup_cmd_copy_link_address     (GtkAction        *action,
                                                         EvWindow         *window);
+static void     ev_view_popup_cmd_save_image_as         (GtkAction        *action,
+                                                        EvWindow         *window);
+static void     ev_view_popup_cmd_copy_image            (GtkAction        *action,
+                                                        EvWindow         *window);
 static void    ev_attachment_popup_cmd_open_attachment (GtkAction        *action,
                                                         EvWindow *window);
 static void    ev_attachment_popup_cmd_save_attachment_as (GtkAction *action, 
@@ -3290,18 +3296,13 @@ ev_window_sidebar_visibility_changed_cb (EvSidebar  *ev_sidebar,
        }
 }
 
-static gboolean
-view_menu_popup_cb (EvView   *view,
-                   EvLink   *link,
-                   EvWindow *ev_window)
+static void
+view_menu_link_popup (EvWindow *ev_window,
+                     EvLink   *link)
 {
-       GtkWidget *popup;
        gboolean   show_external = FALSE;
        gboolean   show_internal = FALSE;
        GtkAction *action;
-
-       if (ev_view_get_presentation (EV_VIEW (ev_window->priv->view)))
-               return FALSE;
        
        if (ev_window->priv->link)
                g_object_unref (ev_window->priv->link);
@@ -3311,26 +3312,23 @@ view_menu_popup_cb (EvView   *view,
        else    
                ev_window->priv->link = NULL;
 
-       popup = ev_window->priv->view_popup;
-
        if (ev_window->priv->link) {
                EvLinkAction *ev_action;
 
                ev_action = ev_link_get_action (link);
-               if (!ev_action)
-                       return FALSE;
-               
-               switch (ev_link_action_get_action_type (ev_action)) {
-                       case EV_LINK_ACTION_TYPE_GOTO_DEST:
-                       case EV_LINK_ACTION_TYPE_GOTO_REMOTE:
-                               show_internal = TRUE;
-                               break;
-                       case EV_LINK_ACTION_TYPE_EXTERNAL_URI:
-                       case EV_LINK_ACTION_TYPE_LAUNCH:
-                               show_external = TRUE;
-                               break;
-                       default:
-                               break;
+               if (ev_action) {
+                       switch (ev_link_action_get_action_type (ev_action)) {
+                               case EV_LINK_ACTION_TYPE_GOTO_DEST:
+                               case EV_LINK_ACTION_TYPE_GOTO_REMOTE:
+                                       show_internal = TRUE;
+                                       break;
+                               case EV_LINK_ACTION_TYPE_EXTERNAL_URI:
+                               case EV_LINK_ACTION_TYPE_LAUNCH:
+                                       show_external = TRUE;
+                                       break;
+                               default:
+                                       break;
+                       }
                }
        }
        
@@ -3349,9 +3347,49 @@ view_menu_popup_cb (EvView   *view,
        action = gtk_action_group_get_action (ev_window->priv->view_popup_action_group,
                                              "OpenLinkNewWindow");
        gtk_action_set_visible (action, show_internal);
+}
 
-       gtk_menu_popup (GTK_MENU (popup), NULL, NULL,
-                       NULL, NULL,
+static void
+view_menu_image_popup (EvWindow  *ev_window,
+                      EvImage   *image)
+{
+       GtkAction *action;
+       gboolean   show_image = FALSE;
+       
+       if (ev_window->priv->image)
+               g_object_unref (ev_window->priv->image);
+       
+       if (image)
+               ev_window->priv->image = g_object_ref (image);
+       else    
+               ev_window->priv->image = NULL;
+
+       show_image = (ev_window->priv->image != NULL);
+       
+       action = gtk_action_group_get_action (ev_window->priv->view_popup_action_group,
+                                             "SaveImageAs");
+       gtk_action_set_visible (action, show_image);
+
+       action = gtk_action_group_get_action (ev_window->priv->view_popup_action_group,
+                                             "CopyImage");
+       gtk_action_set_visible (action, show_image);
+}
+
+static gboolean
+view_menu_popup_cb (EvView   *view,
+                   GObject  *object,
+                   EvWindow *ev_window)
+{
+       if (ev_view_get_presentation (EV_VIEW (ev_window->priv->view)))
+               return FALSE;
+
+       view_menu_link_popup (ev_window,
+                             EV_IS_LINK (object) ? EV_LINK (object) : NULL);
+       view_menu_image_popup (ev_window,
+                              EV_IS_IMAGE (object) ? EV_IMAGE (object) : NULL);
+       
+       gtk_menu_popup (GTK_MENU (ev_window->priv->view_popup),
+                       NULL, NULL, NULL, NULL,
                        3, gtk_get_current_event_time ());
        return TRUE;
 }
@@ -3617,6 +3655,11 @@ ev_window_dispose (GObject *object)
                priv->link = NULL;
        }
 
+       if (priv->image) {
+               g_object_unref (priv->image);
+               priv->image = NULL;
+       }
+
        if (priv->attach_list) {
                g_list_foreach (priv->attach_list,
                                (GFunc) g_object_unref,
@@ -3848,8 +3891,11 @@ static const GtkActionEntry view_popup_entries [] = {
        { "OpenLinkNewWindow", NULL, N_("Open in New _Window"), NULL,
          NULL, G_CALLBACK (ev_view_popup_cmd_open_link_new_window) },
        { "CopyLinkAddress", NULL, N_("_Copy Link Address"), NULL,
-         NULL,
-         G_CALLBACK (ev_view_popup_cmd_copy_link_address) },
+         NULL, G_CALLBACK (ev_view_popup_cmd_copy_link_address) },
+       { "SaveImageAs", NULL, N_("_Save Image As..."), NULL,
+         NULL, G_CALLBACK (ev_view_popup_cmd_save_image_as) },
+       { "CopyImage", NULL, N_("Copy _Image"), NULL,
+         NULL, G_CALLBACK (ev_view_popup_cmd_copy_image) },
 };
 
 static const GtkActionEntry attachment_popup_entries [] = {
@@ -4228,6 +4274,86 @@ ev_view_popup_cmd_copy_link_address (GtkAction *action, EvWindow *window)
        gtk_clipboard_set_text (clipboard, uri, -1);
 }
 
+static void
+image_save_dialog_response_cb (GtkWidget *fc,
+                              gint       response_id,
+                              EvWindow  *ev_window)
+{
+       gchar  *uri;
+       gchar  *filename;
+       GError *error = NULL;
+       
+       if (response_id != GTK_RESPONSE_OK) {
+               gtk_widget_destroy (fc);
+               return;
+       }
+
+       uri = gtk_file_chooser_get_uri (GTK_FILE_CHOOSER (fc));
+       filename = g_filename_from_uri (uri, NULL, NULL);
+       g_free (uri);
+       
+       /* FIXME: allow saving in other image formats than png */
+       gdk_pixbuf_save (ev_image_get_pixbuf (ev_window->priv->image),
+                        filename, "png", &error, NULL);
+       
+       if (error) {
+               ev_window_error_dialog (GTK_WINDOW (fc),
+                                       _("The image could not be saved."),
+                                       error);
+               g_error_free (error);
+       }
+
+       g_free (filename);
+
+       gtk_widget_destroy (fc);
+}
+
+static void
+ev_view_popup_cmd_save_image_as (GtkAction *action, EvWindow *window)
+{
+       GtkWidget     *fc;
+       GtkFileFilter *filter;
+
+       if (!window->priv->image)
+               return;
+
+       fc = gtk_file_chooser_dialog_new (_("Save Image"),
+                                         GTK_WINDOW (window),
+                                         GTK_FILE_CHOOSER_ACTION_SAVE,
+                                         GTK_STOCK_CANCEL,
+                                         GTK_RESPONSE_CANCEL,
+                                         GTK_STOCK_SAVE, GTK_RESPONSE_OK,
+                                         NULL);
+
+       gtk_dialog_set_default_response (GTK_DIALOG (fc), GTK_RESPONSE_OK);
+       gtk_file_chooser_set_do_overwrite_confirmation (GTK_FILE_CHOOSER (fc), TRUE);
+
+       filter = gtk_file_filter_new ();
+       gtk_file_filter_set_name (filter, _("Images"));
+       gtk_file_filter_add_pixbuf_formats (filter);
+       gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (fc), filter);
+
+       g_signal_connect (fc, "response",
+                         G_CALLBACK (image_save_dialog_response_cb),
+                         window);
+
+       gtk_widget_show (fc);
+}
+
+static void
+ev_view_popup_cmd_copy_image (GtkAction *action, EvWindow *window)
+{
+       GtkClipboard *clipboard;
+
+       if (!window->priv->image)
+               return;
+       
+       clipboard = gtk_widget_get_clipboard (GTK_WIDGET (window),
+                                             GDK_SELECTION_CLIPBOARD);
+       gtk_clipboard_set_image (clipboard,
+                                ev_image_get_pixbuf (window->priv->image));
+}
+
 static void
 ev_attachment_popup_cmd_open_attachment (GtkAction *action, EvWindow *window)
 {
@@ -4317,7 +4443,7 @@ ev_attachment_popup_cmd_save_attachment_as (GtkAction *action, EvWindow *window)
                attachment = (EvAttachment *) window->priv->attach_list->data;
        
        fc = gtk_file_chooser_dialog_new (
-               _("Save a Copy"),
+               _("Save Attachment"),
                GTK_WINDOW (window),
                attachment ? GTK_FILE_CHOOSER_ACTION_SAVE : GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
                GTK_STOCK_CANCEL,