842f10d521db01666d5a00f4b879c2da511ddce1
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 #ifdef HAVE_CONFIG_H
19 # include "config.h"
20 #endif
21 #include "path-prefix.h"
23 #include "ui/clipboard.h"
25 // TODO: reduce header bloat if possible
27 #include <list>
28 #include <algorithm>
29 #include <gtkmm/clipboard.h>
30 #include <glibmm/ustring.h>
31 #include <glibmm/i18n.h>
32 #include <glib/gstdio.h> // for g_file_set_contents etc., used in _onGet and paste
33 #include "gc-core.h"
34 #include "xml/repr.h"
35 #include "inkscape.h"
36 #include "io/stringstream.h"
37 #include "desktop.h"
38 #include "desktop-handles.h"
39 #include "desktop-style.h" // for sp_desktop_set_style, used in _pasteStyle
40 #include "document.h"
41 #include "document-private.h"
42 #include "selection.h"
43 #include "message-stack.h"
44 #include "context-fns.h"
45 #include "dropper-context.h" // used in copy()
46 #include "style.h"
47 #include "extension/db.h" // extension database
48 #include "extension/input.h"
49 #include "extension/output.h"
50 #include "selection-chemistry.h"
51 #include "libnr/nr-rect.h"
52 #include "box3d.h"
53 #include "gradient-drag.h"
54 #include "sp-item.h"
55 #include "sp-item-transform.h" // for sp_item_scale_rel, used in _pasteSize
56 #include "sp-path.h"
57 #include "sp-pattern.h"
58 #include "sp-shape.h"
59 #include "sp-gradient.h"
60 #include "sp-gradient-reference.h"
61 #include "sp-gradient-fns.h"
62 #include "sp-linear-gradient-fns.h"
63 #include "sp-radial-gradient-fns.h"
64 #include "sp-clippath.h"
65 #include "sp-mask.h"
66 #include "sp-textpath.h"
67 #include "live_effects/lpeobject.h"
68 #include "live_effects/parameter/path.h"
69 #include "svg/svg.h" // for sp_svg_transform_write, used in _copySelection
70 #include "svg/css-ostringstream.h" // used in _parseColor
71 #include "file.h" // for file_import, used in _pasteImage
72 #include "prefs-utils.h" // for prefs_get_string_attribute, used in _pasteImage
73 #include "text-context.h"
74 #include "text-editing.h"
75 #include "tools-switch.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();
100 ClipboardManagerImpl();
101 ~ClipboardManagerImpl();
103 private:
104 void _copySelection(Inkscape::Selection *);
105 void _copyUsedDefs(SPItem *);
106 void _copyGradient(SPGradient *);
107 void _copyPattern(SPPattern *);
108 void _copyTextPath(SPTextPath *);
109 Inkscape::XML::Node *_copyNode(Inkscape::XML::Node *, Inkscape::XML::Document *, Inkscape::XML::Node *);
111 void _pasteDocument(SPDocument *, bool in_place);
112 void _pasteDefs(SPDocument *);
113 bool _pasteImage();
114 bool _pasteText();
115 SPCSSAttr *_parseColor(const Glib::ustring &);
116 void _applyPathEffect(SPItem *, gchar const *);
117 SPDocument *_retrieveClipboard(Glib::ustring = "");
119 // clipboard callbacks
120 void _onGet(Gtk::SelectionData &, guint);
121 void _onClear();
123 // various helpers
124 void _createInternalClipboard();
125 void _discardInternalClipboard();
126 Inkscape::XML::Node *_createClipNode();
127 NR::scale _getScale(Geom::Point &, Geom::Point &, NR::Rect &, bool, bool);
128 Glib::ustring _getBestTarget();
129 void _setClipboardTargets();
130 void _setClipboardColor(guint32);
131 void _userWarn(SPDesktop *, char const *);
133 // private properites
134 SPDocument *_clipboardSPDoc; ///< Document that stores the clipboard until someone requests it
135 Inkscape::XML::Node *_defs; ///< Reference to the clipboard document's defs node
136 Inkscape::XML::Node *_root; ///< Reference to the clipboard's root node
137 Inkscape::XML::Node *_clipnode; ///< The node that holds extra information
138 Inkscape::XML::Document *_doc; ///< Reference to the clipboard's Inkscape::XML::Document
140 Glib::RefPtr<Gtk::Clipboard> _clipboard; ///< Handle to the system wide clipboard - for convenience
141 std::list<Glib::ustring> _preferred_targets; ///< List of supported clipboard targets
142 };
145 ClipboardManagerImpl::ClipboardManagerImpl()
146 : _clipboardSPDoc(NULL),
147 _defs(NULL),
148 _root(NULL),
149 _clipnode(NULL),
150 _doc(NULL),
151 _clipboard( Gtk::Clipboard::get() )
152 {
153 // push supported clipboard targets, in order of preference
154 _preferred_targets.push_back("image/x-inkscape-svg");
155 _preferred_targets.push_back("image/svg+xml");
156 _preferred_targets.push_back("image/svg+xml-compressed");
157 #ifdef WIN32
158 _preferred_targets.push_back("image/x-emf");
159 #endif
160 _preferred_targets.push_back("application/pdf");
161 _preferred_targets.push_back("image/x-adobe-illustrator");
162 }
165 ClipboardManagerImpl::~ClipboardManagerImpl() {}
168 /**
169 * @brief Copy selection contents to the clipboard
170 */
171 void ClipboardManagerImpl::copy()
172 {
173 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
174 if ( desktop == NULL ) return;
175 Inkscape::Selection *selection = sp_desktop_selection(desktop);
177 // Special case for when the gradient dragger is active - copies gradient color
178 if (desktop->event_context->get_drag()) {
179 GrDrag *drag = desktop->event_context->get_drag();
180 if (drag->hasSelection()) {
181 _setClipboardColor(drag->getColor());
182 _discardInternalClipboard();
183 return;
184 }
185 }
187 // Special case for when the color picker ("dropper") is active - copies color under cursor
188 if (tools_isactive(desktop, TOOLS_DROPPER)) {
189 _setClipboardColor(sp_dropper_context_get_color(desktop->event_context));
190 _discardInternalClipboard();
191 return;
192 }
194 // Special case for when the text tool is active - if some text is selected, copy plain text,
195 // not the object that holds it
196 if (tools_isactive(desktop, TOOLS_TEXT)) {
197 Glib::ustring selected_text = sp_text_get_selected_text(desktop->event_context);
198 if (!selected_text.empty()) {
199 _clipboard->set_text(selected_text);
200 _discardInternalClipboard();
201 return;
202 }
203 }
205 if (selection->isEmpty()) { // check whether something is selected
206 _userWarn(desktop, _("Nothing was copied."));
207 return;
208 }
209 _discardInternalClipboard();
211 _createInternalClipboard(); // construct a new clipboard document
212 _copySelection(selection); // copy all items in the selection to the internal clipboard
213 fit_canvas_to_drawing(_clipboardSPDoc);
215 _setClipboardTargets();
216 }
219 /**
220 * @brief Copy a Live Path Effect path parameter to the clipboard
221 * @param pp The path parameter to store in the clipboard
222 */
223 void ClipboardManagerImpl::copyPathParameter(Inkscape::LivePathEffect::PathParam *pp)
224 {
225 if ( pp == NULL ) return;
226 gchar *svgd = pp->param_writeSVGValue();
227 if ( svgd == NULL || *svgd == '\0' ) return;
229 _discardInternalClipboard();
230 _createInternalClipboard();
232 Inkscape::XML::Node *pathnode = _doc->createElement("svg:path");
233 pathnode->setAttribute("d", svgd);
234 g_free(svgd);
235 _root->appendChild(pathnode);
236 Inkscape::GC::release(pathnode);
238 fit_canvas_to_drawing(_clipboardSPDoc);
239 _setClipboardTargets();
240 }
242 /**
243 * @brief Paste from the system clipboard into the active desktop
244 * @param in_place Whether to put the contents where they were when copied
245 */
246 bool ClipboardManagerImpl::paste(bool in_place)
247 {
248 // do any checking whether we really are able to paste before requesting the contents
249 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
250 if ( desktop == NULL ) return false;
251 if ( Inkscape::have_viable_layer(desktop, desktop->messageStack()) == false ) return false;
253 Glib::ustring target = _getBestTarget();
255 // Special cases of clipboard content handling go here 00ff00
256 // Note that target priority is determined in _getBestTarget.
257 // TODO: Handle x-special/gnome-copied-files and text/uri-list to support pasting files
259 // if there is an image on the clipboard, paste it
260 if ( target == CLIPBOARD_GDK_PIXBUF_TARGET ) return _pasteImage();
261 // if there's only text, paste it into a selected text object or create a new one
262 if ( target == CLIPBOARD_TEXT_TARGET ) return _pasteText();
264 // otherwise, use the import extensions
265 SPDocument *tempdoc = _retrieveClipboard(target);
266 if ( tempdoc == NULL ) {
267 _userWarn(desktop, _("Nothing on the clipboard."));
268 return false;
269 }
271 _pasteDocument(tempdoc, in_place);
272 sp_document_unref(tempdoc);
274 return true;
275 }
278 /**
279 * @brief Implements the Paste Style action
280 */
281 bool ClipboardManagerImpl::pasteStyle()
282 {
283 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
284 if (desktop == NULL) return false;
286 // check whether something is selected
287 Inkscape::Selection *selection = sp_desktop_selection(desktop);
288 if (selection->isEmpty()) {
289 _userWarn(desktop, _("Select <b>object(s)</b> to paste style to."));
290 return false;
291 }
293 SPDocument *tempdoc = _retrieveClipboard("image/x-inkscape-svg");
294 if ( tempdoc == NULL ) {
295 _userWarn(desktop, _("No style on the clipboard."));
296 return false;
297 }
299 Inkscape::XML::Node
300 *root = sp_document_repr_root(tempdoc),
301 *clipnode = sp_repr_lookup_name(root, "inkscape:clipboard", 1);
303 bool pasted = false;
305 if (clipnode) {
306 _pasteDefs(tempdoc);
307 SPCSSAttr *style = sp_repr_css_attr(clipnode, "style");
308 sp_desktop_set_style(desktop, style);
309 pasted = true;
310 }
311 else {
312 _userWarn(desktop, _("No style on the clipboard."));
313 }
315 sp_document_unref(tempdoc);
316 return pasted;
317 }
320 /**
321 * @brief Resize the selection or each object in the selection to match the clipboard's size
322 * @param separately Whether to scale each object in the selection separately
323 * @param apply_x Whether to scale the width of objects / selection
324 * @param apply_y Whether to scale the height of objects / selection
325 */
326 bool ClipboardManagerImpl::pasteSize(bool separately, bool apply_x, bool apply_y)
327 {
328 if(!apply_x && !apply_y) return false; // pointless parameters
330 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
331 if ( desktop == NULL ) return false;
332 Inkscape::Selection *selection = sp_desktop_selection(desktop);
333 if (selection->isEmpty()) {
334 _userWarn(desktop, _("Select <b>object(s)</b> to paste size to."));
335 return false;
336 }
338 // FIXME: actually, this should accept arbitrary documents
339 SPDocument *tempdoc = _retrieveClipboard("image/x-inkscape-svg");
340 if ( tempdoc == NULL ) {
341 _userWarn(desktop, _("No size on the clipboard."));
342 return false;
343 }
345 // retrieve size ifomration from the clipboard
346 Inkscape::XML::Node *root = sp_document_repr_root(tempdoc);
347 Inkscape::XML::Node *clipnode = sp_repr_lookup_name(root, "inkscape:clipboard", 1);
348 bool pasted = false;
349 if (clipnode) {
350 Geom::Point min, max;
351 sp_repr_get_point(clipnode, "min", &min);
352 sp_repr_get_point(clipnode, "max", &max);
354 // resize each object in the selection
355 if (separately) {
356 for (GSList *i = const_cast<GSList*>(selection->itemList()) ; i ; i = i->next) {
357 SPItem *item = SP_ITEM(i->data);
358 NR::Maybe<NR::Rect> obj_size = sp_item_bbox_desktop(item);
359 if ( !obj_size || obj_size->isEmpty() ) continue;
360 sp_item_scale_rel(item, _getScale(min, max, *obj_size, apply_x, apply_y));
361 }
362 }
363 // resize the selection as a whole
364 else {
365 NR::Maybe<NR::Rect> sel_size = selection->bounds();
366 if ( sel_size && !sel_size->isEmpty() ) {
367 sp_selection_scale_relative(selection, sel_size->midpoint(),
368 _getScale(min, max, *sel_size, apply_x, apply_y));
369 }
370 }
371 pasted = true;
372 }
373 sp_document_unref(tempdoc);
374 return pasted;
375 }
378 /**
379 * @brief Applies a path effect from the clipboard to the selected path
380 */
381 bool ClipboardManagerImpl::pastePathEffect()
382 {
383 /** @todo FIXME: pastePathEffect crashes when moving the path with the applied effect,
384 segfaulting in fork_private_if_necessary(). */
386 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
387 if ( desktop == NULL ) return false;
388 Inkscape::Selection *selection = sp_desktop_selection(desktop);
389 if (selection->isEmpty()) {
390 _userWarn(desktop, _("Select <b>object(s)</b> to paste live path effect to."));
391 return false;
392 }
394 Inkscape::XML::Node *root, *clipnode;
395 gchar const *effect;
396 SPDocument *tempdoc = _retrieveClipboard("image/x-inkscape-svg");
397 if ( tempdoc == NULL ) goto no_effect;
399 root = sp_document_repr_root(tempdoc),
400 clipnode = sp_repr_lookup_name(root, "inkscape:clipboard", 1);
401 if ( clipnode == NULL ) goto no_effect;
402 effect = clipnode->attribute("inkscape:path-effect");
403 if ( effect == NULL ) goto no_effect;
405 _pasteDefs(tempdoc);
406 for (GSList *item = const_cast<GSList *>(selection->itemList()) ; item ; item = item->next) {
407 _applyPathEffect(reinterpret_cast<SPItem*>(item->data), effect);
408 }
409 return true;
411 no_effect:
412 _userWarn(desktop, _("No effect on the clipboard."));
413 return false;
414 }
417 /**
418 * @brief Get LPE path data from the clipboard
419 * @return The retrieved path data (contents of the d attribute), or "" if no path was found
420 */
421 Glib::ustring ClipboardManagerImpl::getPathParameter()
422 {
423 SPDocument *tempdoc = _retrieveClipboard(); // any target will do here
424 if ( tempdoc == NULL ) {
425 _userWarn(SP_ACTIVE_DESKTOP, _("Nothing on the clipboard."));
426 return "";
427 }
428 Inkscape::XML::Node
429 *root = sp_document_repr_root(tempdoc),
430 *path = sp_repr_lookup_name(root, "svg:path", -1); // unlimited search depth
431 if ( path == NULL ) {
432 _userWarn(SP_ACTIVE_DESKTOP, _("Clipboard does not contain a path."));
433 sp_document_unref(tempdoc);
434 return "";
435 }
436 gchar const *svgd = path->attribute("d");
437 return svgd;
438 }
441 /**
442 * @brief Get object id of a shape or text item from the clipboard
443 * @return The retrieved id string (contents of the id attribute), or "" if no shape or text item was found
444 */
445 Glib::ustring ClipboardManagerImpl::getShapeOrTextObjectId()
446 {
447 SPDocument *tempdoc = _retrieveClipboard(); // any target will do here
448 if ( tempdoc == NULL ) {
449 _userWarn(SP_ACTIVE_DESKTOP, _("Nothing on the clipboard."));
450 return "";
451 }
452 Inkscape::XML::Node *root = sp_document_repr_root(tempdoc);
454 Inkscape::XML::Node *repr = sp_repr_lookup_name(root, "svg:path", -1); // unlimited search depth
455 if ( repr == NULL )
456 repr = sp_repr_lookup_name(root, "svg:text", -1);
458 if ( repr == NULL ) {
459 _userWarn(SP_ACTIVE_DESKTOP, _("Clipboard does not contain a path."));
460 sp_document_unref(tempdoc);
461 return "";
462 }
463 gchar const *svgd = repr->attribute("id");
464 return svgd;
465 }
468 /**
469 * @brief Iterate over a list of items and copy them to the clipboard.
470 */
471 void ClipboardManagerImpl::_copySelection(Inkscape::Selection *selection)
472 {
473 GSList const *items = selection->itemList();
474 // copy the defs used by all items
475 for (GSList *i = const_cast<GSList *>(items) ; i != NULL ; i = i->next) {
476 _copyUsedDefs(SP_ITEM (i->data));
477 }
479 // copy the representation of the items
480 GSList *sorted_items = g_slist_copy(const_cast<GSList *>(items));
481 sorted_items = g_slist_sort(sorted_items, (GCompareFunc) sp_object_compare_position);
483 for (GSList *i = sorted_items ; i ; i = i->next) {
484 if (!SP_IS_ITEM(i->data)) continue;
485 Inkscape::XML::Node *obj = SP_OBJECT_REPR(i->data);
486 Inkscape::XML::Node *obj_copy = _copyNode(obj, _doc, _root);
488 // copy complete inherited style
489 SPCSSAttr *css = sp_repr_css_attr_inherited(obj, "style");
490 sp_repr_css_set(obj_copy, css, "style");
491 sp_repr_css_attr_unref(css);
493 // write the complete accumulated transform passed to us
494 // (we're dealing with unattached representations, so we write to their attributes
495 // instead of using sp_item_set_transform)
496 gchar *transform_str = sp_svg_transform_write(sp_item_i2doc_affine(SP_ITEM(i->data)));
497 obj_copy->setAttribute("transform", transform_str);
498 g_free(transform_str);
499 }
501 // copy style for Paste Style action
502 if (sorted_items) {
503 if(SP_IS_ITEM(sorted_items->data)) {
504 SPCSSAttr *style = take_style_from_item((SPItem *) sorted_items->data);
505 sp_repr_css_set(_clipnode, style, "style");
506 sp_repr_css_attr_unref(style);
507 }
509 // copy path effect from the first path
510 if (SP_IS_OBJECT(sorted_items->data)) {
511 gchar const *effect = SP_OBJECT_REPR(sorted_items->data)->attribute("inkscape:path-effect");
512 if (effect) {
513 _clipnode->setAttribute("inkscape:path-effect", effect);
514 }
515 }
516 }
518 NR::Maybe<NR::Rect> size = selection->bounds();
519 if (size) {
520 sp_repr_set_point(_clipnode, "min", size->min().to_2geom());
521 sp_repr_set_point(_clipnode, "max", size->max().to_2geom());
522 }
524 g_slist_free(sorted_items);
525 }
528 /**
529 * @brief Recursively copy all the definitions used by a given item to the clipboard defs
530 */
531 void ClipboardManagerImpl::_copyUsedDefs(SPItem *item)
532 {
533 // copy fill and stroke styles (patterns and gradients)
534 SPStyle *style = SP_OBJECT_STYLE(item);
536 if (style && (style->fill.isPaintserver())) {
537 SPObject *server = SP_OBJECT_STYLE_FILL_SERVER(item);
538 if (SP_IS_LINEARGRADIENT(server) || SP_IS_RADIALGRADIENT(server))
539 _copyGradient(SP_GRADIENT(server));
540 if (SP_IS_PATTERN(server))
541 _copyPattern(SP_PATTERN(server));
542 }
543 if (style && (style->stroke.isPaintserver())) {
544 SPObject *server = SP_OBJECT_STYLE_STROKE_SERVER(item);
545 if (SP_IS_LINEARGRADIENT(server) || SP_IS_RADIALGRADIENT(server))
546 _copyGradient(SP_GRADIENT(server));
547 if (SP_IS_PATTERN(server))
548 _copyPattern(SP_PATTERN(server));
549 }
551 // For shapes, copy all of the shape's markers
552 if (SP_IS_SHAPE(item)) {
553 SPShape *shape = SP_SHAPE (item);
554 for (int i = 0 ; i < SP_MARKER_LOC_QTY ; i++) {
555 if (shape->marker[i]) {
556 _copyNode(SP_OBJECT_REPR(SP_OBJECT(shape->marker[i])), _doc, _defs);
557 }
558 }
559 }
560 // For lpe items, copy liveeffect if applicable
561 if (SP_IS_LPE_ITEM(item)) {
562 SPLPEItem *lpeitem = SP_LPE_ITEM (item);
563 if (sp_lpe_item_has_path_effect(lpeitem)) {
564 _copyNode(SP_OBJECT_REPR(SP_OBJECT(sp_lpe_item_get_livepatheffectobject(lpeitem))), _doc, _defs);
565 }
566 }
567 // For 3D boxes, copy perspectives
568 if (SP_IS_BOX3D(item)) {
569 _copyNode(SP_OBJECT_REPR(SP_OBJECT(box3d_get_perspective(SP_BOX3D(item)))), _doc, _defs);
570 }
571 // Copy text paths
572 if (SP_IS_TEXT_TEXTPATH(item)) {
573 _copyTextPath(SP_TEXTPATH(sp_object_first_child(SP_OBJECT(item))));
574 }
575 // Copy clipping objects
576 if (item->clip_ref->getObject()) {
577 _copyNode(SP_OBJECT_REPR(item->clip_ref->getObject()), _doc, _defs);
578 }
579 // Copy mask objects
580 if (item->mask_ref->getObject()) {
581 SPObject *mask = item->mask_ref->getObject();
582 _copyNode(SP_OBJECT_REPR(mask), _doc, _defs);
583 // recurse into the mask for its gradients etc.
584 for (SPObject *o = SP_OBJECT(mask)->children ; o != NULL ; o = o->next) {
585 if (SP_IS_ITEM(o))
586 _copyUsedDefs(SP_ITEM(o));
587 }
588 }
589 // Copy filters
590 if (style->getFilter()) {
591 SPObject *filter = style->getFilter();
592 if (SP_IS_FILTER(filter)) {
593 _copyNode(SP_OBJECT_REPR(filter), _doc, _defs);
594 }
595 }
597 // recurse
598 for (SPObject *o = SP_OBJECT(item)->children ; o != NULL ; o = o->next) {
599 if (SP_IS_ITEM(o))
600 _copyUsedDefs(SP_ITEM(o));
601 }
602 }
605 /**
606 * @brief Copy a single gradient to the clipboard's defs element
607 */
608 void ClipboardManagerImpl::_copyGradient(SPGradient *gradient)
609 {
610 while (gradient) {
611 // climb up the refs, copying each one in the chain
612 _copyNode(SP_OBJECT_REPR(gradient), _doc, _defs);
613 gradient = gradient->ref->getObject();
614 }
615 }
618 /**
619 * @brief Copy a single pattern to the clipboard document's defs element
620 */
621 void ClipboardManagerImpl::_copyPattern(SPPattern *pattern)
622 {
623 // climb up the references, copying each one in the chain
624 while (pattern) {
625 _copyNode(SP_OBJECT_REPR(pattern), _doc, _defs);
627 // items in the pattern may also use gradients and other patterns, so recurse
628 for (SPObject *child = sp_object_first_child(SP_OBJECT(pattern)) ; child != NULL ; child = SP_OBJECT_NEXT(child) ) {
629 if (!SP_IS_ITEM (child)) continue;
630 _copyUsedDefs(SP_ITEM(child));
631 }
632 pattern = pattern->ref->getObject();
633 }
634 }
637 /**
638 * @brief Copy a text path to the clipboard's defs element
639 */
640 void ClipboardManagerImpl::_copyTextPath(SPTextPath *tp)
641 {
642 SPItem *path = sp_textpath_get_path_item(tp);
643 if(!path) return;
644 Inkscape::XML::Node *path_node = SP_OBJECT_REPR(path);
646 // Do not copy the text path to defs if it's already copied
647 if(sp_repr_lookup_child(_root, "id", path_node->attribute("id"))) return;
648 _copyNode(path_node, _doc, _defs);
649 }
652 /**
653 * @brief Copy a single XML node from one document to another
654 * @param node The node to be copied
655 * @param target_doc The document to which the node is to be copied
656 * @param parent The node in the target document which will become the parent of the copied node
657 * @return Pointer to the copied node
658 */
659 Inkscape::XML::Node *ClipboardManagerImpl::_copyNode(Inkscape::XML::Node *node, Inkscape::XML::Document *target_doc, Inkscape::XML::Node *parent)
660 {
661 Inkscape::XML::Node *dup = node->duplicate(target_doc);
662 parent->appendChild(dup);
663 Inkscape::GC::release(dup);
664 return dup;
665 }
668 /**
669 * @brief Paste the contents of a document into the active desktop
670 * @param clipdoc The document to paste
671 * @param in_place Whether to paste the selection where it was when copied
672 * @pre @c clipdoc is not empty and items can be added to the current layer
673 */
674 void ClipboardManagerImpl::_pasteDocument(SPDocument *clipdoc, bool in_place)
675 {
676 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
677 SPDocument *target_document = sp_desktop_document(desktop);
678 Inkscape::XML::Node
679 *root = sp_document_repr_root(clipdoc),
680 *target_parent = SP_OBJECT_REPR(desktop->currentLayer());
681 Inkscape::XML::Document *target_xmldoc = sp_document_repr_doc(target_document);
683 // copy definitions
684 _pasteDefs(clipdoc);
686 // copy objects
687 GSList *pasted_objects = NULL;
688 for (Inkscape::XML::Node *obj = root->firstChild() ; obj ; obj = obj->next()) {
689 // Don't copy metadata, defs, named views and internal clipboard contents to the document
690 if (!strcmp(obj->name(), "svg:defs")) continue;
691 if (!strcmp(obj->name(), "svg:metadata")) continue;
692 if (!strcmp(obj->name(), "sodipodi:namedview")) continue;
693 if (!strcmp(obj->name(), "inkscape:clipboard")) continue;
694 Inkscape::XML::Node *obj_copy = _copyNode(obj, target_xmldoc, target_parent);
695 pasted_objects = g_slist_prepend(pasted_objects, (gpointer) obj_copy);
696 }
698 Inkscape::Selection *selection = sp_desktop_selection(desktop);
699 selection->setReprList(pasted_objects);
701 // move the selection to the right position
702 if(in_place)
703 {
704 Inkscape::XML::Node *clipnode = sp_repr_lookup_name(root, "inkscape:clipboard", 1);
705 if (clipnode) {
706 Geom::Point min, max;
707 sp_repr_get_point(clipnode, "min", &min);
708 sp_repr_get_point(clipnode, "max", &max);
710 // this formula was discovered empyrically
711 min[Geom::Y] += ((max[Geom::Y] - min[Geom::Y]) - sp_document_height(target_document));
712 sp_selection_move_relative(selection, NR::Point(min));
713 }
714 }
715 // copied from former sp_selection_paste in selection-chemistry.cpp
716 else {
717 sp_document_ensure_up_to_date(target_document);
718 NR::Maybe<NR::Rect> sel_size = selection->bounds();
720 NR::Point m( desktop->point() );
721 if (sel_size) {
722 m -= sel_size->midpoint();
723 }
724 sp_selection_move_relative(selection, m);
725 }
727 g_slist_free(pasted_objects);
728 }
731 /**
732 * @brief Paste SVG defs from the document retrieved from the clipboard into the active document
733 * @param clipdoc The document to paste
734 * @pre @c clipdoc != NULL and pasting into the active document is possible
735 */
736 void ClipboardManagerImpl::_pasteDefs(SPDocument *clipdoc)
737 {
738 // boilerplate vars copied from _pasteDocument
739 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
740 SPDocument *target_document = sp_desktop_document(desktop);
741 Inkscape::XML::Node
742 *root = sp_document_repr_root(clipdoc),
743 *defs = sp_repr_lookup_name(root, "svg:defs", 1),
744 *target_defs = SP_OBJECT_REPR(SP_DOCUMENT_DEFS(target_document));
745 Inkscape::XML::Document *target_xmldoc = sp_document_repr_doc(target_document);
747 for (Inkscape::XML::Node *def = defs->firstChild() ; def ; def = def->next()) {
748 /// @todo TODO: implement def id collision resolution in ClipboardManagerImpl::_pasteDefs()
750 /*
751 // simplistic solution: when a collision occurs, add "a" to id until it's unique
752 Glib::ustring pasted_id = def->attribute("id");
753 if ( pasted_id.empty() ) continue; // defs without id are useless
754 Glib::ustring pasted_id_original = pasted_id;
756 while(sp_repr_lookup_child(target_defs, "id", pasted_id.data())) {
757 pasted_id.append("a");
758 }
760 if ( pasted_id != pasted_id_original ) {
761 def->setAttribute("id", pasted_id.data());
762 // Update the id in the rest of the document so there are no dangling references
763 // How to do that?
764 _changeIdReferences(clipdoc, pasted_id_original, pasted_id);
765 }
766 */
767 if (sp_repr_lookup_child(target_defs, "id", def->attribute("id")))
768 continue; // skip duplicate defs - temporary non-solution
770 _copyNode(def, target_xmldoc, target_defs);
771 }
772 }
775 /**
776 * @brief Retrieve a bitmap image from the clipboard and paste it into the active document
777 */
778 bool ClipboardManagerImpl::_pasteImage()
779 {
780 SPDocument *doc = SP_ACTIVE_DOCUMENT;
781 if ( doc == NULL ) return false;
783 // retrieve image data
784 Glib::RefPtr<Gdk::Pixbuf> img = _clipboard->wait_for_image();
785 if (!img) return false;
787 // Very stupid hack: Write into a file, then import the file into the document.
788 // To avoid using tmpfile and POSIX file handles, make the filename based on current time.
789 // This wasn't my idea, I just copied this from selection-chemistry.cpp
790 // and just can't think of something saner at the moment. Pasting more than
791 // one image per second will overwrite the image.
792 // However, I don't think anyone is able to copy a _different_ image into inkscape
793 // in 1 second.
794 time_t rawtime;
795 char image_filename[128];
796 gchar const *save_folder;
798 time(&rawtime);
799 strftime(image_filename, 128, "inkscape_pasted_image_%Y%m%d_%H%M%S.png", localtime( &rawtime ));
800 save_folder = (gchar const *) prefs_get_string_attribute("dialogs.save_as", "path");
802 gchar *image_path = g_build_filename(save_folder, image_filename, NULL);
803 img->save(image_path, "png");
804 file_import(doc, image_path, NULL);
805 g_free(image_path);
807 return true;
808 }
810 /**
811 * @brief Paste text into the selected text object or create a new one to hold it
812 */
813 bool ClipboardManagerImpl::_pasteText()
814 {
815 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
816 if ( desktop == NULL ) return false;
818 // if the text editing tool is active, paste the text into the active text object
819 if (tools_isactive(desktop, TOOLS_TEXT))
820 return sp_text_paste_inline(desktop->event_context);
822 // try to parse the text as a color and, if successful, apply it as the current style
823 SPCSSAttr *css = _parseColor(_clipboard->wait_for_text());
824 if (css) {
825 sp_desktop_set_style(desktop, css);
826 return true;
827 }
829 return false;
830 }
833 /**
834 * @brief Attempt to parse the passed string as a hexadecimal RGB or RGBA color
835 * @param text The Glib::ustring to parse
836 * @return New CSS style representation if the parsing was successful, NULL otherwise
837 */
838 SPCSSAttr *ClipboardManagerImpl::_parseColor(const Glib::ustring &text)
839 {
840 Glib::ustring::size_type len = text.bytes();
841 char *str = const_cast<char *>(text.data());
842 bool attempt_alpha = false;
843 if ( !str || ( *str == '\0' ) ) return NULL; // this is OK due to boolean short-circuit
845 // those conditionals guard against parsing e.g. the string "fab" as "fab000"
846 // (incomplete color) and "45fab71" as "45fab710" (incomplete alpha)
847 if ( *str == '#' ) {
848 if ( len < 7 ) return NULL;
849 if ( len >= 9 ) attempt_alpha = true;
850 } else {
851 if ( len < 6 ) return NULL;
852 if ( len >= 8 ) attempt_alpha = true;
853 }
855 unsigned int color = 0, alpha = 0xff;
857 // skip a leading #, if present
858 if ( *str == '#' ) ++str;
860 // try to parse first 6 digits
861 int res = sscanf(str, "%6x", &color);
862 if ( res && ( res != EOF ) ) {
863 if (attempt_alpha) {// try to parse alpha if there's enough characters
864 sscanf(str + 6, "%2x", &alpha);
865 if ( !res || res == EOF ) alpha = 0xff;
866 }
868 SPCSSAttr *color_css = sp_repr_css_attr_new();
870 // print and set properties
871 gchar color_str[16];
872 g_snprintf(color_str, 16, "#%06x", color);
873 sp_repr_css_set_property(color_css, "fill", color_str);
875 float opacity = static_cast<float>(alpha)/static_cast<float>(0xff);
876 if (opacity > 1.0) opacity = 1.0; // safeguard
877 Inkscape::CSSOStringStream opcss;
878 opcss << opacity;
879 sp_repr_css_set_property(color_css, "fill-opacity", opcss.str().data());
880 return color_css;
881 }
882 return NULL;
883 }
886 /**
887 * @brief Applies a pasted path effect to a given item
888 */
889 void ClipboardManagerImpl::_applyPathEffect(SPItem *item, gchar const *effect)
890 {
891 if ( item == NULL ) return;
893 if (SP_IS_LPE_ITEM(item))
894 {
895 SPLPEItem *lpeitem = SP_LPE_ITEM(item);
896 SPObject *obj = sp_uri_reference_resolve(_clipboardSPDoc, effect);
897 if (!obj) return;
898 // if the effect is not used by anyone, we might as well take it
899 LivePathEffectObject *lpeobj = LIVEPATHEFFECT(obj)->fork_private_if_necessary(1);
900 sp_lpe_item_set_path_effect(lpeitem, lpeobj);
901 }
902 }
905 /**
906 * @brief Retrieve the clipboard contents as a document
907 * @return Clipboard contents converted to SPDocument, or NULL if no suitable content was present
908 */
909 SPDocument *ClipboardManagerImpl::_retrieveClipboard(Glib::ustring required_target)
910 {
911 Glib::ustring best_target;
912 if ( required_target == "" ) best_target = _getBestTarget();
913 else best_target = required_target;
915 if ( best_target == "" ) {
916 return NULL;
917 }
919 // doing this synchronously makes better sense
920 Gtk::SelectionData sel = _clipboard->wait_for_contents(best_target);
922 Glib::ustring target = sel.get_target();
924 // there is no specific plain SVG input extension, so if we can paste the Inkscape SVG format,
925 // we use the image/svg+xml mimetype to look up the input extension
926 if(target == "image/x-inkscape-svg")
927 target = "image/svg+xml";
929 Inkscape::Extension::DB::InputList inlist;
930 Inkscape::Extension::db.get_input_list(inlist);
931 Inkscape::Extension::DB::InputList::const_iterator in = inlist.begin();
932 for (; in != inlist.end() && target != (*in)->get_mimetype() ; ++in);
933 if ( in == inlist.end() ) return NULL; // this shouldn't happen unless _getBestTarget returns something bogus
935 // FIXME: Temporary hack until we add memory input.
936 // Save the clipboard contents to some file, then read it
937 gchar *filename = g_build_filename( g_get_tmp_dir(), "inkscape-clipboard-import", NULL );
938 g_file_set_contents(filename, (const gchar *) sel.get_data(), sel.get_length(), NULL);
940 SPDocument *tempdoc = (*in)->open(filename);
941 g_unlink(filename);
942 g_free(filename);
944 return tempdoc;
945 }
948 /**
949 * @brief Callback called when some other application requests data from Inkscape
950 *
951 * Finds a suitable output extension to save the internal clipboard document,
952 * then saves it to memory and sets the clipboard contents.
953 */
954 void ClipboardManagerImpl::_onGet(Gtk::SelectionData &sel, guint info)
955 {
956 g_assert( _clipboardSPDoc != NULL );
958 const Glib::ustring target = sel.get_target();
959 if(target == "") return; // this shouldn't happen
961 Inkscape::Extension::DB::OutputList outlist;
962 Inkscape::Extension::db.get_output_list(outlist);
963 Inkscape::Extension::DB::OutputList::const_iterator out = outlist.begin();
964 for ( ; out != outlist.end() && target != (*out)->get_mimetype() ; ++out);
965 if ( out == outlist.end() ) return; // this also shouldn't happen
967 // FIXME: Temporary hack until we add support for memory output.
968 // Save to a temporary file, read it back and then set the clipboard contents
969 gchar *filename = g_build_filename( g_get_tmp_dir(), "inkscape-clipboard-export", NULL );
970 gsize len; gchar *data;
972 (*out)->save(_clipboardSPDoc, filename);
973 g_file_get_contents(filename, &data, &len, NULL);
974 g_unlink(filename); // delete the temporary file
975 g_free(filename);
977 sel.set(8, (guint8 const *) data, len);
978 }
981 /**
982 * @brief Callback when someone else takes the clipboard
983 *
984 * When the clipboard owner changes, this callback clears the internal clipboard document
985 * to reduce memory usage.
986 */
987 void ClipboardManagerImpl::_onClear()
988 {
989 // why is this called before _onGet???
990 //_discardInternalClipboard();
991 }
994 /**
995 * @brief Creates an internal clipboard document from scratch
996 */
997 void ClipboardManagerImpl::_createInternalClipboard()
998 {
999 if ( _clipboardSPDoc == NULL ) {
1000 _clipboardSPDoc = sp_document_new(NULL, false, true);
1001 //g_assert( _clipboardSPDoc != NULL );
1002 _defs = SP_OBJECT_REPR(SP_DOCUMENT_DEFS(_clipboardSPDoc));
1003 _doc = sp_document_repr_doc(_clipboardSPDoc);
1004 _root = sp_document_repr_root(_clipboardSPDoc);
1006 _clipnode = _doc->createElement("inkscape:clipboard");
1007 _root->appendChild(_clipnode);
1008 Inkscape::GC::release(_clipnode);
1009 }
1010 }
1013 /**
1014 * @brief Deletes the internal clipboard document
1015 */
1016 void ClipboardManagerImpl::_discardInternalClipboard()
1017 {
1018 if ( _clipboardSPDoc != NULL ) {
1019 sp_document_unref(_clipboardSPDoc);
1020 _clipboardSPDoc = NULL;
1021 _defs = NULL;
1022 _doc = NULL;
1023 _root = NULL;
1024 _clipnode = NULL;
1025 }
1026 }
1029 /**
1030 * @brief Get the scale to resize an item, based on the command and desktop state
1031 */
1032 NR::scale ClipboardManagerImpl::_getScale(Geom::Point &min, Geom::Point &max, NR::Rect &obj_rect, bool apply_x, bool apply_y)
1033 {
1034 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1035 double scale_x = 1.0;
1036 double scale_y = 1.0;
1038 if (apply_x) {
1039 scale_x = (max[Geom::X] - min[Geom::X]) / obj_rect.extent(NR::X);
1040 }
1041 if (apply_y) {
1042 scale_y = (max[Geom::Y] - min[Geom::Y]) / obj_rect.extent(NR::Y);
1043 }
1044 // If the "lock aspect ratio" button is pressed and we paste only a single coordinate,
1045 // resize the second one by the same ratio too
1046 if (desktop->isToolboxButtonActive("lock")) {
1047 if (apply_x && !apply_y) scale_y = scale_x;
1048 if (apply_y && !apply_x) scale_x = scale_y;
1049 }
1051 return NR::scale(scale_x, scale_y);
1052 }
1055 /**
1056 * @brief Find the most suitable clipboard target
1057 */
1058 Glib::ustring ClipboardManagerImpl::_getBestTarget()
1059 {
1060 std::list<Glib::ustring> targets = _clipboard->wait_for_targets();
1062 // clipboard target debugging snippet
1063 /*
1064 g_debug("Begin clipboard targets");
1065 for ( std::list<Glib::ustring>::iterator x = targets.begin() ; x != targets.end(); ++x )
1066 g_debug("Clipboard target: %s", (*x).data());
1067 g_debug("End clipboard targets\n");
1068 //*/
1070 for(std::list<Glib::ustring>::iterator i = _preferred_targets.begin() ;
1071 i != _preferred_targets.end() ; ++i)
1072 {
1073 if ( std::find(targets.begin(), targets.end(), *i) != targets.end() )
1074 return *i;
1075 }
1076 if (_clipboard->wait_is_image_available())
1077 return CLIPBOARD_GDK_PIXBUF_TARGET;
1078 if (_clipboard->wait_is_text_available())
1079 return CLIPBOARD_TEXT_TARGET;
1081 return "";
1082 }
1085 /**
1086 * @brief Set the clipboard targets to reflect the mimetypes Inkscape can output
1087 */
1088 void ClipboardManagerImpl::_setClipboardTargets()
1089 {
1090 Inkscape::Extension::DB::OutputList outlist;
1091 Inkscape::Extension::db.get_output_list(outlist);
1092 std::list<Gtk::TargetEntry> target_list;
1093 for (Inkscape::Extension::DB::OutputList::const_iterator out = outlist.begin() ; out != outlist.end() ; ++out) {
1094 target_list.push_back(Gtk::TargetEntry( (*out)->get_mimetype() ));
1095 }
1097 _clipboard->set(target_list,
1098 sigc::mem_fun(*this, &ClipboardManagerImpl::_onGet),
1099 sigc::mem_fun(*this, &ClipboardManagerImpl::_onClear));
1100 }
1103 /**
1104 * @brief Set the string representation of a 32-bit RGBA color as the clipboard contents
1105 */
1106 void ClipboardManagerImpl::_setClipboardColor(guint32 color)
1107 {
1108 gchar colorstr[16];
1109 g_snprintf(colorstr, 16, "%08x", color);
1110 _clipboard->set_text(colorstr);
1111 }
1114 /**
1115 * @brief Put a notification on the mesage stack
1116 */
1117 void ClipboardManagerImpl::_userWarn(SPDesktop *desktop, char const *msg)
1118 {
1119 desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, msg);
1120 }
1124 /* #######################################
1125 ClipboardManager class
1126 ####################################### */
1128 ClipboardManager *ClipboardManager::_instance = NULL;
1130 ClipboardManager::ClipboardManager() {}
1131 ClipboardManager::~ClipboardManager() {}
1132 ClipboardManager *ClipboardManager::get()
1133 {
1134 if ( _instance == NULL )
1135 _instance = new ClipboardManagerImpl;
1136 return _instance;
1137 }
1139 } // namespace Inkscape
1140 } // namespace IO
1142 /*
1143 Local Variables:
1144 mode:c++
1145 c-file-style:"stroustrup"
1146 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1147 indent-tabs-mode:nil
1148 fill-column:99
1149 End:
1150 */
1151 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :