f0537b720b592e1b2fbef70fd2674cfb424c6164
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/parameter/path.h"
65 #include "svg/svg.h" // for sp_svg_transform_write, used in _copySelection
66 #include "svg/css-ostringstream.h" // used in _parseColor
67 #include "file.h" // for file_import, used in _pasteImage
68 #include "prefs-utils.h" // for prefs_get_string_attribute, used in _pasteImage
69 #include "text-context.h"
70 #include "text-editing.h"
71 #include "tools-switch.h"
72 #include "live_effects/n-art-bpath-2geom.h"
73 #include "path-chemistry.h"
75 /// @brief Made up mimetype to represent Gdk::Pixbuf clipboard contents
76 #define CLIPBOARD_GDK_PIXBUF_TARGET "image/x-gdk-pixbuf"
78 #define CLIPBOARD_TEXT_TARGET "text/plain"
80 namespace Inkscape {
81 namespace UI {
84 /**
85 * @brief Default implementation of the clipboard manager
86 */
87 class ClipboardManagerImpl : public ClipboardManager {
88 public:
89 virtual void copy();
90 virtual void copyPathParameter(Inkscape::LivePathEffect::PathParam *);
91 virtual bool paste(bool in_place);
92 virtual bool pasteStyle();
93 virtual bool pasteSize(bool, bool, bool);
94 virtual bool pastePathEffect();
95 virtual Glib::ustring getPathParameter();
96 virtual Glib::ustring getShapeOrTextObjectId();
98 ClipboardManagerImpl();
99 ~ClipboardManagerImpl();
101 private:
102 void _copySelection(Inkscape::Selection *);
103 void _copyUsedDefs(SPItem *);
104 void _copyGradient(SPGradient *);
105 void _copyPattern(SPPattern *);
106 void _copyTextPath(SPTextPath *);
107 Inkscape::XML::Node *_copyNode(Inkscape::XML::Node *, Inkscape::XML::Document *, Inkscape::XML::Node *);
109 void _pasteDocument(SPDocument *, bool in_place);
110 void _pasteDefs(SPDocument *);
111 bool _pasteImage();
112 bool _pasteText();
113 SPCSSAttr *_parseColor(const Glib::ustring &);
114 void _applyPathEffect(SPItem *, gchar const *);
115 SPDocument *_retrieveClipboard(Glib::ustring = "");
117 // clipboard callbacks
118 void _onGet(Gtk::SelectionData &, guint);
119 void _onClear();
121 // various helpers
122 void _createInternalClipboard();
123 void _discardInternalClipboard();
124 Inkscape::XML::Node *_createClipNode();
125 NR::scale _getScale(Geom::Point &, Geom::Point &, NR::Rect &, bool, bool);
126 Glib::ustring _getBestTarget();
127 void _setClipboardTargets();
128 void _setClipboardColor(guint32);
129 void _userWarn(SPDesktop *, char const *);
131 // private properites
132 SPDocument *_clipboardSPDoc; ///< Document that stores the clipboard until someone requests it
133 Inkscape::XML::Node *_defs; ///< Reference to the clipboard document's defs node
134 Inkscape::XML::Node *_root; ///< Reference to the clipboard's root node
135 Inkscape::XML::Node *_clipnode; ///< The node that holds extra information
136 Inkscape::XML::Document *_doc; ///< Reference to the clipboard's Inkscape::XML::Document
138 Glib::RefPtr<Gtk::Clipboard> _clipboard; ///< Handle to the system wide clipboard - for convenience
139 std::list<Glib::ustring> _preferred_targets; ///< List of supported clipboard targets
140 };
143 ClipboardManagerImpl::ClipboardManagerImpl()
144 : _clipboardSPDoc(NULL),
145 _defs(NULL),
146 _root(NULL),
147 _clipnode(NULL),
148 _doc(NULL),
149 _clipboard( Gtk::Clipboard::get() )
150 {
151 // push supported clipboard targets, in order of preference
152 _preferred_targets.push_back("image/x-inkscape-svg");
153 _preferred_targets.push_back("image/svg+xml");
154 _preferred_targets.push_back("image/svg+xml-compressed");
155 #ifdef WIN32
156 _preferred_targets.push_back("image/x-emf");
157 #endif
158 _preferred_targets.push_back("application/pdf");
159 _preferred_targets.push_back("image/x-adobe-illustrator");
160 }
163 ClipboardManagerImpl::~ClipboardManagerImpl() {}
166 /**
167 * @brief Copy selection contents to the clipboard
168 */
169 void ClipboardManagerImpl::copy()
170 {
171 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
172 if ( desktop == NULL ) return;
173 Inkscape::Selection *selection = sp_desktop_selection(desktop);
175 // Special case for when the gradient dragger is active - copies gradient color
176 if (desktop->event_context->get_drag()) {
177 GrDrag *drag = desktop->event_context->get_drag();
178 if (drag->hasSelection()) {
179 _setClipboardColor(drag->getColor());
180 _discardInternalClipboard();
181 return;
182 }
183 }
185 // Special case for when the color picker ("dropper") is active - copies color under cursor
186 if (tools_isactive(desktop, TOOLS_DROPPER)) {
187 _setClipboardColor(sp_dropper_context_get_color(desktop->event_context));
188 _discardInternalClipboard();
189 return;
190 }
192 // Special case for when the text tool is active - if some text is selected, copy plain text,
193 // not the object that holds it
194 if (tools_isactive(desktop, TOOLS_TEXT)) {
195 Glib::ustring selected_text = sp_text_get_selected_text(desktop->event_context);
196 if (!selected_text.empty()) {
197 _clipboard->set_text(selected_text);
198 _discardInternalClipboard();
199 return;
200 }
201 }
203 if (selection->isEmpty()) { // check whether something is selected
204 _userWarn(desktop, _("Nothing was copied."));
205 return;
206 }
207 _discardInternalClipboard();
209 _createInternalClipboard(); // construct a new clipboard document
210 _copySelection(selection); // copy all items in the selection to the internal clipboard
211 fit_canvas_to_drawing(_clipboardSPDoc);
213 _setClipboardTargets();
214 }
217 /**
218 * @brief Copy a Live Path Effect path parameter to the clipboard
219 * @param pp The path parameter to store in the clipboard
220 */
221 void ClipboardManagerImpl::copyPathParameter(Inkscape::LivePathEffect::PathParam *pp)
222 {
223 if ( pp == NULL ) return;
224 gchar *svgd = SVGD_from_2GeomPath( pp->get_pathvector() );
225 if ( svgd == NULL || *svgd == '\0' ) return;
227 _discardInternalClipboard();
228 _createInternalClipboard();
230 Inkscape::XML::Node *pathnode = _doc->createElement("svg:path");
231 pathnode->setAttribute("d", svgd);
232 g_free(svgd);
233 _root->appendChild(pathnode);
234 Inkscape::GC::release(pathnode);
236 fit_canvas_to_drawing(_clipboardSPDoc);
237 _setClipboardTargets();
238 }
240 /**
241 * @brief Paste from the system clipboard into the active desktop
242 * @param in_place Whether to put the contents where they were when copied
243 */
244 bool ClipboardManagerImpl::paste(bool in_place)
245 {
246 // do any checking whether we really are able to paste before requesting the contents
247 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
248 if ( desktop == NULL ) return false;
249 if ( Inkscape::have_viable_layer(desktop, desktop->messageStack()) == false ) return false;
251 Glib::ustring target = _getBestTarget();
253 // Special cases of clipboard content handling go here 00ff00
254 // Note that target priority is determined in _getBestTarget.
255 // TODO: Handle x-special/gnome-copied-files and text/uri-list to support pasting files
257 // if there is an image on the clipboard, paste it
258 if ( target == CLIPBOARD_GDK_PIXBUF_TARGET ) return _pasteImage();
259 // if there's only text, paste it into a selected text object or create a new one
260 if ( target == CLIPBOARD_TEXT_TARGET ) return _pasteText();
262 // otherwise, use the import extensions
263 SPDocument *tempdoc = _retrieveClipboard(target);
264 if ( tempdoc == NULL ) {
265 _userWarn(desktop, _("Nothing on the clipboard."));
266 return false;
267 }
269 _pasteDocument(tempdoc, in_place);
270 sp_document_unref(tempdoc);
272 return true;
273 }
276 /**
277 * @brief Implements the Paste Style action
278 */
279 bool ClipboardManagerImpl::pasteStyle()
280 {
281 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
282 if (desktop == NULL) return false;
284 // check whether something is selected
285 Inkscape::Selection *selection = sp_desktop_selection(desktop);
286 if (selection->isEmpty()) {
287 _userWarn(desktop, _("Select <b>object(s)</b> to paste style to."));
288 return false;
289 }
291 SPDocument *tempdoc = _retrieveClipboard("image/x-inkscape-svg");
292 if ( tempdoc == NULL ) {
293 _userWarn(desktop, _("No style on the clipboard."));
294 return false;
295 }
297 Inkscape::XML::Node
298 *root = sp_document_repr_root(tempdoc),
299 *clipnode = sp_repr_lookup_name(root, "inkscape:clipboard", 1);
301 bool pasted = false;
303 if (clipnode) {
304 _pasteDefs(tempdoc);
305 SPCSSAttr *style = sp_repr_css_attr(clipnode, "style");
306 sp_desktop_set_style(desktop, style);
307 pasted = true;
308 }
309 else {
310 _userWarn(desktop, _("No style on the clipboard."));
311 }
313 sp_document_unref(tempdoc);
314 return pasted;
315 }
318 /**
319 * @brief Resize the selection or each object in the selection to match the clipboard's size
320 * @param separately Whether to scale each object in the selection separately
321 * @param apply_x Whether to scale the width of objects / selection
322 * @param apply_y Whether to scale the height of objects / selection
323 */
324 bool ClipboardManagerImpl::pasteSize(bool separately, bool apply_x, bool apply_y)
325 {
326 if(!apply_x && !apply_y) return false; // pointless parameters
328 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
329 if ( desktop == NULL ) return false;
330 Inkscape::Selection *selection = sp_desktop_selection(desktop);
331 if (selection->isEmpty()) {
332 _userWarn(desktop, _("Select <b>object(s)</b> to paste size to."));
333 return false;
334 }
336 // FIXME: actually, this should accept arbitrary documents
337 SPDocument *tempdoc = _retrieveClipboard("image/x-inkscape-svg");
338 if ( tempdoc == NULL ) {
339 _userWarn(desktop, _("No size on the clipboard."));
340 return false;
341 }
343 // retrieve size ifomration from the clipboard
344 Inkscape::XML::Node *root = sp_document_repr_root(tempdoc);
345 Inkscape::XML::Node *clipnode = sp_repr_lookup_name(root, "inkscape:clipboard", 1);
346 bool pasted = false;
347 if (clipnode) {
348 Geom::Point min, max;
349 sp_repr_get_point(clipnode, "min", &min);
350 sp_repr_get_point(clipnode, "max", &max);
352 // resize each object in the selection
353 if (separately) {
354 for (GSList *i = const_cast<GSList*>(selection->itemList()) ; i ; i = i->next) {
355 SPItem *item = SP_ITEM(i->data);
356 NR::Maybe<NR::Rect> obj_size = sp_item_bbox_desktop(item);
357 if ( !obj_size || obj_size->isEmpty() ) continue;
358 sp_item_scale_rel(item, _getScale(min, max, *obj_size, apply_x, apply_y));
359 }
360 }
361 // resize the selection as a whole
362 else {
363 NR::Maybe<NR::Rect> sel_size = selection->bounds();
364 if ( sel_size && !sel_size->isEmpty() ) {
365 sp_selection_scale_relative(selection, sel_size->midpoint(),
366 _getScale(min, max, *sel_size, apply_x, apply_y));
367 }
368 }
369 pasted = true;
370 }
371 sp_document_unref(tempdoc);
372 return pasted;
373 }
376 /**
377 * @brief Applies a path effect from the clipboard to the selected path
378 */
379 bool ClipboardManagerImpl::pastePathEffect()
380 {
381 /** @todo FIXME: pastePathEffect crashes when moving the path with the applied effect,
382 segfaulting in fork_private_if_necessary(). */
384 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
385 if ( desktop == NULL )
386 return false;
388 Inkscape::Selection *selection = sp_desktop_selection(desktop);
389 if (selection && selection->isEmpty()) {
390 _userWarn(desktop, _("Select <b>object(s)</b> to paste live path effect to."));
391 return false;
392 }
394 SPDocument *tempdoc = _retrieveClipboard("image/x-inkscape-svg");
395 if ( tempdoc ) {
396 Inkscape::XML::Node *root = sp_document_repr_root(tempdoc);
397 Inkscape::XML::Node *clipnode = sp_repr_lookup_name(root, "inkscape:clipboard", 1);
398 if ( clipnode ) {
399 gchar const *effect = clipnode->attribute("inkscape:path-effect");
400 if ( effect ) {
401 _pasteDefs(tempdoc);
402 // make sure all selected items are converted to paths first (i.e. rectangles)
403 sp_selected_path_to_curves(false);
404 for (GSList *item = const_cast<GSList *>(selection->itemList()) ; item ; item = item->next) {
405 _applyPathEffect(reinterpret_cast<SPItem*>(item->data), effect);
406 }
408 return true;
409 }
410 }
411 }
413 // no_effect:
414 _userWarn(desktop, _("No effect on the clipboard."));
415 return false;
416 }
419 /**
420 * @brief Get LPE path data from the clipboard
421 * @return The retrieved path data (contents of the d attribute), or "" if no path was found
422 */
423 Glib::ustring ClipboardManagerImpl::getPathParameter()
424 {
425 SPDocument *tempdoc = _retrieveClipboard(); // any target will do here
426 if ( tempdoc == NULL ) {
427 _userWarn(SP_ACTIVE_DESKTOP, _("Nothing on the clipboard."));
428 return "";
429 }
430 Inkscape::XML::Node
431 *root = sp_document_repr_root(tempdoc),
432 *path = sp_repr_lookup_name(root, "svg:path", -1); // unlimited search depth
433 if ( path == NULL ) {
434 _userWarn(SP_ACTIVE_DESKTOP, _("Clipboard does not contain a path."));
435 sp_document_unref(tempdoc);
436 return "";
437 }
438 gchar const *svgd = path->attribute("d");
439 return svgd;
440 }
443 /**
444 * @brief Get object id of a shape or text item from the clipboard
445 * @return The retrieved id string (contents of the id attribute), or "" if no shape or text item was found
446 */
447 Glib::ustring ClipboardManagerImpl::getShapeOrTextObjectId()
448 {
449 SPDocument *tempdoc = _retrieveClipboard(); // any target will do here
450 if ( tempdoc == NULL ) {
451 _userWarn(SP_ACTIVE_DESKTOP, _("Nothing on the clipboard."));
452 return "";
453 }
454 Inkscape::XML::Node *root = sp_document_repr_root(tempdoc);
456 Inkscape::XML::Node *repr = sp_repr_lookup_name(root, "svg:path", -1); // unlimited search depth
457 if ( repr == NULL )
458 repr = sp_repr_lookup_name(root, "svg:text", -1);
460 if ( repr == NULL ) {
461 _userWarn(SP_ACTIVE_DESKTOP, _("Clipboard does not contain a path."));
462 sp_document_unref(tempdoc);
463 return "";
464 }
465 gchar const *svgd = repr->attribute("id");
466 return svgd;
467 }
470 /**
471 * @brief Iterate over a list of items and copy them to the clipboard.
472 */
473 void ClipboardManagerImpl::_copySelection(Inkscape::Selection *selection)
474 {
475 GSList const *items = selection->itemList();
476 // copy the defs used by all items
477 for (GSList *i = const_cast<GSList *>(items) ; i != NULL ; i = i->next) {
478 _copyUsedDefs(SP_ITEM (i->data));
479 }
481 // copy the representation of the items
482 GSList *sorted_items = g_slist_copy(const_cast<GSList *>(items));
483 sorted_items = g_slist_sort(sorted_items, (GCompareFunc) sp_object_compare_position);
485 for (GSList *i = sorted_items ; i ; i = i->next) {
486 if (!SP_IS_ITEM(i->data)) continue;
487 Inkscape::XML::Node *obj = SP_OBJECT_REPR(i->data);
488 Inkscape::XML::Node *obj_copy = _copyNode(obj, _doc, _root);
490 // copy complete inherited style
491 SPCSSAttr *css = sp_repr_css_attr_inherited(obj, "style");
492 sp_repr_css_set(obj_copy, css, "style");
493 sp_repr_css_attr_unref(css);
495 // write the complete accumulated transform passed to us
496 // (we're dealing with unattached representations, so we write to their attributes
497 // instead of using sp_item_set_transform)
498 gchar *transform_str = sp_svg_transform_write(sp_item_i2doc_affine(SP_ITEM(i->data)));
499 obj_copy->setAttribute("transform", transform_str);
500 g_free(transform_str);
501 }
503 // copy style for Paste Style action
504 if (sorted_items) {
505 if(SP_IS_ITEM(sorted_items->data)) {
506 SPCSSAttr *style = take_style_from_item((SPItem *) sorted_items->data);
507 sp_repr_css_set(_clipnode, style, "style");
508 sp_repr_css_attr_unref(style);
509 }
511 // copy path effect from the first path
512 if (SP_IS_OBJECT(sorted_items->data)) {
513 gchar const *effect = SP_OBJECT_REPR(sorted_items->data)->attribute("inkscape:path-effect");
514 if (effect) {
515 _clipnode->setAttribute("inkscape:path-effect", effect);
516 }
517 }
518 }
520 NR::Maybe<NR::Rect> size = selection->bounds();
521 if (size) {
522 sp_repr_set_point(_clipnode, "min", size->min().to_2geom());
523 sp_repr_set_point(_clipnode, "max", size->max().to_2geom());
524 }
526 g_slist_free(sorted_items);
527 }
530 /**
531 * @brief Recursively copy all the definitions used by a given item to the clipboard defs
532 */
533 void ClipboardManagerImpl::_copyUsedDefs(SPItem *item)
534 {
535 // copy fill and stroke styles (patterns and gradients)
536 SPStyle *style = SP_OBJECT_STYLE(item);
538 if (style && (style->fill.isPaintserver())) {
539 SPObject *server = SP_OBJECT_STYLE_FILL_SERVER(item);
540 if (SP_IS_LINEARGRADIENT(server) || SP_IS_RADIALGRADIENT(server))
541 _copyGradient(SP_GRADIENT(server));
542 if (SP_IS_PATTERN(server))
543 _copyPattern(SP_PATTERN(server));
544 }
545 if (style && (style->stroke.isPaintserver())) {
546 SPObject *server = SP_OBJECT_STYLE_STROKE_SERVER(item);
547 if (SP_IS_LINEARGRADIENT(server) || SP_IS_RADIALGRADIENT(server))
548 _copyGradient(SP_GRADIENT(server));
549 if (SP_IS_PATTERN(server))
550 _copyPattern(SP_PATTERN(server));
551 }
553 // For shapes, copy all of the shape's markers
554 if (SP_IS_SHAPE(item)) {
555 SPShape *shape = SP_SHAPE (item);
556 for (int i = 0 ; i < SP_MARKER_LOC_QTY ; i++) {
557 if (shape->marker[i]) {
558 _copyNode(SP_OBJECT_REPR(SP_OBJECT(shape->marker[i])), _doc, _defs);
559 }
560 }
561 }
562 // For lpe items, copy liveeffect if applicable
563 if (SP_IS_LPE_ITEM(item)) {
564 SPLPEItem *lpeitem = SP_LPE_ITEM (item);
565 if (sp_lpe_item_has_path_effect(lpeitem)) {
566 _copyNode(SP_OBJECT_REPR(SP_OBJECT(sp_lpe_item_get_livepatheffectobject(lpeitem))), _doc, _defs);
567 }
568 }
569 // For 3D boxes, copy perspectives
570 if (SP_IS_BOX3D(item)) {
571 _copyNode(SP_OBJECT_REPR(SP_OBJECT(box3d_get_perspective(SP_BOX3D(item)))), _doc, _defs);
572 }
573 // Copy text paths
574 if (SP_IS_TEXT_TEXTPATH(item)) {
575 _copyTextPath(SP_TEXTPATH(sp_object_first_child(SP_OBJECT(item))));
576 }
577 // Copy clipping objects
578 if (item->clip_ref->getObject()) {
579 _copyNode(SP_OBJECT_REPR(item->clip_ref->getObject()), _doc, _defs);
580 }
581 // Copy mask objects
582 if (item->mask_ref->getObject()) {
583 SPObject *mask = item->mask_ref->getObject();
584 _copyNode(SP_OBJECT_REPR(mask), _doc, _defs);
585 // recurse into the mask for its gradients etc.
586 for (SPObject *o = SP_OBJECT(mask)->children ; o != NULL ; o = o->next) {
587 if (SP_IS_ITEM(o))
588 _copyUsedDefs(SP_ITEM(o));
589 }
590 }
591 // Copy filters
592 if (style->getFilter()) {
593 SPObject *filter = style->getFilter();
594 if (SP_IS_FILTER(filter)) {
595 _copyNode(SP_OBJECT_REPR(filter), _doc, _defs);
596 }
597 }
599 // recurse
600 for (SPObject *o = SP_OBJECT(item)->children ; o != NULL ; o = o->next) {
601 if (SP_IS_ITEM(o))
602 _copyUsedDefs(SP_ITEM(o));
603 }
604 }
607 /**
608 * @brief Copy a single gradient to the clipboard's defs element
609 */
610 void ClipboardManagerImpl::_copyGradient(SPGradient *gradient)
611 {
612 while (gradient) {
613 // climb up the refs, copying each one in the chain
614 _copyNode(SP_OBJECT_REPR(gradient), _doc, _defs);
615 gradient = gradient->ref->getObject();
616 }
617 }
620 /**
621 * @brief Copy a single pattern to the clipboard document's defs element
622 */
623 void ClipboardManagerImpl::_copyPattern(SPPattern *pattern)
624 {
625 // climb up the references, copying each one in the chain
626 while (pattern) {
627 _copyNode(SP_OBJECT_REPR(pattern), _doc, _defs);
629 // items in the pattern may also use gradients and other patterns, so recurse
630 for (SPObject *child = sp_object_first_child(SP_OBJECT(pattern)) ; child != NULL ; child = SP_OBJECT_NEXT(child) ) {
631 if (!SP_IS_ITEM (child)) continue;
632 _copyUsedDefs(SP_ITEM(child));
633 }
634 pattern = pattern->ref->getObject();
635 }
636 }
639 /**
640 * @brief Copy a text path to the clipboard's defs element
641 */
642 void ClipboardManagerImpl::_copyTextPath(SPTextPath *tp)
643 {
644 SPItem *path = sp_textpath_get_path_item(tp);
645 if(!path) return;
646 Inkscape::XML::Node *path_node = SP_OBJECT_REPR(path);
648 // Do not copy the text path to defs if it's already copied
649 if(sp_repr_lookup_child(_root, "id", path_node->attribute("id"))) return;
650 _copyNode(path_node, _doc, _defs);
651 }
654 /**
655 * @brief Copy a single XML node from one document to another
656 * @param node The node to be copied
657 * @param target_doc The document to which the node is to be copied
658 * @param parent The node in the target document which will become the parent of the copied node
659 * @return Pointer to the copied node
660 */
661 Inkscape::XML::Node *ClipboardManagerImpl::_copyNode(Inkscape::XML::Node *node, Inkscape::XML::Document *target_doc, Inkscape::XML::Node *parent)
662 {
663 Inkscape::XML::Node *dup = node->duplicate(target_doc);
664 parent->appendChild(dup);
665 Inkscape::GC::release(dup);
666 return dup;
667 }
670 /**
671 * @brief Paste the contents of a document into the active desktop
672 * @param clipdoc The document to paste
673 * @param in_place Whether to paste the selection where it was when copied
674 * @pre @c clipdoc is not empty and items can be added to the current layer
675 */
676 void ClipboardManagerImpl::_pasteDocument(SPDocument *clipdoc, bool in_place)
677 {
678 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
679 SPDocument *target_document = sp_desktop_document(desktop);
680 Inkscape::XML::Node
681 *root = sp_document_repr_root(clipdoc),
682 *target_parent = SP_OBJECT_REPR(desktop->currentLayer());
683 Inkscape::XML::Document *target_xmldoc = sp_document_repr_doc(target_document);
685 // copy definitions
686 _pasteDefs(clipdoc);
688 // copy objects
689 GSList *pasted_objects = NULL;
690 for (Inkscape::XML::Node *obj = root->firstChild() ; obj ; obj = obj->next()) {
691 // Don't copy metadata, defs, named views and internal clipboard contents to the document
692 if (!strcmp(obj->name(), "svg:defs")) continue;
693 if (!strcmp(obj->name(), "svg:metadata")) continue;
694 if (!strcmp(obj->name(), "sodipodi:namedview")) continue;
695 if (!strcmp(obj->name(), "inkscape:clipboard")) continue;
696 Inkscape::XML::Node *obj_copy = _copyNode(obj, target_xmldoc, target_parent);
697 pasted_objects = g_slist_prepend(pasted_objects, (gpointer) obj_copy);
698 }
700 Inkscape::Selection *selection = sp_desktop_selection(desktop);
701 selection->setReprList(pasted_objects);
703 // move the selection to the right position
704 if(in_place)
705 {
706 Inkscape::XML::Node *clipnode = sp_repr_lookup_name(root, "inkscape:clipboard", 1);
707 if (clipnode) {
708 Geom::Point min, max;
709 sp_repr_get_point(clipnode, "min", &min);
710 sp_repr_get_point(clipnode, "max", &max);
712 // this formula was discovered empyrically
713 min[Geom::Y] += ((max[Geom::Y] - min[Geom::Y]) - sp_document_height(target_document));
714 sp_selection_move_relative(selection, NR::Point(min));
715 }
716 }
717 // copied from former sp_selection_paste in selection-chemistry.cpp
718 else {
719 sp_document_ensure_up_to_date(target_document);
720 NR::Maybe<NR::Rect> sel_size = selection->bounds();
722 NR::Point m( desktop->point() );
723 if (sel_size) {
724 m -= sel_size->midpoint();
725 }
726 sp_selection_move_relative(selection, m);
727 }
729 g_slist_free(pasted_objects);
730 }
733 /**
734 * @brief Paste SVG defs from the document retrieved from the clipboard into the active document
735 * @param clipdoc The document to paste
736 * @pre @c clipdoc != NULL and pasting into the active document is possible
737 */
738 void ClipboardManagerImpl::_pasteDefs(SPDocument *clipdoc)
739 {
740 // boilerplate vars copied from _pasteDocument
741 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
742 SPDocument *target_document = sp_desktop_document(desktop);
743 Inkscape::XML::Node
744 *root = sp_document_repr_root(clipdoc),
745 *defs = sp_repr_lookup_name(root, "svg:defs", 1),
746 *target_defs = SP_OBJECT_REPR(SP_DOCUMENT_DEFS(target_document));
747 Inkscape::XML::Document *target_xmldoc = sp_document_repr_doc(target_document);
749 for (Inkscape::XML::Node *def = defs->firstChild() ; def ; def = def->next()) {
750 /// @todo TODO: implement def id collision resolution in ClipboardManagerImpl::_pasteDefs()
752 /*
753 // simplistic solution: when a collision occurs, add "a" to id until it's unique
754 Glib::ustring pasted_id = def->attribute("id");
755 if ( pasted_id.empty() ) continue; // defs without id are useless
756 Glib::ustring pasted_id_original = pasted_id;
758 while(sp_repr_lookup_child(target_defs, "id", pasted_id.data())) {
759 pasted_id.append("a");
760 }
762 if ( pasted_id != pasted_id_original ) {
763 def->setAttribute("id", pasted_id.data());
764 // Update the id in the rest of the document so there are no dangling references
765 // How to do that?
766 _changeIdReferences(clipdoc, pasted_id_original, pasted_id);
767 }
768 */
769 if (sp_repr_lookup_child(target_defs, "id", def->attribute("id")))
770 continue; // skip duplicate defs - temporary non-solution
772 _copyNode(def, target_xmldoc, target_defs);
773 }
774 }
777 /**
778 * @brief Retrieve a bitmap image from the clipboard and paste it into the active document
779 */
780 bool ClipboardManagerImpl::_pasteImage()
781 {
782 SPDocument *doc = SP_ACTIVE_DOCUMENT;
783 if ( doc == NULL ) return false;
785 // retrieve image data
786 Glib::RefPtr<Gdk::Pixbuf> img = _clipboard->wait_for_image();
787 if (!img) return false;
789 // Very stupid hack: Write into a file, then import the file into the document.
790 // To avoid using tmpfile and POSIX file handles, make the filename based on current time.
791 // This wasn't my idea, I just copied this from selection-chemistry.cpp
792 // and just can't think of something saner at the moment. Pasting more than
793 // one image per second will overwrite the image.
794 // However, I don't think anyone is able to copy a _different_ image into inkscape
795 // in 1 second.
796 time_t rawtime;
797 char image_filename[128];
798 gchar const *save_folder;
800 time(&rawtime);
801 strftime(image_filename, 128, "inkscape_pasted_image_%Y%m%d_%H%M%S.png", localtime( &rawtime ));
802 save_folder = (gchar const *) prefs_get_string_attribute("dialogs.save_as", "path");
804 gchar *image_path = g_build_filename(save_folder, image_filename, NULL);
805 img->save(image_path, "png");
806 file_import(doc, image_path, NULL);
807 g_free(image_path);
809 return true;
810 }
812 /**
813 * @brief Paste text into the selected text object or create a new one to hold it
814 */
815 bool ClipboardManagerImpl::_pasteText()
816 {
817 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
818 if ( desktop == NULL ) return false;
820 // if the text editing tool is active, paste the text into the active text object
821 if (tools_isactive(desktop, TOOLS_TEXT))
822 return sp_text_paste_inline(desktop->event_context);
824 // try to parse the text as a color and, if successful, apply it as the current style
825 SPCSSAttr *css = _parseColor(_clipboard->wait_for_text());
826 if (css) {
827 sp_desktop_set_style(desktop, css);
828 return true;
829 }
831 return false;
832 }
835 /**
836 * @brief Attempt to parse the passed string as a hexadecimal RGB or RGBA color
837 * @param text The Glib::ustring to parse
838 * @return New CSS style representation if the parsing was successful, NULL otherwise
839 */
840 SPCSSAttr *ClipboardManagerImpl::_parseColor(const Glib::ustring &text)
841 {
842 Glib::ustring::size_type len = text.bytes();
843 char *str = const_cast<char *>(text.data());
844 bool attempt_alpha = false;
845 if ( !str || ( *str == '\0' ) ) return NULL; // this is OK due to boolean short-circuit
847 // those conditionals guard against parsing e.g. the string "fab" as "fab000"
848 // (incomplete color) and "45fab71" as "45fab710" (incomplete alpha)
849 if ( *str == '#' ) {
850 if ( len < 7 ) return NULL;
851 if ( len >= 9 ) attempt_alpha = true;
852 } else {
853 if ( len < 6 ) return NULL;
854 if ( len >= 8 ) attempt_alpha = true;
855 }
857 unsigned int color = 0, alpha = 0xff;
859 // skip a leading #, if present
860 if ( *str == '#' ) ++str;
862 // try to parse first 6 digits
863 int res = sscanf(str, "%6x", &color);
864 if ( res && ( res != EOF ) ) {
865 if (attempt_alpha) {// try to parse alpha if there's enough characters
866 sscanf(str + 6, "%2x", &alpha);
867 if ( !res || res == EOF ) alpha = 0xff;
868 }
870 SPCSSAttr *color_css = sp_repr_css_attr_new();
872 // print and set properties
873 gchar color_str[16];
874 g_snprintf(color_str, 16, "#%06x", color);
875 sp_repr_css_set_property(color_css, "fill", color_str);
877 float opacity = static_cast<float>(alpha)/static_cast<float>(0xff);
878 if (opacity > 1.0) opacity = 1.0; // safeguard
879 Inkscape::CSSOStringStream opcss;
880 opcss << opacity;
881 sp_repr_css_set_property(color_css, "fill-opacity", opcss.str().data());
882 return color_css;
883 }
884 return NULL;
885 }
888 /**
889 * @brief Applies a pasted path effect to a given item
890 */
891 void ClipboardManagerImpl::_applyPathEffect(SPItem *item, gchar const *effect)
892 {
893 if ( item == NULL ) return;
894 if ( SP_IS_RECT(item) ) return;
896 if (SP_IS_LPE_ITEM(item))
897 {
898 SPLPEItem *lpeitem = SP_LPE_ITEM(item);
899 SPObject *obj = sp_uri_reference_resolve(_clipboardSPDoc, effect);
900 if (!obj) return;
901 // if the effect is not used by anyone, we might as well take it
902 LivePathEffectObject *lpeobj = LIVEPATHEFFECT(obj)->fork_private_if_necessary(1);
903 sp_lpe_item_set_path_effect(lpeitem, lpeobj);
904 }
905 }
908 /**
909 * @brief Retrieve the clipboard contents as a document
910 * @return Clipboard contents converted to SPDocument, or NULL if no suitable content was present
911 */
912 SPDocument *ClipboardManagerImpl::_retrieveClipboard(Glib::ustring required_target)
913 {
914 Glib::ustring best_target;
915 if ( required_target == "" )
916 best_target = _getBestTarget();
917 else
918 best_target = required_target;
920 if ( best_target == "" ) {
921 return NULL;
922 }
924 if ( !_clipboard->wait_is_target_available(best_target) ) {
925 return NULL;
926 }
928 // doing this synchronously makes better sense
929 // TODO: use another method because this one is badly broken imo.
930 // from documentation: "Returns: A SelectionData object, which will be invalid if retrieving the given target failed."
931 // I don't know how to check whether an object is 'valid' or not, unusable if that's not possible...
932 Gtk::SelectionData sel = _clipboard->wait_for_contents(best_target);
933 Glib::ustring target = sel.get_target(); // this can crash if the result was invalid of last function. No way to check for this :(
935 // there is no specific plain SVG input extension, so if we can paste the Inkscape SVG format,
936 // we use the image/svg+xml mimetype to look up the input extension
937 if(target == "image/x-inkscape-svg")
938 target = "image/svg+xml";
940 Inkscape::Extension::DB::InputList inlist;
941 Inkscape::Extension::db.get_input_list(inlist);
942 Inkscape::Extension::DB::InputList::const_iterator in = inlist.begin();
943 for (; in != inlist.end() && target != (*in)->get_mimetype() ; ++in);
944 if ( in == inlist.end() )
945 return NULL; // this shouldn't happen unless _getBestTarget returns something bogus
947 // FIXME: Temporary hack until we add memory input.
948 // Save the clipboard contents to some file, then read it
949 gchar *filename = g_build_filename( g_get_tmp_dir(), "inkscape-clipboard-import", NULL );
950 g_file_set_contents(filename, (const gchar *) sel.get_data(), sel.get_length(), NULL);
952 SPDocument *tempdoc = (*in)->open(filename);
953 g_unlink(filename);
954 g_free(filename);
956 return tempdoc;
957 }
960 /**
961 * @brief Callback called when some other application requests data from Inkscape
962 *
963 * Finds a suitable output extension to save the internal clipboard document,
964 * then saves it to memory and sets the clipboard contents.
965 */
966 void ClipboardManagerImpl::_onGet(Gtk::SelectionData &sel, guint info)
967 {
968 g_assert( _clipboardSPDoc != NULL );
970 const Glib::ustring target = sel.get_target();
971 if(target == "") return; // this shouldn't happen
973 Inkscape::Extension::DB::OutputList outlist;
974 Inkscape::Extension::db.get_output_list(outlist);
975 Inkscape::Extension::DB::OutputList::const_iterator out = outlist.begin();
976 for ( ; out != outlist.end() && target != (*out)->get_mimetype() ; ++out);
977 if ( out == outlist.end() ) return; // this also shouldn't happen
979 // FIXME: Temporary hack until we add support for memory output.
980 // Save to a temporary file, read it back and then set the clipboard contents
981 gchar *filename = g_build_filename( g_get_tmp_dir(), "inkscape-clipboard-export", NULL );
982 gsize len; gchar *data;
984 (*out)->save(_clipboardSPDoc, filename);
985 g_file_get_contents(filename, &data, &len, NULL);
986 g_unlink(filename); // delete the temporary file
987 g_free(filename);
989 sel.set(8, (guint8 const *) data, len);
990 }
993 /**
994 * @brief Callback when someone else takes the clipboard
995 *
996 * When the clipboard owner changes, this callback clears the internal clipboard document
997 * to reduce memory usage.
998 */
999 void ClipboardManagerImpl::_onClear()
1000 {
1001 // why is this called before _onGet???
1002 //_discardInternalClipboard();
1003 }
1006 /**
1007 * @brief Creates an internal clipboard document from scratch
1008 */
1009 void ClipboardManagerImpl::_createInternalClipboard()
1010 {
1011 if ( _clipboardSPDoc == NULL ) {
1012 _clipboardSPDoc = sp_document_new(NULL, false, true);
1013 //g_assert( _clipboardSPDoc != NULL );
1014 _defs = SP_OBJECT_REPR(SP_DOCUMENT_DEFS(_clipboardSPDoc));
1015 _doc = sp_document_repr_doc(_clipboardSPDoc);
1016 _root = sp_document_repr_root(_clipboardSPDoc);
1018 _clipnode = _doc->createElement("inkscape:clipboard");
1019 _root->appendChild(_clipnode);
1020 Inkscape::GC::release(_clipnode);
1021 }
1022 }
1025 /**
1026 * @brief Deletes the internal clipboard document
1027 */
1028 void ClipboardManagerImpl::_discardInternalClipboard()
1029 {
1030 if ( _clipboardSPDoc != NULL ) {
1031 sp_document_unref(_clipboardSPDoc);
1032 _clipboardSPDoc = NULL;
1033 _defs = NULL;
1034 _doc = NULL;
1035 _root = NULL;
1036 _clipnode = NULL;
1037 }
1038 }
1041 /**
1042 * @brief Get the scale to resize an item, based on the command and desktop state
1043 */
1044 NR::scale ClipboardManagerImpl::_getScale(Geom::Point &min, Geom::Point &max, NR::Rect &obj_rect, bool apply_x, bool apply_y)
1045 {
1046 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1047 double scale_x = 1.0;
1048 double scale_y = 1.0;
1050 if (apply_x) {
1051 scale_x = (max[Geom::X] - min[Geom::X]) / obj_rect.extent(NR::X);
1052 }
1053 if (apply_y) {
1054 scale_y = (max[Geom::Y] - min[Geom::Y]) / obj_rect.extent(NR::Y);
1055 }
1056 // If the "lock aspect ratio" button is pressed and we paste only a single coordinate,
1057 // resize the second one by the same ratio too
1058 if (desktop->isToolboxButtonActive("lock")) {
1059 if (apply_x && !apply_y) scale_y = scale_x;
1060 if (apply_y && !apply_x) scale_x = scale_y;
1061 }
1063 return NR::scale(scale_x, scale_y);
1064 }
1067 /**
1068 * @brief Find the most suitable clipboard target
1069 */
1070 Glib::ustring ClipboardManagerImpl::_getBestTarget()
1071 {
1072 std::list<Glib::ustring> targets = _clipboard->wait_for_targets();
1074 // clipboard target debugging snippet
1075 /*
1076 g_debug("Begin clipboard targets");
1077 for ( std::list<Glib::ustring>::iterator x = targets.begin() ; x != targets.end(); ++x )
1078 g_debug("Clipboard target: %s", (*x).data());
1079 g_debug("End clipboard targets\n");
1080 //*/
1082 for(std::list<Glib::ustring>::iterator i = _preferred_targets.begin() ;
1083 i != _preferred_targets.end() ; ++i)
1084 {
1085 if ( std::find(targets.begin(), targets.end(), *i) != targets.end() )
1086 return *i;
1087 }
1088 if (_clipboard->wait_is_image_available())
1089 return CLIPBOARD_GDK_PIXBUF_TARGET;
1090 if (_clipboard->wait_is_text_available())
1091 return CLIPBOARD_TEXT_TARGET;
1093 return "";
1094 }
1097 /**
1098 * @brief Set the clipboard targets to reflect the mimetypes Inkscape can output
1099 */
1100 void ClipboardManagerImpl::_setClipboardTargets()
1101 {
1102 Inkscape::Extension::DB::OutputList outlist;
1103 Inkscape::Extension::db.get_output_list(outlist);
1104 std::list<Gtk::TargetEntry> target_list;
1105 for (Inkscape::Extension::DB::OutputList::const_iterator out = outlist.begin() ; out != outlist.end() ; ++out) {
1106 target_list.push_back(Gtk::TargetEntry( (*out)->get_mimetype() ));
1107 }
1109 _clipboard->set(target_list,
1110 sigc::mem_fun(*this, &ClipboardManagerImpl::_onGet),
1111 sigc::mem_fun(*this, &ClipboardManagerImpl::_onClear));
1112 }
1115 /**
1116 * @brief Set the string representation of a 32-bit RGBA color as the clipboard contents
1117 */
1118 void ClipboardManagerImpl::_setClipboardColor(guint32 color)
1119 {
1120 gchar colorstr[16];
1121 g_snprintf(colorstr, 16, "%08x", color);
1122 _clipboard->set_text(colorstr);
1123 }
1126 /**
1127 * @brief Put a notification on the mesage stack
1128 */
1129 void ClipboardManagerImpl::_userWarn(SPDesktop *desktop, char const *msg)
1130 {
1131 desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, msg);
1132 }
1136 /* #######################################
1137 ClipboardManager class
1138 ####################################### */
1140 ClipboardManager *ClipboardManager::_instance = NULL;
1142 ClipboardManager::ClipboardManager() {}
1143 ClipboardManager::~ClipboardManager() {}
1144 ClipboardManager *ClipboardManager::get()
1145 {
1146 if ( _instance == NULL )
1147 _instance = new ClipboardManagerImpl;
1148 return _instance;
1149 }
1151 } // namespace Inkscape
1152 } // namespace IO
1154 /*
1155 Local Variables:
1156 mode:c++
1157 c-file-style:"stroustrup"
1158 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1159 indent-tabs-mode:nil
1160 fill-column:99
1161 End:
1162 */
1163 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :