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() )
153 {
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");
163 }
166 ClipboardManagerImpl::~ClipboardManagerImpl() {}
169 /**
170 * @brief Copy selection contents to the clipboard
171 */
172 void ClipboardManagerImpl::copy()
173 {
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();
217 }
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)
225 {
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();
241 }
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)
248 {
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;
276 }
278 /**
279 * @brief Returns the id of the first visible copied object
280 */
281 const gchar *ClipboardManagerImpl::getFirstObjectID()
282 {
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;
310 }
313 /**
314 * @brief Implements the Paste Style action
315 */
316 bool ClipboardManagerImpl::pasteStyle()
317 {
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;
352 }
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)
362 {
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;
410 }
413 /**
414 * @brief Applies a path effect from the clipboard to the selected path
415 */
416 bool ClipboardManagerImpl::pastePathEffect()
417 {
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;
453 }
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()
461 {
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;
477 }
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()
485 {
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;
504 }
507 /**
508 * @brief Iterate over a list of items and copy them to the clipboard.
509 */
510 void ClipboardManagerImpl::_copySelection(Inkscape::Selection *selection)
511 {
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);
564 }
567 /**
568 * @brief Recursively copy all the definitions used by a given item to the clipboard defs
569 */
570 void ClipboardManagerImpl::_copyUsedDefs(SPItem *item)
571 {
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 }
646 }
649 /**
650 * @brief Copy a single gradient to the clipboard's defs element
651 */
652 void ClipboardManagerImpl::_copyGradient(SPGradient *gradient)
653 {
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 }
659 }
662 /**
663 * @brief Copy a single pattern to the clipboard document's defs element
664 */
665 void ClipboardManagerImpl::_copyPattern(SPPattern *pattern)
666 {
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 }
678 }
681 /**
682 * @brief Copy a text path to the clipboard's defs element
683 */
684 void ClipboardManagerImpl::_copyTextPath(SPTextPath *tp)
685 {
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);
693 }
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)
704 {
705 Inkscape::XML::Node *dup = node->duplicate(target_doc);
706 parent->appendChild(dup);
707 Inkscape::GC::release(dup);
708 return dup;
709 }
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)
719 {
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);
772 }
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)
781 {
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);
793 for (Inkscape::XML::Node *def = defs->firstChild() ; def ; def = def->next()) {
794 _copyNode(def, target_xmldoc, target_defs);
795 }
796 }
799 /**
800 * @brief Retrieve a bitmap image from the clipboard and paste it into the active document
801 */
802 bool ClipboardManagerImpl::_pasteImage()
803 {
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;
832 }
834 /**
835 * @brief Paste text into the selected text object or create a new one to hold it
836 */
837 bool ClipboardManagerImpl::_pasteText()
838 {
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;
854 }
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)
863 {
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;
907 }
910 /**
911 * @brief Applies a pasted path effect to a given item
912 */
913 void ClipboardManagerImpl::_applyPathEffect(SPItem *item, gchar const *effect)
914 {
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 }
927 }
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)
935 {
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;
979 }
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*/)
989 {
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);
1012 }
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()
1022 {
1023 // why is this called before _onGet???
1024 //_discardInternalClipboard();
1025 }
1028 /**
1029 * @brief Creates an internal clipboard document from scratch
1030 */
1031 void ClipboardManagerImpl::_createInternalClipboard()
1032 {
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 }
1044 }
1047 /**
1048 * @brief Deletes the internal clipboard document
1049 */
1050 void ClipboardManagerImpl::_discardInternalClipboard()
1051 {
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 }
1060 }
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)
1067 {
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);
1086 }
1089 /**
1090 * @brief Find the most suitable clipboard target
1091 */
1092 Glib::ustring ClipboardManagerImpl::_getBestTarget()
1093 {
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 "";
1116 }
1119 /**
1120 * @brief Set the clipboard targets to reflect the mimetypes Inkscape can output
1121 */
1122 void ClipboardManagerImpl::_setClipboardTargets()
1123 {
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));
1134 }
1137 /**
1138 * @brief Set the string representation of a 32-bit RGBA color as the clipboard contents
1139 */
1140 void ClipboardManagerImpl::_setClipboardColor(guint32 color)
1141 {
1142 gchar colorstr[16];
1143 g_snprintf(colorstr, 16, "%08x", color);
1144 _clipboard->set_text(colorstr);
1145 }
1148 /**
1149 * @brief Put a notification on the mesage stack
1150 */
1151 void ClipboardManagerImpl::_userWarn(SPDesktop *desktop, char const *msg)
1152 {
1153 desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, msg);
1154 }
1158 /* #######################################
1159 ClipboardManager class
1160 ####################################### */
1162 ClipboardManager *ClipboardManager::_instance = NULL;
1164 ClipboardManager::ClipboardManager() {}
1165 ClipboardManager::~ClipboardManager() {}
1166 ClipboardManager *ClipboardManager::get()
1167 {
1168 if ( _instance == NULL )
1169 _instance = new ClipboardManagerImpl;
1170 return _instance;
1171 }
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 :