Code

Resolve ID clashes when pasting (fixes bug 165936).
[inkscape.git] / src / ui / clipboard.cpp
1 /** @file
2  * @brief System-wide clipboard management - implementation
3  */
4 /* Authors:
5  *   Krzysztof KosiƄski <tweenk@o2.pl>
6  *   Incorporates some code from selection-chemistry.cpp, see that file for more credits.
7  *
8  * Copyright (C) 2008 authors
9  *
10  * This program is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU General Public License
12  * as published by the Free Software Foundation; either version 2
13  * of the License, or (at your option) any later version.
14  *
15  * See the file COPYING for details.
16  */
18 #include "ui/clipboard.h"
20 // TODO: reduce header bloat if possible
22 #include <list>
23 #include <algorithm>
24 #include <gtkmm/clipboard.h>
25 #include <glibmm/ustring.h>
26 #include <glibmm/i18n.h>
27 #include <glib/gstdio.h> // for g_file_set_contents etc., used in _onGet and paste
28 #include "gc-core.h"
29 #include "xml/repr.h"
30 #include "inkscape.h"
31 #include "io/stringstream.h"
32 #include "desktop.h"
33 #include "desktop-handles.h"
34 #include "desktop-style.h" // for sp_desktop_set_style, used in _pasteStyle
35 #include "document.h"
36 #include "document-private.h"
37 #include "selection.h"
38 #include "message-stack.h"
39 #include "context-fns.h"
40 #include "dropper-context.h" // used in copy()
41 #include "style.h"
42 #include "extension/db.h" // extension database
43 #include "extension/input.h"
44 #include "extension/output.h"
45 #include "selection-chemistry.h"
46 #include "libnr/nr-rect.h"
47 #include "box3d.h"
48 #include "gradient-drag.h"
49 #include "sp-item.h"
50 #include "sp-item-transform.h" // for sp_item_scale_rel, used in _pasteSize
51 #include "sp-path.h"
52 #include "sp-pattern.h"
53 #include "sp-shape.h"
54 #include "sp-gradient.h"
55 #include "sp-gradient-reference.h"
56 #include "sp-gradient-fns.h"
57 #include "sp-linear-gradient-fns.h"
58 #include "sp-radial-gradient-fns.h"
59 #include "sp-clippath.h"
60 #include "sp-mask.h"
61 #include "sp-textpath.h"
62 #include "sp-rect.h"
63 #include "live_effects/lpeobject.h"
64 #include "live_effects/lpeobject-reference.h"
65 #include "live_effects/parameter/path.h"
66 #include "svg/svg.h" // for sp_svg_transform_write, used in _copySelection
67 #include "svg/css-ostringstream.h" // used in _parseColor
68 #include "file.h" // for file_import, used in _pasteImage
69 #include "prefs-utils.h" // for prefs_get_string_attribute, used in _pasteImage
70 #include "text-context.h"
71 #include "text-editing.h"
72 #include "tools-switch.h"
73 #include "libnr/n-art-bpath-2geom.h"
74 #include "path-chemistry.h"
75 #include "id-clash.h"
77 /// @brief Made up mimetype to represent Gdk::Pixbuf clipboard contents
78 #define CLIPBOARD_GDK_PIXBUF_TARGET "image/x-gdk-pixbuf"
80 #define CLIPBOARD_TEXT_TARGET "text/plain"
82 namespace Inkscape {
83 namespace UI {
86 /**
87  * @brief Default implementation of the clipboard manager
88  */
89 class ClipboardManagerImpl : public ClipboardManager {
90 public:
91     virtual void copy();
92     virtual void copyPathParameter(Inkscape::LivePathEffect::PathParam *);
93     virtual bool paste(bool in_place);
94     virtual bool pasteStyle();
95     virtual bool pasteSize(bool, bool, bool);
96     virtual bool pastePathEffect();
97     virtual Glib::ustring getPathParameter();
98     virtual Glib::ustring getShapeOrTextObjectId();
99     virtual const gchar *getFirstObjectID();
101     ClipboardManagerImpl();
102     ~ClipboardManagerImpl();
104 private:
105     void _copySelection(Inkscape::Selection *);
106     void _copyUsedDefs(SPItem *);
107     void _copyGradient(SPGradient *);
108     void _copyPattern(SPPattern *);
109     void _copyTextPath(SPTextPath *);
110     Inkscape::XML::Node *_copyNode(Inkscape::XML::Node *, Inkscape::XML::Document *, Inkscape::XML::Node *);
112     void _pasteDocument(SPDocument *, bool in_place);
113     void _pasteDefs(SPDocument *);
114     bool _pasteImage();
115     bool _pasteText();
116     SPCSSAttr *_parseColor(const Glib::ustring &);
117     void _applyPathEffect(SPItem *, gchar const *);
118     SPDocument *_retrieveClipboard(Glib::ustring = "");
120     // clipboard callbacks
121     void _onGet(Gtk::SelectionData &, guint);
122     void _onClear();
124     // various helpers
125     void _createInternalClipboard();
126     void _discardInternalClipboard();
127     Inkscape::XML::Node *_createClipNode();
128     NR::scale _getScale(Geom::Point &, Geom::Point &, NR::Rect &, bool, bool);
129     Glib::ustring _getBestTarget();
130     void _setClipboardTargets();
131     void _setClipboardColor(guint32);
132     void _userWarn(SPDesktop *, char const *);
134     // private properites
135     SPDocument *_clipboardSPDoc; ///< Document that stores the clipboard until someone requests it
136     Inkscape::XML::Node *_defs; ///< Reference to the clipboard document's defs node
137     Inkscape::XML::Node *_root; ///< Reference to the clipboard's root node
138     Inkscape::XML::Node *_clipnode; ///< The node that holds extra information
139     Inkscape::XML::Document *_doc; ///< Reference to the clipboard's Inkscape::XML::Document
141     Glib::RefPtr<Gtk::Clipboard> _clipboard; ///< Handle to the system wide clipboard - for convenience
142     std::list<Glib::ustring> _preferred_targets; ///< List of supported clipboard targets
143 };
146 ClipboardManagerImpl::ClipboardManagerImpl()
147     : _clipboardSPDoc(NULL),
148       _defs(NULL),
149       _root(NULL),
150       _clipnode(NULL),
151       _doc(NULL),
152       _clipboard( Gtk::Clipboard::get() )
154     // push supported clipboard targets, in order of preference
155     _preferred_targets.push_back("image/x-inkscape-svg");
156     _preferred_targets.push_back("image/svg+xml");
157     _preferred_targets.push_back("image/svg+xml-compressed");
158 #ifdef WIN32
159     _preferred_targets.push_back("image/x-emf");
160 #endif
161     _preferred_targets.push_back("application/pdf");
162     _preferred_targets.push_back("image/x-adobe-illustrator");
166 ClipboardManagerImpl::~ClipboardManagerImpl() {}
169 /**
170  * @brief Copy selection contents to the clipboard
171  */
172 void ClipboardManagerImpl::copy()
174     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
175     if ( desktop == NULL ) return;
176     Inkscape::Selection *selection = sp_desktop_selection(desktop);
178     // Special case for when the gradient dragger is active - copies gradient color
179     if (desktop->event_context->get_drag()) {
180         GrDrag *drag = desktop->event_context->get_drag();
181         if (drag->hasSelection()) {
182             _setClipboardColor(drag->getColor());
183             _discardInternalClipboard();
184             return;
185         }
186     }
188     // Special case for when the color picker ("dropper") is active - copies color under cursor
189     if (tools_isactive(desktop, TOOLS_DROPPER)) {
190         _setClipboardColor(sp_dropper_context_get_color(desktop->event_context));
191         _discardInternalClipboard();
192         return;
193     }
195     // Special case for when the text tool is active - if some text is selected, copy plain text,
196     // not the object that holds it
197     if (tools_isactive(desktop, TOOLS_TEXT)) {
198         Glib::ustring selected_text = sp_text_get_selected_text(desktop->event_context);
199         if (!selected_text.empty()) {
200             _clipboard->set_text(selected_text);
201             _discardInternalClipboard();
202             return;
203         }
204     }
206     if (selection->isEmpty()) {  // check whether something is selected
207         _userWarn(desktop, _("Nothing was copied."));
208         return;
209     }
210     _discardInternalClipboard();
212     _createInternalClipboard();   // construct a new clipboard document
213     _copySelection(selection);   // copy all items in the selection to the internal clipboard
214     fit_canvas_to_drawing(_clipboardSPDoc);
216     _setClipboardTargets();
220 /**
221  * @brief Copy a Live Path Effect path parameter to the clipboard
222  * @param pp The path parameter to store in the clipboard
223  */
224 void ClipboardManagerImpl::copyPathParameter(Inkscape::LivePathEffect::PathParam *pp)
226     if ( pp == NULL ) return;
227     gchar *svgd = sp_svg_write_path( pp->get_pathvector() );
228     if ( svgd == NULL || *svgd == '\0' ) return;
230     _discardInternalClipboard();
231     _createInternalClipboard();
233     Inkscape::XML::Node *pathnode = _doc->createElement("svg:path");
234     pathnode->setAttribute("d", svgd);
235     g_free(svgd);
236     _root->appendChild(pathnode);
237     Inkscape::GC::release(pathnode);
239     fit_canvas_to_drawing(_clipboardSPDoc);
240     _setClipboardTargets();
243 /**
244  * @brief Paste from the system clipboard into the active desktop
245  * @param in_place Whether to put the contents where they were when copied
246  */
247 bool ClipboardManagerImpl::paste(bool in_place)
249     // do any checking whether we really are able to paste before requesting the contents
250     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
251     if ( desktop == NULL ) return false;
252     if ( Inkscape::have_viable_layer(desktop, desktop->messageStack()) == false ) return false;
254     Glib::ustring target = _getBestTarget();
256     // Special cases of clipboard content handling go here 00ff00
257     // Note that target priority is determined in _getBestTarget.
258     // TODO: Handle x-special/gnome-copied-files and text/uri-list to support pasting files
260     // if there is an image on the clipboard, paste it
261     if ( target == CLIPBOARD_GDK_PIXBUF_TARGET ) return _pasteImage();
262     // if there's only text, paste it into a selected text object or create a new one
263     if ( target == CLIPBOARD_TEXT_TARGET ) return _pasteText();
265     // otherwise, use the import extensions
266     SPDocument *tempdoc = _retrieveClipboard(target);
267     if ( tempdoc == NULL ) {
268         _userWarn(desktop, _("Nothing on the clipboard."));
269         return false;
270     }
272     _pasteDocument(tempdoc, in_place);
273     sp_document_unref(tempdoc);
275     return true;
278 /**
279  * @brief Returns the id of the first visible copied object
280  */
281 const gchar *ClipboardManagerImpl::getFirstObjectID()
283     SPDocument *tempdoc = _retrieveClipboard("image/x-inkscape-svg");
284     if ( tempdoc == NULL ) {
285         return NULL;
286     }
288     Inkscape::XML::Node
289         *root = sp_document_repr_root(tempdoc);
291     if (!root)
292         return NULL;
294     Inkscape::XML::Node *ch = sp_repr_children(root);
295     while (ch != NULL && 
296            strcmp(ch->name(), "svg:g") &&
297            strcmp(ch->name(), "svg:path") &&
298            strcmp(ch->name(), "svg:use") &&
299            strcmp(ch->name(), "svg:text") &&
300            strcmp(ch->name(), "svg:image") &&
301            strcmp(ch->name(), "svg:rect")
302         )
303         ch = ch->next();
305     if (ch) {
306         return ch->attribute("id");
307     }
309     return NULL;
313 /**
314  * @brief Implements the Paste Style action
315  */
316 bool ClipboardManagerImpl::pasteStyle()
318     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
319     if (desktop == NULL) return false;
321     // check whether something is selected
322     Inkscape::Selection *selection = sp_desktop_selection(desktop);
323     if (selection->isEmpty()) {
324         _userWarn(desktop, _("Select <b>object(s)</b> to paste style to."));
325         return false;
326     }
328     SPDocument *tempdoc = _retrieveClipboard("image/x-inkscape-svg");
329     if ( tempdoc == NULL ) {
330         _userWarn(desktop, _("No style on the clipboard."));
331         return false;
332     }
334     Inkscape::XML::Node
335         *root = sp_document_repr_root(tempdoc),
336         *clipnode = sp_repr_lookup_name(root, "inkscape:clipboard", 1);
338     bool pasted = false;
340     if (clipnode) {
341         _pasteDefs(tempdoc);
342         SPCSSAttr *style = sp_repr_css_attr(clipnode, "style");
343         sp_desktop_set_style(desktop, style);
344         pasted = true;
345     }
346     else {
347         _userWarn(desktop, _("No style on the clipboard."));
348     }
350     sp_document_unref(tempdoc);
351     return pasted;
355 /**
356  * @brief Resize the selection or each object in the selection to match the clipboard's size
357  * @param separately Whether to scale each object in the selection separately
358  * @param apply_x Whether to scale the width of objects / selection
359  * @param apply_y Whether to scale the height of objects / selection
360  */
361 bool ClipboardManagerImpl::pasteSize(bool separately, bool apply_x, bool apply_y)
363     if(!apply_x && !apply_y) return false; // pointless parameters
365     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
366     if ( desktop == NULL ) return false;
367     Inkscape::Selection *selection = sp_desktop_selection(desktop);
368     if (selection->isEmpty()) {
369         _userWarn(desktop, _("Select <b>object(s)</b> to paste size to."));
370         return false;
371     }
373     // FIXME: actually, this should accept arbitrary documents
374     SPDocument *tempdoc = _retrieveClipboard("image/x-inkscape-svg");
375     if ( tempdoc == NULL ) {
376         _userWarn(desktop, _("No size on the clipboard."));
377         return false;
378     }
380     // retrieve size ifomration from the clipboard
381     Inkscape::XML::Node *root = sp_document_repr_root(tempdoc);
382     Inkscape::XML::Node *clipnode = sp_repr_lookup_name(root, "inkscape:clipboard", 1);
383     bool pasted = false;
384     if (clipnode) {
385         Geom::Point min, max;
386         sp_repr_get_point(clipnode, "min", &min);
387         sp_repr_get_point(clipnode, "max", &max);
389         // resize each object in the selection
390         if (separately) {
391             for (GSList *i = const_cast<GSList*>(selection->itemList()) ; i ; i = i->next) {
392                 SPItem *item = SP_ITEM(i->data);
393                 NR::Maybe<NR::Rect> obj_size = sp_item_bbox_desktop(item);
394                 if ( !obj_size || obj_size->isEmpty() ) continue;
395                 sp_item_scale_rel(item, _getScale(min, max, *obj_size, apply_x, apply_y));
396             }
397         }
398         // resize the selection as a whole
399         else {
400             NR::Maybe<NR::Rect> sel_size = selection->bounds();
401             if ( sel_size && !sel_size->isEmpty() ) {
402                 sp_selection_scale_relative(selection, sel_size->midpoint(),
403                     _getScale(min, max, *sel_size, apply_x, apply_y));
404             }
405         }
406         pasted = true;
407     }
408     sp_document_unref(tempdoc);
409     return pasted;
413 /**
414  * @brief Applies a path effect from the clipboard to the selected path
415  */
416 bool ClipboardManagerImpl::pastePathEffect()
418     /** @todo FIXME: pastePathEffect crashes when moving the path with the applied effect,
419         segfaulting in fork_private_if_necessary(). */
421     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
422     if ( desktop == NULL )
423         return false;
425     Inkscape::Selection *selection = sp_desktop_selection(desktop);
426     if (selection && selection->isEmpty()) {
427         _userWarn(desktop, _("Select <b>object(s)</b> to paste live path effect to."));
428         return false;
429     }
431     SPDocument *tempdoc = _retrieveClipboard("image/x-inkscape-svg");
432     if ( tempdoc ) {
433         Inkscape::XML::Node *root = sp_document_repr_root(tempdoc);
434         Inkscape::XML::Node *clipnode = sp_repr_lookup_name(root, "inkscape:clipboard", 1);
435         if ( clipnode ) {
436             gchar const *effect = clipnode->attribute("inkscape:path-effect");
437             if ( effect ) {
438                 _pasteDefs(tempdoc);
439                 // make sure all selected items are converted to paths first (i.e. rectangles)
440                 sp_selected_path_to_curves(false);
441                 for (GSList *item = const_cast<GSList *>(selection->itemList()) ; item ; item = item->next) {
442                     _applyPathEffect(reinterpret_cast<SPItem*>(item->data), effect);
443                 }
445                 return true;
446             }
447         }
448     }
450     // no_effect:
451     _userWarn(desktop, _("No effect on the clipboard."));
452     return false;
456 /**
457  * @brief Get LPE path data from the clipboard
458  * @return The retrieved path data (contents of the d attribute), or "" if no path was found
459  */
460 Glib::ustring ClipboardManagerImpl::getPathParameter()
462     SPDocument *tempdoc = _retrieveClipboard(); // any target will do here
463     if ( tempdoc == NULL ) {
464         _userWarn(SP_ACTIVE_DESKTOP, _("Nothing on the clipboard."));
465         return "";
466     }
467     Inkscape::XML::Node
468         *root = sp_document_repr_root(tempdoc),
469         *path = sp_repr_lookup_name(root, "svg:path", -1); // unlimited search depth
470     if ( path == NULL ) {
471         _userWarn(SP_ACTIVE_DESKTOP, _("Clipboard does not contain a path."));
472         sp_document_unref(tempdoc);
473         return "";
474     }
475     gchar const *svgd = path->attribute("d");
476     return svgd;
480 /**
481  * @brief Get object id of a shape or text item from the clipboard
482  * @return The retrieved id string (contents of the id attribute), or "" if no shape or text item was found
483  */
484 Glib::ustring ClipboardManagerImpl::getShapeOrTextObjectId()
486     SPDocument *tempdoc = _retrieveClipboard(); // any target will do here
487     if ( tempdoc == NULL ) {
488         _userWarn(SP_ACTIVE_DESKTOP, _("Nothing on the clipboard."));
489         return "";
490     }
491     Inkscape::XML::Node *root = sp_document_repr_root(tempdoc);
493     Inkscape::XML::Node *repr = sp_repr_lookup_name(root, "svg:path", -1); // unlimited search depth
494     if ( repr == NULL )
495         repr = sp_repr_lookup_name(root, "svg:text", -1);
497     if ( repr == NULL ) {
498         _userWarn(SP_ACTIVE_DESKTOP, _("Clipboard does not contain a path."));
499         sp_document_unref(tempdoc);
500         return "";
501     }
502     gchar const *svgd = repr->attribute("id");
503     return svgd;
507 /**
508  * @brief Iterate over a list of items and copy them to the clipboard.
509  */
510 void ClipboardManagerImpl::_copySelection(Inkscape::Selection *selection)
512     GSList const *items = selection->itemList();
513     // copy the defs used by all items
514     for (GSList *i = const_cast<GSList *>(items) ; i != NULL ; i = i->next) {
515         _copyUsedDefs(SP_ITEM (i->data));
516     }
518     // copy the representation of the items
519     GSList *sorted_items = g_slist_copy(const_cast<GSList *>(items));
520     sorted_items = g_slist_sort(sorted_items, (GCompareFunc) sp_object_compare_position);
522     for (GSList *i = sorted_items ; i ; i = i->next) {
523         if (!SP_IS_ITEM(i->data)) continue;
524         Inkscape::XML::Node *obj = SP_OBJECT_REPR(i->data);
525         Inkscape::XML::Node *obj_copy = _copyNode(obj, _doc, _root);
527         // copy complete inherited style
528         SPCSSAttr *css = sp_repr_css_attr_inherited(obj, "style");
529         sp_repr_css_set(obj_copy, css, "style");
530         sp_repr_css_attr_unref(css);
532         // write the complete accumulated transform passed to us
533         // (we're dealing with unattached representations, so we write to their attributes
534         // instead of using sp_item_set_transform)
535         gchar *transform_str = sp_svg_transform_write(from_2geom(sp_item_i2doc_affine(SP_ITEM(i->data))));
536         obj_copy->setAttribute("transform", transform_str);
537         g_free(transform_str);
538     }
540     // copy style for Paste Style action
541     if (sorted_items) {
542         if(SP_IS_ITEM(sorted_items->data)) {
543             SPCSSAttr *style = take_style_from_item((SPItem *) sorted_items->data);
544             sp_repr_css_set(_clipnode, style, "style");
545             sp_repr_css_attr_unref(style);
546         }
548         // copy path effect from the first path
549         if (SP_IS_OBJECT(sorted_items->data)) {
550             gchar const *effect = SP_OBJECT_REPR(sorted_items->data)->attribute("inkscape:path-effect");
551             if (effect) {
552                 _clipnode->setAttribute("inkscape:path-effect", effect);
553             }
554         }
555     }
557     NR::Maybe<NR::Rect> size = selection->bounds();
558     if (size) {
559         sp_repr_set_point(_clipnode, "min", size->min().to_2geom());
560         sp_repr_set_point(_clipnode, "max", size->max().to_2geom());
561     }
563     g_slist_free(sorted_items);
567 /**
568  * @brief Recursively copy all the definitions used by a given item to the clipboard defs
569  */
570 void ClipboardManagerImpl::_copyUsedDefs(SPItem *item)
572     // copy fill and stroke styles (patterns and gradients)
573     SPStyle *style = SP_OBJECT_STYLE(item);
575     if (style && (style->fill.isPaintserver())) {
576         SPObject *server = SP_OBJECT_STYLE_FILL_SERVER(item);
577         if (SP_IS_LINEARGRADIENT(server) || SP_IS_RADIALGRADIENT(server))
578             _copyGradient(SP_GRADIENT(server));
579         if (SP_IS_PATTERN(server))
580             _copyPattern(SP_PATTERN(server));
581     }
582     if (style && (style->stroke.isPaintserver())) {
583         SPObject *server = SP_OBJECT_STYLE_STROKE_SERVER(item);
584         if (SP_IS_LINEARGRADIENT(server) || SP_IS_RADIALGRADIENT(server))
585             _copyGradient(SP_GRADIENT(server));
586         if (SP_IS_PATTERN(server))
587             _copyPattern(SP_PATTERN(server));
588     }
590     // For shapes, copy all of the shape's markers
591     if (SP_IS_SHAPE(item)) {
592         SPShape *shape = SP_SHAPE (item);
593         for (int i = 0 ; i < SP_MARKER_LOC_QTY ; i++) {
594             if (shape->marker[i]) {
595                 _copyNode(SP_OBJECT_REPR(SP_OBJECT(shape->marker[i])), _doc, _defs);
596             }
597         }
598     }
599     // For lpe items, copy lpe stack if applicable
600     if (SP_IS_LPE_ITEM(item)) {
601         SPLPEItem *lpeitem = SP_LPE_ITEM (item);
602         if (sp_lpe_item_has_path_effect(lpeitem)) {
603             for (PathEffectList::iterator it = lpeitem->path_effect_list->begin(); it != lpeitem->path_effect_list->end(); ++it)
604             {
605                 LivePathEffectObject *lpeobj = (*it)->lpeobject;
606                 if (lpeobj)
607                     _copyNode(SP_OBJECT_REPR(SP_OBJECT(lpeobj)), _doc, _defs);
608             }
609         }
610     }
611     // For 3D boxes, copy perspectives
612     if (SP_IS_BOX3D(item)) {
613         _copyNode(SP_OBJECT_REPR(SP_OBJECT(box3d_get_perspective(SP_BOX3D(item)))), _doc, _defs);
614     }
615     // Copy text paths
616     if (SP_IS_TEXT_TEXTPATH(item)) {
617         _copyTextPath(SP_TEXTPATH(sp_object_first_child(SP_OBJECT(item))));
618     }
619     // Copy clipping objects
620     if (item->clip_ref->getObject()) {
621         _copyNode(SP_OBJECT_REPR(item->clip_ref->getObject()), _doc, _defs);
622     }
623     // Copy mask objects
624     if (item->mask_ref->getObject()) {
625         SPObject *mask = item->mask_ref->getObject();
626         _copyNode(SP_OBJECT_REPR(mask), _doc, _defs);
627         // recurse into the mask for its gradients etc.
628         for (SPObject *o = SP_OBJECT(mask)->children ; o != NULL ; o = o->next) {
629             if (SP_IS_ITEM(o))
630                 _copyUsedDefs(SP_ITEM(o));
631         }
632     }
633     // Copy filters
634     if (style->getFilter()) {
635         SPObject *filter = style->getFilter();
636         if (SP_IS_FILTER(filter)) {
637             _copyNode(SP_OBJECT_REPR(filter), _doc, _defs);
638         }
639     }
641     // recurse
642     for (SPObject *o = SP_OBJECT(item)->children ; o != NULL ; o = o->next) {
643         if (SP_IS_ITEM(o))
644             _copyUsedDefs(SP_ITEM(o));
645     }
649 /**
650  * @brief Copy a single gradient to the clipboard's defs element
651  */
652 void ClipboardManagerImpl::_copyGradient(SPGradient *gradient)
654     while (gradient) {
655         // climb up the refs, copying each one in the chain
656         _copyNode(SP_OBJECT_REPR(gradient), _doc, _defs);
657         gradient = gradient->ref->getObject();
658     }
662 /**
663  * @brief Copy a single pattern to the clipboard document's defs element
664  */
665 void ClipboardManagerImpl::_copyPattern(SPPattern *pattern)
667     // climb up the references, copying each one in the chain
668     while (pattern) {
669         _copyNode(SP_OBJECT_REPR(pattern), _doc, _defs);
671         // items in the pattern may also use gradients and other patterns, so recurse
672         for (SPObject *child = sp_object_first_child(SP_OBJECT(pattern)) ; child != NULL ; child = SP_OBJECT_NEXT(child) ) {
673             if (!SP_IS_ITEM (child)) continue;
674             _copyUsedDefs(SP_ITEM(child));
675         }
676         pattern = pattern->ref->getObject();
677     }
681 /**
682  * @brief Copy a text path to the clipboard's defs element
683  */
684 void ClipboardManagerImpl::_copyTextPath(SPTextPath *tp)
686     SPItem *path = sp_textpath_get_path_item(tp);
687     if(!path) return;
688     Inkscape::XML::Node *path_node = SP_OBJECT_REPR(path);
690     // Do not copy the text path to defs if it's already copied
691     if(sp_repr_lookup_child(_root, "id", path_node->attribute("id"))) return;
692     _copyNode(path_node, _doc, _defs);
696 /**
697  * @brief Copy a single XML node from one document to another
698  * @param node The node to be copied
699  * @param target_doc The document to which the node is to be copied
700  * @param parent The node in the target document which will become the parent of the copied node
701  * @return Pointer to the copied node
702  */
703 Inkscape::XML::Node *ClipboardManagerImpl::_copyNode(Inkscape::XML::Node *node, Inkscape::XML::Document *target_doc, Inkscape::XML::Node *parent)
705     Inkscape::XML::Node *dup = node->duplicate(target_doc);
706     parent->appendChild(dup);
707     Inkscape::GC::release(dup);
708     return dup;
712 /**
713  * @brief Paste the contents of a document into the active desktop
714  * @param clipdoc The document to paste
715  * @param in_place Whether to paste the selection where it was when copied
716  * @pre @c clipdoc is not empty and items can be added to the current layer
717  */
718 void ClipboardManagerImpl::_pasteDocument(SPDocument *clipdoc, bool in_place)
720     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
721     SPDocument *target_document = sp_desktop_document(desktop);
722     Inkscape::XML::Node
723         *root = sp_document_repr_root(clipdoc),
724         *target_parent = SP_OBJECT_REPR(desktop->currentLayer());
725     Inkscape::XML::Document *target_xmldoc = sp_document_repr_doc(target_document);
727     // copy definitions
728     _pasteDefs(clipdoc);
730     // copy objects
731     GSList *pasted_objects = NULL;
732     for (Inkscape::XML::Node *obj = root->firstChild() ; obj ; obj = obj->next()) {
733         // Don't copy metadata, defs, named views and internal clipboard contents to the document
734         if (!strcmp(obj->name(), "svg:defs")) continue;
735         if (!strcmp(obj->name(), "svg:metadata")) continue;
736         if (!strcmp(obj->name(), "sodipodi:namedview")) continue;
737         if (!strcmp(obj->name(), "inkscape:clipboard")) continue;
738         Inkscape::XML::Node *obj_copy = _copyNode(obj, target_xmldoc, target_parent);
739         pasted_objects = g_slist_prepend(pasted_objects, (gpointer) obj_copy);
740     }
742     Inkscape::Selection *selection = sp_desktop_selection(desktop);
743     selection->setReprList(pasted_objects);
745     // move the selection to the right position
746     if(in_place)
747     {
748         Inkscape::XML::Node *clipnode = sp_repr_lookup_name(root, "inkscape:clipboard", 1);
749         if (clipnode) {
750             Geom::Point min, max;
751             sp_repr_get_point(clipnode, "min", &min);
752             sp_repr_get_point(clipnode, "max", &max);
754             // this formula was discovered empyrically
755             min[Geom::Y] += ((max[Geom::Y] - min[Geom::Y]) - sp_document_height(target_document));
756             sp_selection_move_relative(selection, NR::Point(min));
757         }
758     }
759     // copied from former sp_selection_paste in selection-chemistry.cpp
760     else {
761         sp_document_ensure_up_to_date(target_document);
762         NR::Maybe<NR::Rect> sel_size = selection->bounds();
764         NR::Point m( desktop->point() );
765         if (sel_size) {
766             m -= sel_size->midpoint();
767         }
768         sp_selection_move_relative(selection, m);
769     }
771     g_slist_free(pasted_objects);
775 /**
776  * @brief Paste SVG defs from the document retrieved from the clipboard into the active document
777  * @param clipdoc The document to paste
778  * @pre @c clipdoc != NULL and pasting into the active document is possible
779  */
780 void ClipboardManagerImpl::_pasteDefs(SPDocument *clipdoc)
782     // boilerplate vars copied from _pasteDocument
783     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
784     SPDocument *target_document = sp_desktop_document(desktop);
785     Inkscape::XML::Node
786         *root = sp_document_repr_root(clipdoc),
787         *defs = sp_repr_lookup_name(root, "svg:defs", 1),
788         *target_defs = SP_OBJECT_REPR(SP_DOCUMENT_DEFS(target_document));
789     Inkscape::XML::Document *target_xmldoc = sp_document_repr_doc(target_document);
791     prevent_id_clashes(clipdoc, target_document);
792     
793     for (Inkscape::XML::Node *def = defs->firstChild() ; def ; def = def->next()) {
794         _copyNode(def, target_xmldoc, target_defs);
795     }
799 /**
800  * @brief Retrieve a bitmap image from the clipboard and paste it into the active document
801  */
802 bool ClipboardManagerImpl::_pasteImage()
804     SPDocument *doc = SP_ACTIVE_DOCUMENT;
805     if ( doc == NULL ) return false;
807     // retrieve image data
808     Glib::RefPtr<Gdk::Pixbuf> img = _clipboard->wait_for_image();
809     if (!img) return false;
811     // Very stupid hack: Write into a file, then import the file into the document.
812     // To avoid using tmpfile and POSIX file handles, make the filename based on current time.
813     // This wasn't my idea, I just copied this from selection-chemistry.cpp
814     // and just can't think of something saner at the moment. Pasting more than
815     // one image per second will overwrite the image.
816     // However, I don't think anyone is able to copy a _different_ image into inkscape
817     // in 1 second.
818     time_t rawtime;
819     char image_filename[128];
820     gchar const *save_folder;
822     time(&rawtime);
823     strftime(image_filename, 128, "inkscape_pasted_image_%Y%m%d_%H%M%S.png", localtime( &rawtime ));
824     save_folder = (gchar const *) prefs_get_string_attribute("dialogs.save_as", "path");
826     gchar *image_path = g_build_filename(save_folder, image_filename, NULL);
827     img->save(image_path, "png");
828     file_import(doc, image_path, NULL);
829     g_free(image_path);
831     return true;
834 /**
835  * @brief Paste text into the selected text object or create a new one to hold it
836  */
837 bool ClipboardManagerImpl::_pasteText()
839     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
840     if ( desktop == NULL ) return false;
842     // if the text editing tool is active, paste the text into the active text object
843     if (tools_isactive(desktop, TOOLS_TEXT))
844         return sp_text_paste_inline(desktop->event_context);
846     // try to parse the text as a color and, if successful, apply it as the current style
847     SPCSSAttr *css = _parseColor(_clipboard->wait_for_text());
848     if (css) {
849         sp_desktop_set_style(desktop, css);
850         return true;
851     }
853     return false;
857 /**
858  * @brief Attempt to parse the passed string as a hexadecimal RGB or RGBA color
859  * @param text The Glib::ustring to parse
860  * @return New CSS style representation if the parsing was successful, NULL otherwise
861  */
862 SPCSSAttr *ClipboardManagerImpl::_parseColor(const Glib::ustring &text)
864     Glib::ustring::size_type len = text.bytes();
865     char *str = const_cast<char *>(text.data());
866     bool attempt_alpha = false;
867     if ( !str || ( *str == '\0' ) ) return NULL; // this is OK due to boolean short-circuit
869     // those conditionals guard against parsing e.g. the string "fab" as "fab000"
870     // (incomplete color) and "45fab71" as "45fab710" (incomplete alpha)
871     if ( *str == '#' ) {
872         if ( len < 7 ) return NULL;
873         if ( len >= 9 ) attempt_alpha = true;
874     } else {
875         if ( len < 6 ) return NULL;
876         if ( len >= 8 ) attempt_alpha = true;
877     }
879     unsigned int color = 0, alpha = 0xff;
881     // skip a leading #, if present
882     if ( *str == '#' ) ++str;
884     // try to parse first 6 digits
885     int res = sscanf(str, "%6x", &color);
886     if ( res && ( res != EOF ) ) {
887         if (attempt_alpha) {// try to parse alpha if there's enough characters
888             sscanf(str + 6, "%2x", &alpha);
889             if ( !res || res == EOF ) alpha = 0xff;
890         }
892         SPCSSAttr *color_css = sp_repr_css_attr_new();
894         // print and set properties
895         gchar color_str[16];
896         g_snprintf(color_str, 16, "#%06x", color);
897         sp_repr_css_set_property(color_css, "fill", color_str);
899         float opacity = static_cast<float>(alpha)/static_cast<float>(0xff);
900         if (opacity > 1.0) opacity = 1.0; // safeguard
901         Inkscape::CSSOStringStream opcss;
902         opcss << opacity;
903         sp_repr_css_set_property(color_css, "fill-opacity", opcss.str().data());
904         return color_css;
905     }
906     return NULL;
910 /**
911  * @brief Applies a pasted path effect to a given item
912  */
913 void ClipboardManagerImpl::_applyPathEffect(SPItem *item, gchar const *effect)
915     if ( item == NULL ) return;
916     if ( SP_IS_RECT(item) ) return;
918     if (SP_IS_LPE_ITEM(item))
919     {
920         SPLPEItem *lpeitem = SP_LPE_ITEM(item);
921         SPObject *obj = sp_uri_reference_resolve(_clipboardSPDoc, effect);
922         if (!obj) return;
923         // if the effect is not used by anyone, we might as well take it
924         LivePathEffectObject *lpeobj = LIVEPATHEFFECT(obj)->fork_private_if_necessary(1);
925         sp_lpe_item_add_path_effect(lpeitem, lpeobj);
926     }
930 /**
931  * @brief Retrieve the clipboard contents as a document
932  * @return Clipboard contents converted to SPDocument, or NULL if no suitable content was present
933  */
934 SPDocument *ClipboardManagerImpl::_retrieveClipboard(Glib::ustring required_target)
936     Glib::ustring best_target;
937     if ( required_target == "" )
938         best_target = _getBestTarget();
939     else
940         best_target = required_target;
942     if ( best_target == "" ) {
943         return NULL;
944     }
946     if ( !_clipboard->wait_is_target_available(best_target) ) {
947         return NULL;
948     }
950     // doing this synchronously makes better sense
951     // TODO: use another method because this one is badly broken imo.
952     // from documentation: "Returns: A SelectionData object, which will be invalid if retrieving the given target failed."
953     // I don't know how to check whether an object is 'valid' or not, unusable if that's not possible...
954     Gtk::SelectionData sel = _clipboard->wait_for_contents(best_target);
955     Glib::ustring target = sel.get_target();  // this can crash if the result was invalid of last function. No way to check for this :(
957     // there is no specific plain SVG input extension, so if we can paste the Inkscape SVG format,
958     // we use the image/svg+xml mimetype to look up the input extension
959     if(target == "image/x-inkscape-svg")
960         target = "image/svg+xml";
962     Inkscape::Extension::DB::InputList inlist;
963     Inkscape::Extension::db.get_input_list(inlist);
964     Inkscape::Extension::DB::InputList::const_iterator in = inlist.begin();
965     for (; in != inlist.end() && target != (*in)->get_mimetype() ; ++in);
966     if ( in == inlist.end() )
967         return NULL; // this shouldn't happen unless _getBestTarget returns something bogus
969     // FIXME: Temporary hack until we add memory input.
970     // Save the clipboard contents to some file, then read it
971     gchar *filename = g_build_filename( g_get_tmp_dir(), "inkscape-clipboard-import", NULL );
972     g_file_set_contents(filename, (const gchar *) sel.get_data(), sel.get_length(), NULL);
974     SPDocument *tempdoc = (*in)->open(filename);
975     g_unlink(filename);
976     g_free(filename);
978     return tempdoc;
982 /**
983  * @brief Callback called when some other application requests data from Inkscape
984  *
985  * Finds a suitable output extension to save the internal clipboard document,
986  * then saves it to memory and sets the clipboard contents.
987  */
988 void ClipboardManagerImpl::_onGet(Gtk::SelectionData &sel, guint /*info*/)
990     g_assert( _clipboardSPDoc != NULL );
992     const Glib::ustring target = sel.get_target();
993     if(target == "") return; // this shouldn't happen
995     Inkscape::Extension::DB::OutputList outlist;
996     Inkscape::Extension::db.get_output_list(outlist);
997     Inkscape::Extension::DB::OutputList::const_iterator out = outlist.begin();
998     for ( ; out != outlist.end() && target != (*out)->get_mimetype() ; ++out);
999     if ( out == outlist.end() ) return; // this also shouldn't happen
1001     // FIXME: Temporary hack until we add support for memory output.
1002     // Save to a temporary file, read it back and then set the clipboard contents
1003     gchar *filename = g_build_filename( g_get_tmp_dir(), "inkscape-clipboard-export", NULL );
1004     gsize len; gchar *data;
1006     (*out)->save(_clipboardSPDoc, filename);
1007     g_file_get_contents(filename, &data, &len, NULL);
1008     g_unlink(filename); // delete the temporary file
1009     g_free(filename);
1011     sel.set(8, (guint8 const *) data, len);
1015 /**
1016  * @brief Callback when someone else takes the clipboard
1017  *
1018  * When the clipboard owner changes, this callback clears the internal clipboard document
1019  * to reduce memory usage.
1020  */
1021 void ClipboardManagerImpl::_onClear()
1023     // why is this called before _onGet???
1024     //_discardInternalClipboard();
1028 /**
1029  * @brief Creates an internal clipboard document from scratch
1030  */
1031 void ClipboardManagerImpl::_createInternalClipboard()
1033     if ( _clipboardSPDoc == NULL ) {
1034         _clipboardSPDoc = sp_document_new(NULL, false, true);
1035         //g_assert( _clipboardSPDoc != NULL );
1036         _defs = SP_OBJECT_REPR(SP_DOCUMENT_DEFS(_clipboardSPDoc));
1037         _doc = sp_document_repr_doc(_clipboardSPDoc);
1038         _root = sp_document_repr_root(_clipboardSPDoc);
1040         _clipnode = _doc->createElement("inkscape:clipboard");
1041         _root->appendChild(_clipnode);
1042         Inkscape::GC::release(_clipnode);
1043     }
1047 /**
1048  * @brief Deletes the internal clipboard document
1049  */
1050 void ClipboardManagerImpl::_discardInternalClipboard()
1052     if ( _clipboardSPDoc != NULL ) {
1053         sp_document_unref(_clipboardSPDoc);
1054         _clipboardSPDoc = NULL;
1055         _defs = NULL;
1056         _doc = NULL;
1057         _root = NULL;
1058         _clipnode = NULL;
1059     }
1063 /**
1064  * @brief Get the scale to resize an item, based on the command and desktop state
1065  */
1066 NR::scale ClipboardManagerImpl::_getScale(Geom::Point &min, Geom::Point &max, NR::Rect &obj_rect, bool apply_x, bool apply_y)
1068     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1069     double scale_x = 1.0;
1070     double scale_y = 1.0;
1072     if (apply_x) {
1073         scale_x = (max[Geom::X] - min[Geom::X]) / obj_rect.extent(NR::X);
1074     }
1075     if (apply_y) {
1076         scale_y = (max[Geom::Y] - min[Geom::Y]) / obj_rect.extent(NR::Y);
1077     }
1078     // If the "lock aspect ratio" button is pressed and we paste only a single coordinate,
1079     // resize the second one by the same ratio too
1080     if (desktop->isToolboxButtonActive("lock")) {
1081         if (apply_x && !apply_y) scale_y = scale_x;
1082         if (apply_y && !apply_x) scale_x = scale_y;
1083     }
1085     return NR::scale(scale_x, scale_y);
1089 /**
1090  * @brief Find the most suitable clipboard target
1091  */
1092 Glib::ustring ClipboardManagerImpl::_getBestTarget()
1094     std::list<Glib::ustring> targets = _clipboard->wait_for_targets();
1096     // clipboard target debugging snippet
1097     /*
1098     g_debug("Begin clipboard targets");
1099     for ( std::list<Glib::ustring>::iterator x = targets.begin() ; x != targets.end(); ++x )
1100         g_debug("Clipboard target: %s", (*x).data());
1101     g_debug("End clipboard targets\n");
1102     //*/
1104     for(std::list<Glib::ustring>::iterator i = _preferred_targets.begin() ;
1105         i != _preferred_targets.end() ; ++i)
1106     {
1107         if ( std::find(targets.begin(), targets.end(), *i) != targets.end() )
1108             return *i;
1109     }
1110     if (_clipboard->wait_is_image_available())
1111         return CLIPBOARD_GDK_PIXBUF_TARGET;
1112     if (_clipboard->wait_is_text_available())
1113         return CLIPBOARD_TEXT_TARGET;
1115     return "";
1119 /**
1120  * @brief Set the clipboard targets to reflect the mimetypes Inkscape can output
1121  */
1122 void ClipboardManagerImpl::_setClipboardTargets()
1124     Inkscape::Extension::DB::OutputList outlist;
1125     Inkscape::Extension::db.get_output_list(outlist);
1126     std::list<Gtk::TargetEntry> target_list;
1127     for (Inkscape::Extension::DB::OutputList::const_iterator out = outlist.begin() ; out != outlist.end() ; ++out) {
1128         target_list.push_back(Gtk::TargetEntry( (*out)->get_mimetype() ));
1129     }
1131     _clipboard->set(target_list,
1132         sigc::mem_fun(*this, &ClipboardManagerImpl::_onGet),
1133         sigc::mem_fun(*this, &ClipboardManagerImpl::_onClear));
1137 /**
1138  * @brief Set the string representation of a 32-bit RGBA color as the clipboard contents
1139  */
1140 void ClipboardManagerImpl::_setClipboardColor(guint32 color)
1142     gchar colorstr[16];
1143     g_snprintf(colorstr, 16, "%08x", color);
1144     _clipboard->set_text(colorstr);
1148 /**
1149  * @brief Put a notification on the mesage stack
1150  */
1151 void ClipboardManagerImpl::_userWarn(SPDesktop *desktop, char const *msg)
1153     desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, msg);
1158 /* #######################################
1159           ClipboardManager class
1160    ####################################### */
1162 ClipboardManager *ClipboardManager::_instance = NULL;
1164 ClipboardManager::ClipboardManager() {}
1165 ClipboardManager::~ClipboardManager() {}
1166 ClipboardManager *ClipboardManager::get()
1168     if ( _instance == NULL )
1169         _instance = new ClipboardManagerImpl;
1170     return _instance;
1173 } // namespace Inkscape
1174 } // namespace IO
1176 /*
1177   Local Variables:
1178   mode:c++
1179   c-file-style:"stroustrup"
1180   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1181   indent-tabs-mode:nil
1182   fill-column:99
1183   End:
1184 */
1185 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :