Code

Improve behavior when pasting, DnDing and importing bitmap images
authorKrzysztof Kosiński <tweenk.pl@gmail.com>
Thu, 4 Mar 2010 23:34:30 +0000 (00:34 +0100)
committerKrzysztof Kosiński <tweenk.pl@gmail.com>
Thu, 4 Mar 2010 23:34:30 +0000 (00:34 +0100)
src/extension/extension.cpp
src/extension/extension.h
src/extension/input.h
src/extension/internal/gdkpixbuf-input.cpp
src/interface.cpp
src/ui/clipboard.cpp

index 52d5f5148b540e3915a1bf1f5354ca2764dad108..b36d2e3a13478194dc50923770cd0e35373d9daa 100644 (file)
@@ -57,7 +57,9 @@ Parameter * param_shared (const gchar * name, GSList * list);
     not related to the module directly.  If the Repr does not include
     a name and an ID the module will be left in an errored state.
 */
-Extension::Extension (Inkscape::XML::Node * in_repr, Implementation::Implementation * in_imp) : _help(NULL)
+Extension::Extension (Inkscape::XML::Node * in_repr, Implementation::Implementation * in_imp)
+    : _help(NULL)
+    , _gui(true)
 {
     repr = in_repr;
     Inkscape::GC::anchor(in_repr);
@@ -673,7 +675,7 @@ public:
 Gtk::Widget *
 Extension::autogui (SPDocument * doc, Inkscape::XML::Node * node, sigc::signal<void> * changeSignal)
 {
-    if (param_visible_count() == 0) return NULL;
+    if (!_gui || param_visible_count() == 0) return NULL;
 
     AutoGUI * agui = Gtk::manage(new AutoGUI());
 
index 48ca86cf780871c77fdaef8206aabecb21206ff0..6210e2eba6ea1fbcaaeae6e6c0e3c6a87d486911 100644 (file)
@@ -99,6 +99,7 @@ private:
     state_t    _state;                    /**< Which state the Extension is currently in */
     std::vector<Dependency *>  _deps;     /**< Dependencies for this extension */
     static std::ofstream error_file;      /**< This is the place where errors get reported */
+    bool _gui;
 
 protected:
     Inkscape::XML::Node *repr;            /**< The XML description of the Extension */
@@ -219,6 +220,7 @@ public:
 public:
     Gtk::Widget *    autogui (SPDocument * doc, Inkscape::XML::Node * node, sigc::signal<void> * changeSignal = NULL);
     void paramListString (std::list <std::string> & retlist);
+    void set_gui(bool s) { _gui = s; }
 
     /* Extension editor dialog stuff */
 public:
index 55d807ce26ba30d93fe4fd54800de7e28a65614b..24cbc4896c048215684f02b8d43ea9884c93e8be 100644 (file)
@@ -11,6 +11,7 @@
 #ifndef INKSCAPE_EXTENSION_INPUT_H__
 #define INKSCAPE_EXTENSION_INPUT_H__
 
+#include <exception>
 #include <glib.h>
 #include "extension.h"
 #include "xml/repr.h"
@@ -30,8 +31,14 @@ public: /* this is a hack for this release, this will be private shortly */
     gchar *output_extension;     /**< Setting of what output extension should be used */
 
 public:
-    class open_failed {};        /**< Generic failure for an undescribed reason */
-    class no_extension_found {}; /**< Failed because we couldn't find an extension to match the filename */
+    struct open_failed : public std::exception {
+        virtual ~open_failed() throw() {}
+        const char *what() const throw() { return "Open failed"; }
+    };
+    struct no_extension_found : public std::exception {
+        virtual ~no_extension_found() throw() {}
+        const char *what() const throw() { return "No suitable input extension found"; }
+    };
 
                   Input                (Inkscape::XML::Node * in_repr,
                                         Implementation::Implementation * in_imp);
index 64a099c8a42e3c673b89b3348936c0e4050d6174..47cde4a5acbffd5cff24b3294fe70d6bee253c7a 100644 (file)
@@ -1,8 +1,10 @@
 #ifdef HAVE_CONFIG_H
 # include <config.h>
 #endif
+#include <glib/gprintf.h>
 #include "document-private.h"
 #include <dir-util.h>
+#include "extension/input.h"
 #include "extension/system.h"
 #include "gdkpixbuf-input.h"
 #include "selection-chemistry.h"
@@ -16,69 +18,107 @@ GdkPixbuf* pixbuf_new_from_file( char const *utf8name, GError **error );
 namespace Extension {
 namespace Internal {
 
+static std::set<Glib::ustring> create_lossy_set()
+{
+    std::set<Glib::ustring> lossy;
+    lossy.insert(".jpg");
+    lossy.insert(".jpeg");
+    return lossy;
+}
+
 SPDocument *
-GdkpixbufInput::open(Inkscape::Extension::Input */*mod*/, char const *uri)
+GdkpixbufInput::open(Inkscape::Extension::Input *mod, char const *uri)
 {
+    bool embed = !mod->get_param_bool("link");
     SPDocument *doc = NULL;
     GdkPixbuf *pb = Inkscape::IO::pixbuf_new_from_file( uri, NULL );
+    static std::set<Glib::ustring> lossy = create_lossy_set();
 
     if (pb) {         /* We are readable */
+        bool is_lossy;
+        Glib::ustring mime_type, ext;
+        Glib::ustring u = uri;
+        std::size_t dotpos = u.rfind('.');
+        if (dotpos != Glib::ustring::npos) {
+            ext = u.substr(dotpos, Glib::ustring::npos);
+        }
+
+        // HACK: replace with something better based on GIO
+        if (!ext.empty() && lossy.find(ext) != lossy.end()) {
+            is_lossy = true;
+            mime_type = "image/jpeg";
+        } else {
+            is_lossy = false;
+            mime_type = "image/png";
+        }
+
         doc = sp_document_new(NULL, TRUE, TRUE);
         bool saved = sp_document_get_undo_sensitive(doc);
         sp_document_set_undo_sensitive(doc, false); // no need to undo in this temporary document
 
-        Inkscape::XML::Node *repr = NULL;
-
         double width = gdk_pixbuf_get_width(pb);
         double height = gdk_pixbuf_get_height(pb);
         gchar const *str = gdk_pixbuf_get_option( pb, "Inkscape::DpiX" );
-        if ( str )
-        {
+        if ( str ) {
             gint dpi = atoi(str);
-            if ( dpi > 0 && dpi != 72 )
-            {
+            if ( dpi > 0 && dpi != 72 ) {
                 double scale = 72.0 / (double)dpi;
                 width *= scale;
             }
         }
         str = gdk_pixbuf_get_option( pb, "Inkscape::DpiY" );
-        if ( str )
-        {
+        if ( str ) {
             gint dpi = atoi(str);
-            if ( dpi > 0 && dpi != 72 )
-            {
+            if ( dpi > 0 && dpi != 72 ) {
                 double scale = 72.0 / (double)dpi;
                 height *= scale;
             }
         }
 
+        // Create image node
         Inkscape::XML::Document *xml_doc = sp_document_repr_doc(doc);
-        // import as <image>
-        repr = xml_doc->createElement("svg:image");
-
-        // convert filename to uri
-        gchar* _uri = g_filename_to_uri(uri, NULL, NULL);
-        if(_uri) {
-            repr->setAttribute("xlink:href", _uri);
-            g_free(_uri);
+        Inkscape::XML::Node *image_node = xml_doc->createElement("svg:image");
+        sp_repr_set_svg_double(image_node, "width", width);
+        sp_repr_set_svg_double(image_node, "height", height);
+
+        if (embed) {
+            // Save pixbuf as JPEG or PNG for embedding
+            gchar *data;
+            gsize length;
+            gdk_pixbuf_save_to_buffer(pb, &data, &length, is_lossy ? "jpeg" : "png", NULL, NULL);
+
+            // Save base64 encoded data in image node
+            // this formula taken from Glib docs
+            guint needed_size = length * 4 / 3 + length * 4 / (3 * 72) + 7;
+            needed_size += 5 + 8 + mime_type.size(); // 5 bytes for data:, 8 for ;base64,
+
+            gchar *buffer = (gchar *) g_malloc(needed_size), *buf_work = buffer;
+            buf_work += g_sprintf(buffer, "data:%s;base64,", mime_type.data());
+
+            gint state = 0, save = 0;
+            gsize written = 0;
+            written += g_base64_encode_step((guchar*) data, length, TRUE, buf_work, &state, &save);
+            written += g_base64_encode_close(TRUE, buf_work + written, &state, &save);
+            buf_work[written] = 0; // null terminate
+
+            image_node->setAttribute("xlink:href", buffer);
+            g_free(buffer);
         } else {
-            repr->setAttribute("xlink:href", uri);
+            // convert filename to uri
+            gchar* _uri = g_filename_to_uri(uri, NULL, NULL);
+            if(_uri) {
+                image_node->setAttribute("xlink:href", _uri);
+                g_free(_uri);
+            } else {
+                image_node->setAttribute("xlink:href", uri);
+            }
         }
-        /* impl: doc->base is currently NULL, so we can use uri for href whether it's absolute
-         * or relative.  The href will get rewritten by rebase_hrefs if by chance uri is relative
-         * and doc gets saved to a different directory.
-         *
-         * We don't bother setting sodipodi:absref, as we assume it's never useful to have
-         * sodipodi:absref with the same value as xlink:href, and rebase_hrefs will provide
-         * sodipodi:absref values where necessary. */
-
-        sp_repr_set_svg_double(repr, "width", width);
-        sp_repr_set_svg_double(repr, "height", height);
-
-        SP_DOCUMENT_ROOT(doc)->appendChildRepr(repr);
-        Inkscape::GC::release(repr);
-        gdk_pixbuf_unref(pb);
-        //alter the canvas size to fit the image size
+
+        g_object_unref(pb);
+
+        // Add it to the current layer
+        SP_DOCUMENT_ROOT(doc)->appendChildRepr(image_node);
+        Inkscape::GC::release(image_node);
         fit_canvas_to_drawing(doc);
         // restore undo, as now this document may be shown to the user if a bitmap was opened
         sp_document_set_undo_sensitive(doc, saved);
@@ -126,6 +166,9 @@ GdkpixbufInput::init(void)
                 "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
                     "<name>" N_("%s GDK pixbuf Input") "</name>\n"
                     "<id>org.inkscape.input.gdkpixbuf.%s</id>\n"
+                    "<param name=\"link\" gui-text=\""
+                        N_("Link image (leave unchecked if in doubt)")
+                        "\" type=\"boolean\">false</param>"
                     "<input>\n"
                         "<extension>.%s</extension>\n"
                         "<mimetype>%s</mimetype>\n"
index 9c187001986ecb616ebaa5f614cd0797777706a5..cb021db18132c14c9812c4c2de3ece980e7588b1 100644 (file)
@@ -23,7 +23,9 @@
 #include <glib.h>
 
 #include "inkscape-private.h"
+#include "extension/db.h"
 #include "extension/effect.h"
+#include "extension/input.h"
 #include "widgets/icon.h"
 #include "preferences.h"
 #include "path-prefix.h"
@@ -1454,48 +1456,26 @@ sp_ui_drag_data_received(GtkWidget *widget,
         case PNG_DATA:
         case JPEG_DATA:
         case IMAGE_DATA: {
-            Inkscape::XML::Document *xml_doc = sp_document_repr_doc(doc);
-            Inkscape::XML::Node *newImage = xml_doc->createElement("svg:image");
-            gchar *atom_name = gdk_atom_name(data->type);
-
-            // this formula taken from Glib docs
-            guint needed_size = data->length * 4 / 3 + data->length * 4 / (3 * 72) + 7;
-            needed_size += 5 + 8 + strlen(atom_name); // 5 bytes for data:, 8 for ;base64,
-
-            gchar *buffer = (gchar *) g_malloc(needed_size), *buf_work = buffer;
-            buf_work += g_sprintf(buffer, "data:%s;base64,", atom_name);
-
-            gint state = 0, save = 0;
-            g_base64_encode_step(data->data, data->length, TRUE, buf_work, &state, &save);
-            g_base64_encode_close(TRUE, buf_work, &state, &save);
-
-            newImage->setAttribute("xlink:href", buffer);
-            g_free(buffer);
-
-            GError *error = NULL;
-            GdkPixbufLoader *loader = gdk_pixbuf_loader_new_with_mime_type( gdk_atom_name(data->type), &error );
-            if ( loader ) {
-                error = NULL;
-                if ( gdk_pixbuf_loader_write( loader, data->data, data->length, &error) ) {
-                    GdkPixbuf *pbuf = gdk_pixbuf_loader_get_pixbuf(loader);
-                    if ( pbuf ) {
-                        char tmp[1024];
-                        int width = gdk_pixbuf_get_width(pbuf);
-                        int height = gdk_pixbuf_get_height(pbuf);
-                        snprintf( tmp, sizeof(tmp), "%d", width );
-                        newImage->setAttribute("width", tmp);
-
-                        snprintf( tmp, sizeof(tmp), "%d", height );
-                        newImage->setAttribute("height", tmp);
-                    }
-                }
-            }
-            g_free(atom_name);
+            const char *mime = (info == JPEG_DATA ? "image/jpeg" : "image/png");
 
-            // Add it to the current layer
-            desktop->currentLayer()->appendChildRepr(newImage);
-
-            Inkscape::GC::release(newImage);
+            Inkscape::Extension::DB::InputList o;
+            Inkscape::Extension::db.get_input_list(o);
+            Inkscape::Extension::DB::InputList::const_iterator i = o.begin();
+            while (i != o.end() && strcmp( (*i)->get_mimetype(), mime ) != 0) {
+                ++i;
+            }
+            Inkscape::Extension::Extension *ext = *i;
+            bool save = ext->get_param_bool("link");
+            ext->set_param_bool("link", false);
+            ext->set_gui(false);
+
+            gchar *filename = g_build_filename( g_get_tmp_dir(), "inkscape-dnd-import", NULL );
+            g_file_set_contents(filename, reinterpret_cast<gchar const *>(data->data), data->length, NULL);
+            file_import(doc, filename, ext);
+            g_free(filename);
+
+            ext->set_param_bool("link", save);
+            ext->set_gui(true);
             sp_document_done( doc , SP_VERB_NONE,
                               _("Drop bitmap image"));
             break;
index f213160f4e2598bab6939b5f39fd40f112dbf948..99d83573097e294f88b68c4f1a227297b04c93f0 100644 (file)
@@ -873,26 +873,25 @@ bool ClipboardManagerImpl::_pasteImage()
     Glib::RefPtr<Gdk::Pixbuf> img = _clipboard->wait_for_image();
     if (!img) return false;
 
-    // Very stupid hack: Write into a file, then import the file into the document.
-    // To avoid using tmpfile and POSIX file handles, make the filename based on current time.
-    // This wasn't my idea, I just copied this from selection-chemistry.cpp
-    // and just can't think of something saner at the moment. Pasting more than
-    // one image per second will overwrite the image.
-    // However, I don't think anyone is able to copy a _different_ image into inkscape
-    // in 1 second.
-    time_t rawtime;
-    char image_filename[128];
-
-    time(&rawtime);
-    strftime(image_filename, 128, "inkscape_pasted_image_%Y%m%d_%H%M%S.png", localtime( &rawtime ));
-    /// @todo Check whether the encoding is correct here
-    Inkscape::Preferences *prefs = Inkscape::Preferences::get();
-    std::string save_folder = Glib::filename_from_utf8(prefs->getString("/dialogs/save_as/path"));
-
-    gchar *image_path = g_build_filename(save_folder.data(), image_filename, NULL);
-    img->save(image_path, "png");
-    file_import(doc, image_path, NULL);
-    g_free(image_path);
+    // AARGH stupid
+    Inkscape::Extension::DB::InputList o;
+    Inkscape::Extension::db.get_input_list(o);
+    Inkscape::Extension::DB::InputList::const_iterator i = o.begin();
+    while (i != o.end() && strcmp( (*i)->get_mimetype(), "image/png" ) != 0) {
+        ++i;
+    }
+    Inkscape::Extension::Extension *png = *i;
+    bool save = png->get_param_bool("link");
+    png->set_param_bool("link", false);
+    png->set_gui(false);
+
+    gchar *filename = g_build_filename( g_get_tmp_dir(), "inkscape-clipboard-import", NULL );
+    img->save(filename, "png");
+    file_import(doc, filename, png);
+    g_free(filename);
+
+    png->set_param_bool("link", save);
+    png->set_gui(true);
 
     return true;
 }