d0b555f84dcaef1e39ebb34e61846506dbf245ee
1 /** @file
2 * @brief System-wide clipboard management - implementation
3 */
4 /* Authors:
5 * Krzysztof KosiĆski <tweenk@o2.pl>
6 * Jon A. Cruz <jon@joncruz.org>
7 * Incorporates some code from selection-chemistry.cpp, see that file for more credits.
8 *
9 * Copyright (C) 2008 authors
10 * Copyright (C) 2010 Jon A. Cruz
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * See the file COPYING for details.
18 */
20 #include "ui/clipboard.h"
22 // TODO: reduce header bloat if possible
24 #include <list>
25 #include <algorithm>
26 #include <gtkmm/clipboard.h>
27 #include <glibmm/ustring.h>
28 #include <glibmm/i18n.h>
29 #include <glib/gstdio.h> // for g_file_set_contents etc., used in _onGet and paste
30 #include "gc-core.h"
31 #include "xml/repr.h"
32 #include "inkscape.h"
33 #include "io/stringstream.h"
34 #include "desktop.h"
35 #include "desktop-handles.h"
36 #include "desktop-style.h" // for sp_desktop_set_style, used in _pasteStyle
37 #include "document.h"
38 #include "document-private.h"
39 #include "selection.h"
40 #include "message-stack.h"
41 #include "context-fns.h"
42 #include "dropper-context.h" // used in copy()
43 #include "style.h"
44 #include "extension/db.h" // extension database
45 #include "extension/input.h"
46 #include "extension/output.h"
47 #include "selection-chemistry.h"
48 #include "libnr/nr-rect.h"
49 #include "libnr/nr-convert2geom.h"
50 #include <2geom/rect.h>
51 #include <2geom/transforms.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 "sp-rect.h"
68 #include "live_effects/lpeobject.h"
69 #include "live_effects/lpeobject-reference.h"
70 #include "live_effects/parameter/path.h"
71 #include "svg/svg.h" // for sp_svg_transform_write, used in _copySelection
72 #include "svg/css-ostringstream.h" // used in _parseColor
73 #include "file.h" // for file_import, used in _pasteImage
74 #include "text-context.h"
75 #include "text-editing.h"
76 #include "tools-switch.h"
77 #include "path-chemistry.h"
78 #include "id-clash.h"
79 #include "unit-constants.h"
80 #include "helper/png-write.h"
81 #include "svg/svg-color.h"
82 #include "sp-namedview.h"
83 #include "snap.h"
85 /// @brief Made up mimetype to represent Gdk::Pixbuf clipboard contents
86 #define CLIPBOARD_GDK_PIXBUF_TARGET "image/x-gdk-pixbuf"
88 #define CLIPBOARD_TEXT_TARGET "text/plain"
90 #ifdef WIN32
91 #include <windows.h>
92 // Clipboard Formats: http://msdn.microsoft.com/en-us/library/ms649013(VS.85).aspx
93 // On Windows, most graphical applications can handle CF_DIB/CF_BITMAP and/or CF_ENHMETAFILE
94 // GTK automatically presents an "image/bmp" target as CF_DIB/CF_BITMAP
95 // Presenting "image/x-emf" as CF_ENHMETAFILE must be done by Inkscape ?
96 #define CLIPBOARD_WIN32_EMF_TARGET "CF_ENHMETAFILE"
97 #define CLIPBOARD_WIN32_EMF_MIME "image/x-emf"
98 #endif
100 namespace Inkscape {
101 namespace UI {
104 /**
105 * @brief Default implementation of the clipboard manager
106 */
107 class ClipboardManagerImpl : public ClipboardManager {
108 public:
109 virtual void copy();
110 virtual void copyPathParameter(Inkscape::LivePathEffect::PathParam *);
111 virtual bool paste(bool in_place);
112 virtual bool pasteStyle();
113 virtual bool pasteSize(bool, bool, bool);
114 virtual bool pastePathEffect();
115 virtual Glib::ustring getPathParameter();
116 virtual Glib::ustring getShapeOrTextObjectId();
117 virtual const gchar *getFirstObjectID();
119 ClipboardManagerImpl();
120 ~ClipboardManagerImpl();
122 private:
123 void _copySelection(Inkscape::Selection *);
124 void _copyUsedDefs(SPItem *);
125 void _copyGradient(SPGradient *);
126 void _copyPattern(SPPattern *);
127 void _copyTextPath(SPTextPath *);
128 Inkscape::XML::Node *_copyNode(Inkscape::XML::Node *, Inkscape::XML::Document *, Inkscape::XML::Node *);
130 void _pasteDocument(SPDocument *, bool in_place);
131 void _pasteDefs(SPDocument *);
132 bool _pasteImage();
133 bool _pasteText();
134 SPCSSAttr *_parseColor(const Glib::ustring &);
135 void _applyPathEffect(SPItem *, gchar const *);
136 SPDocument *_retrieveClipboard(Glib::ustring = "");
138 // clipboard callbacks
139 void _onGet(Gtk::SelectionData &, guint);
140 void _onClear();
142 // various helpers
143 void _createInternalClipboard();
144 void _discardInternalClipboard();
145 Inkscape::XML::Node *_createClipNode();
146 Geom::Scale _getScale(Geom::Point const &, Geom::Point const &, Geom::Rect const &, bool, bool);
147 Glib::ustring _getBestTarget();
148 void _setClipboardTargets();
149 void _setClipboardColor(guint32);
150 void _userWarn(SPDesktop *, char const *);
152 void _inkscape_wait_for_targets(std::list<Glib::ustring> &);
154 // private properites
155 SPDocument *_clipboardSPDoc; ///< Document that stores the clipboard until someone requests it
156 Inkscape::XML::Node *_defs; ///< Reference to the clipboard document's defs node
157 Inkscape::XML::Node *_root; ///< Reference to the clipboard's root node
158 Inkscape::XML::Node *_clipnode; ///< The node that holds extra information
159 Inkscape::XML::Document *_doc; ///< Reference to the clipboard's Inkscape::XML::Document
161 // we need a way to copy plain text AND remember its style;
162 // the standard _clipnode is only available in an SVG tree, hence this special storage
163 SPCSSAttr *_text_style; ///< Style copied along with plain text fragment
165 Glib::RefPtr<Gtk::Clipboard> _clipboard; ///< Handle to the system wide clipboard - for convenience
166 std::list<Glib::ustring> _preferred_targets; ///< List of supported clipboard targets
167 };
170 ClipboardManagerImpl::ClipboardManagerImpl()
171 : _clipboardSPDoc(NULL),
172 _defs(NULL),
173 _root(NULL),
174 _clipnode(NULL),
175 _doc(NULL),
176 _text_style(NULL),
177 _clipboard( Gtk::Clipboard::get() )
178 {
179 // push supported clipboard targets, in order of preference
180 _preferred_targets.push_back("image/x-inkscape-svg");
181 _preferred_targets.push_back("image/svg+xml");
182 _preferred_targets.push_back("image/svg+xml-compressed");
183 #ifdef WIN32
184 _preferred_targets.push_back(CLIPBOARD_WIN32_EMF_MIME);
185 #endif
186 _preferred_targets.push_back("application/pdf");
187 _preferred_targets.push_back("image/x-adobe-illustrator");
188 }
191 ClipboardManagerImpl::~ClipboardManagerImpl() {}
194 /**
195 * @brief Copy selection contents to the clipboard
196 */
197 void ClipboardManagerImpl::copy()
198 {
199 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
200 if ( desktop == NULL ) {
201 return;
202 }
203 Inkscape::Selection *selection = sp_desktop_selection(desktop);
205 // Special case for when the gradient dragger is active - copies gradient color
206 if (desktop->event_context->get_drag()) {
207 GrDrag *drag = desktop->event_context->get_drag();
208 if (drag->hasSelection()) {
209 guint32 col = drag->getColor();
211 // set the color as clipboard content (text in RRGGBBAA format)
212 _setClipboardColor(col);
214 // create a style with this color on fill and opacity in master opacity, so it can be
215 // pasted on other stops or objects
216 if (_text_style) {
217 sp_repr_css_attr_unref(_text_style);
218 _text_style = NULL;
219 }
220 _text_style = sp_repr_css_attr_new();
221 // print and set properties
222 gchar color_str[16];
223 g_snprintf(color_str, 16, "#%06x", col >> 8);
224 sp_repr_css_set_property(_text_style, "fill", color_str);
225 float opacity = SP_RGBA32_A_F(col);
226 if (opacity > 1.0) {
227 opacity = 1.0; // safeguard
228 }
229 Inkscape::CSSOStringStream opcss;
230 opcss << opacity;
231 sp_repr_css_set_property(_text_style, "opacity", opcss.str().data());
233 _discardInternalClipboard();
234 return;
235 }
236 }
238 // Special case for when the color picker ("dropper") is active - copies color under cursor
239 if (tools_isactive(desktop, TOOLS_DROPPER)) {
240 _setClipboardColor(sp_dropper_context_get_color(desktop->event_context));
241 _discardInternalClipboard();
242 return;
243 }
245 // Special case for when the text tool is active - if some text is selected, copy plain text,
246 // not the object that holds it; also copy the style at cursor into
247 if (tools_isactive(desktop, TOOLS_TEXT)) {
248 _discardInternalClipboard();
249 Glib::ustring selected_text = sp_text_get_selected_text(desktop->event_context);
250 if (!selected_text.empty()) {
251 _clipboard->set_text(selected_text);
252 }
253 if (_text_style) {
254 sp_repr_css_attr_unref(_text_style);
255 _text_style = NULL;
256 }
257 _text_style = sp_text_get_style_at_cursor(desktop->event_context);
258 return;
259 }
261 if (selection->isEmpty()) { // check whether something is selected
262 _userWarn(desktop, _("Nothing was copied."));
263 return;
264 }
265 _discardInternalClipboard();
267 _createInternalClipboard(); // construct a new clipboard document
268 _copySelection(selection); // copy all items in the selection to the internal clipboard
269 fit_canvas_to_drawing(_clipboardSPDoc);
271 _setClipboardTargets();
272 }
275 /**
276 * @brief Copy a Live Path Effect path parameter to the clipboard
277 * @param pp The path parameter to store in the clipboard
278 */
279 void ClipboardManagerImpl::copyPathParameter(Inkscape::LivePathEffect::PathParam *pp)
280 {
281 if ( pp == NULL ) {
282 return;
283 }
284 gchar *svgd = sp_svg_write_path( pp->get_pathvector() );
285 if ( svgd == NULL || *svgd == '\0' ) {
286 return;
287 }
289 _discardInternalClipboard();
290 _createInternalClipboard();
292 Inkscape::XML::Node *pathnode = _doc->createElement("svg:path");
293 pathnode->setAttribute("d", svgd);
294 g_free(svgd);
295 _root->appendChild(pathnode);
296 Inkscape::GC::release(pathnode);
298 fit_canvas_to_drawing(_clipboardSPDoc);
299 _setClipboardTargets();
300 }
302 /**
303 * @brief Paste from the system clipboard into the active desktop
304 * @param in_place Whether to put the contents where they were when copied
305 */
306 bool ClipboardManagerImpl::paste(bool in_place)
307 {
308 // do any checking whether we really are able to paste before requesting the contents
309 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
310 if ( desktop == NULL ) {
311 return false;
312 }
313 if ( Inkscape::have_viable_layer(desktop, desktop->messageStack()) == false ) {
314 return false;
315 }
317 Glib::ustring target = _getBestTarget();
319 // Special cases of clipboard content handling go here
320 // Note that target priority is determined in _getBestTarget.
321 // TODO: Handle x-special/gnome-copied-files and text/uri-list to support pasting files
323 // if there is an image on the clipboard, paste it
324 if ( target == CLIPBOARD_GDK_PIXBUF_TARGET ) {
325 return _pasteImage();
326 }
327 // if there's only text, paste it into a selected text object or create a new one
328 if ( target == CLIPBOARD_TEXT_TARGET ) {
329 return _pasteText();
330 }
332 // otherwise, use the import extensions
333 SPDocument *tempdoc = _retrieveClipboard(target);
334 if ( tempdoc == NULL ) {
335 _userWarn(desktop, _("Nothing on the clipboard."));
336 return false;
337 }
339 _pasteDocument(tempdoc, in_place);
340 sp_document_unref(tempdoc);
342 return true;
343 }
345 /**
346 * @brief Returns the id of the first visible copied object
347 */
348 const gchar *ClipboardManagerImpl::getFirstObjectID()
349 {
350 SPDocument *tempdoc = _retrieveClipboard("image/x-inkscape-svg");
351 if ( tempdoc == NULL ) {
352 return NULL;
353 }
355 Inkscape::XML::Node
356 *root = sp_document_repr_root(tempdoc);
358 if (!root) {
359 return NULL;
360 }
362 Inkscape::XML::Node *ch = sp_repr_children(root);
363 while (ch != NULL &&
364 strcmp(ch->name(), "svg:g") &&
365 strcmp(ch->name(), "svg:path") &&
366 strcmp(ch->name(), "svg:use") &&
367 strcmp(ch->name(), "svg:text") &&
368 strcmp(ch->name(), "svg:image") &&
369 strcmp(ch->name(), "svg:rect")
370 ) {
371 ch = ch->next();
372 }
374 if (ch) {
375 return ch->attribute("id");
376 }
378 return NULL;
379 }
382 /**
383 * @brief Implements the Paste Style action
384 */
385 bool ClipboardManagerImpl::pasteStyle()
386 {
387 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
388 if (desktop == NULL) {
389 return false;
390 }
392 // check whether something is selected
393 Inkscape::Selection *selection = sp_desktop_selection(desktop);
394 if (selection->isEmpty()) {
395 _userWarn(desktop, _("Select <b>object(s)</b> to paste style to."));
396 return false;
397 }
399 SPDocument *tempdoc = _retrieveClipboard("image/x-inkscape-svg");
400 if ( tempdoc == NULL ) {
401 // no document, but we can try _text_style
402 if (_text_style) {
403 sp_desktop_set_style(desktop, _text_style);
404 return true;
405 } else {
406 _userWarn(desktop, _("No style on the clipboard."));
407 return false;
408 }
409 }
411 Inkscape::XML::Node
412 *root = sp_document_repr_root(tempdoc),
413 *clipnode = sp_repr_lookup_name(root, "inkscape:clipboard", 1);
415 bool pasted = false;
417 if (clipnode) {
418 _pasteDefs(tempdoc);
419 SPCSSAttr *style = sp_repr_css_attr(clipnode, "style");
420 sp_desktop_set_style(desktop, style);
421 pasted = true;
422 }
423 else {
424 _userWarn(desktop, _("No style on the clipboard."));
425 }
427 sp_document_unref(tempdoc);
428 return pasted;
429 }
432 /**
433 * @brief Resize the selection or each object in the selection to match the clipboard's size
434 * @param separately Whether to scale each object in the selection separately
435 * @param apply_x Whether to scale the width of objects / selection
436 * @param apply_y Whether to scale the height of objects / selection
437 */
438 bool ClipboardManagerImpl::pasteSize(bool separately, bool apply_x, bool apply_y)
439 {
440 if (!apply_x && !apply_y) {
441 return false; // pointless parameters
442 }
444 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
445 if ( desktop == NULL ) {
446 return false;
447 }
448 Inkscape::Selection *selection = sp_desktop_selection(desktop);
449 if (selection->isEmpty()) {
450 _userWarn(desktop, _("Select <b>object(s)</b> to paste size to."));
451 return false;
452 }
454 // FIXME: actually, this should accept arbitrary documents
455 SPDocument *tempdoc = _retrieveClipboard("image/x-inkscape-svg");
456 if ( tempdoc == NULL ) {
457 _userWarn(desktop, _("No size on the clipboard."));
458 return false;
459 }
461 // retrieve size ifomration from the clipboard
462 Inkscape::XML::Node *root = sp_document_repr_root(tempdoc);
463 Inkscape::XML::Node *clipnode = sp_repr_lookup_name(root, "inkscape:clipboard", 1);
464 bool pasted = false;
465 if (clipnode) {
466 Geom::Point min, max;
467 sp_repr_get_point(clipnode, "min", &min);
468 sp_repr_get_point(clipnode, "max", &max);
470 // resize each object in the selection
471 if (separately) {
472 for (GSList *i = const_cast<GSList*>(selection->itemList()) ; i ; i = i->next) {
473 SPItem *item = SP_ITEM(i->data);
474 Geom::OptRect obj_size = sp_item_bbox_desktop(item);
475 if ( !obj_size ) {
476 continue;
477 }
478 sp_item_scale_rel(item, _getScale(min, max, *obj_size, apply_x, apply_y));
479 }
480 }
481 // resize the selection as a whole
482 else {
483 Geom::OptRect sel_size = selection->bounds();
484 if ( sel_size ) {
485 sp_selection_scale_relative(selection, sel_size->midpoint(),
486 _getScale(min, max, *sel_size, apply_x, apply_y));
487 }
488 }
489 pasted = true;
490 }
491 sp_document_unref(tempdoc);
492 return pasted;
493 }
496 /**
497 * @brief Applies a path effect from the clipboard to the selected path
498 */
499 bool ClipboardManagerImpl::pastePathEffect()
500 {
501 /** @todo FIXME: pastePathEffect crashes when moving the path with the applied effect,
502 segfaulting in fork_private_if_necessary(). */
504 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
505 if ( desktop == NULL ) {
506 return false;
507 }
509 Inkscape::Selection *selection = sp_desktop_selection(desktop);
510 if (selection && selection->isEmpty()) {
511 _userWarn(desktop, _("Select <b>object(s)</b> to paste live path effect to."));
512 return false;
513 }
515 SPDocument *tempdoc = _retrieveClipboard("image/x-inkscape-svg");
516 if ( tempdoc ) {
517 Inkscape::XML::Node *root = sp_document_repr_root(tempdoc);
518 Inkscape::XML::Node *clipnode = sp_repr_lookup_name(root, "inkscape:clipboard", 1);
519 if ( clipnode ) {
520 gchar const *effectstack = clipnode->attribute("inkscape:path-effect");
521 if ( effectstack ) {
522 _pasteDefs(tempdoc);
523 // make sure all selected items are converted to paths first (i.e. rectangles)
524 sp_selected_to_lpeitems(desktop);
525 for (GSList *itemptr = const_cast<GSList *>(selection->itemList()) ; itemptr ; itemptr = itemptr->next) {
526 SPItem *item = reinterpret_cast<SPItem*>(itemptr->data);
527 _applyPathEffect(item, effectstack);
528 }
530 return true;
531 }
532 }
533 }
535 // no_effect:
536 _userWarn(desktop, _("No effect on the clipboard."));
537 return false;
538 }
541 /**
542 * @brief Get LPE path data from the clipboard
543 * @return The retrieved path data (contents of the d attribute), or "" if no path was found
544 */
545 Glib::ustring ClipboardManagerImpl::getPathParameter()
546 {
547 SPDocument *tempdoc = _retrieveClipboard(); // any target will do here
548 if ( tempdoc == NULL ) {
549 _userWarn(SP_ACTIVE_DESKTOP, _("Nothing on the clipboard."));
550 return "";
551 }
552 Inkscape::XML::Node
553 *root = sp_document_repr_root(tempdoc),
554 *path = sp_repr_lookup_name(root, "svg:path", -1); // unlimited search depth
555 if ( path == NULL ) {
556 _userWarn(SP_ACTIVE_DESKTOP, _("Clipboard does not contain a path."));
557 sp_document_unref(tempdoc);
558 return "";
559 }
560 gchar const *svgd = path->attribute("d");
561 return svgd;
562 }
565 /**
566 * @brief Get object id of a shape or text item from the clipboard
567 * @return The retrieved id string (contents of the id attribute), or "" if no shape or text item was found
568 */
569 Glib::ustring ClipboardManagerImpl::getShapeOrTextObjectId()
570 {
571 SPDocument *tempdoc = _retrieveClipboard(); // any target will do here
572 if ( tempdoc == NULL ) {
573 _userWarn(SP_ACTIVE_DESKTOP, _("Nothing on the clipboard."));
574 return "";
575 }
576 Inkscape::XML::Node *root = sp_document_repr_root(tempdoc);
578 Inkscape::XML::Node *repr = sp_repr_lookup_name(root, "svg:path", -1); // unlimited search depth
579 if ( repr == NULL ) {
580 repr = sp_repr_lookup_name(root, "svg:text", -1);
581 }
583 if ( repr == NULL ) {
584 _userWarn(SP_ACTIVE_DESKTOP, _("Clipboard does not contain a path."));
585 sp_document_unref(tempdoc);
586 return "";
587 }
588 gchar const *svgd = repr->attribute("id");
589 return svgd;
590 }
593 /**
594 * @brief Iterate over a list of items and copy them to the clipboard.
595 */
596 void ClipboardManagerImpl::_copySelection(Inkscape::Selection *selection)
597 {
598 GSList const *items = selection->itemList();
599 // copy the defs used by all items
600 for (GSList *i = const_cast<GSList *>(items) ; i != NULL ; i = i->next) {
601 _copyUsedDefs(SP_ITEM (i->data));
602 }
604 // copy the representation of the items
605 GSList *sorted_items = g_slist_copy(const_cast<GSList *>(items));
606 sorted_items = g_slist_sort(sorted_items, (GCompareFunc) sp_object_compare_position);
608 for (GSList *i = sorted_items ; i ; i = i->next) {
609 if (!SP_IS_ITEM(i->data)) {
610 continue;
611 }
612 Inkscape::XML::Node *obj = SP_OBJECT_REPR(i->data);
613 Inkscape::XML::Node *obj_copy = _copyNode(obj, _doc, _root);
615 // copy complete inherited style
616 SPCSSAttr *css = sp_repr_css_attr_inherited(obj, "style");
617 sp_repr_css_set(obj_copy, css, "style");
618 sp_repr_css_attr_unref(css);
620 // write the complete accumulated transform passed to us
621 // (we're dealing with unattached representations, so we write to their attributes
622 // instead of using sp_item_set_transform)
623 gchar *transform_str = sp_svg_transform_write(sp_item_i2doc_affine(SP_ITEM(i->data)));
624 obj_copy->setAttribute("transform", transform_str);
625 g_free(transform_str);
626 }
628 // copy style for Paste Style action
629 if (sorted_items) {
630 if (SP_IS_ITEM(sorted_items->data)) {
631 SPCSSAttr *style = take_style_from_item((SPItem *) sorted_items->data);
632 sp_repr_css_set(_clipnode, style, "style");
633 sp_repr_css_attr_unref(style);
634 }
636 // copy path effect from the first path
637 if (SP_IS_OBJECT(sorted_items->data)) {
638 gchar const *effect = SP_OBJECT_REPR(sorted_items->data)->attribute("inkscape:path-effect");
639 if (effect) {
640 _clipnode->setAttribute("inkscape:path-effect", effect);
641 }
642 }
643 }
645 Geom::OptRect size = selection->bounds();
646 if (size) {
647 sp_repr_set_point(_clipnode, "min", size->min());
648 sp_repr_set_point(_clipnode, "max", size->max());
649 }
651 g_slist_free(sorted_items);
652 }
655 /**
656 * @brief Recursively copy all the definitions used by a given item to the clipboard defs
657 */
658 void ClipboardManagerImpl::_copyUsedDefs(SPItem *item)
659 {
660 // copy fill and stroke styles (patterns and gradients)
661 SPStyle *style = SP_OBJECT_STYLE(item);
663 if (style && (style->fill.isPaintserver())) {
664 SPObject *server = SP_OBJECT_STYLE_FILL_SERVER(item);
665 if (SP_IS_LINEARGRADIENT(server) || SP_IS_RADIALGRADIENT(server)) {
666 _copyGradient(SP_GRADIENT(server));
667 }
668 if (SP_IS_PATTERN(server)) {
669 _copyPattern(SP_PATTERN(server));
670 }
671 }
672 if (style && (style->stroke.isPaintserver())) {
673 SPObject *server = SP_OBJECT_STYLE_STROKE_SERVER(item);
674 if (SP_IS_LINEARGRADIENT(server) || SP_IS_RADIALGRADIENT(server)) {
675 _copyGradient(SP_GRADIENT(server));
676 }
677 if (SP_IS_PATTERN(server)) {
678 _copyPattern(SP_PATTERN(server));
679 }
680 }
682 // For shapes, copy all of the shape's markers
683 if (SP_IS_SHAPE(item)) {
684 SPShape *shape = SP_SHAPE (item);
685 for (int i = 0 ; i < SP_MARKER_LOC_QTY ; i++) {
686 if (shape->marker[i]) {
687 _copyNode(SP_OBJECT_REPR(SP_OBJECT(shape->marker[i])), _doc, _defs);
688 }
689 }
690 }
691 // For lpe items, copy lpe stack if applicable
692 if (SP_IS_LPE_ITEM(item)) {
693 SPLPEItem *lpeitem = SP_LPE_ITEM (item);
694 if (sp_lpe_item_has_path_effect(lpeitem)) {
695 for (PathEffectList::iterator it = lpeitem->path_effect_list->begin(); it != lpeitem->path_effect_list->end(); ++it)
696 {
697 LivePathEffectObject *lpeobj = (*it)->lpeobject;
698 if (lpeobj) {
699 _copyNode(SP_OBJECT_REPR(SP_OBJECT(lpeobj)), _doc, _defs);
700 }
701 }
702 }
703 }
704 // For 3D boxes, copy perspectives
705 if (SP_IS_BOX3D(item)) {
706 _copyNode(SP_OBJECT_REPR(SP_OBJECT(box3d_get_perspective(SP_BOX3D(item)))), _doc, _defs);
707 }
708 // Copy text paths
709 if (SP_IS_TEXT_TEXTPATH(item)) {
710 _copyTextPath(SP_TEXTPATH(sp_object_first_child(SP_OBJECT(item))));
711 }
712 // Copy clipping objects
713 if (item->clip_ref->getObject()) {
714 _copyNode(SP_OBJECT_REPR(item->clip_ref->getObject()), _doc, _defs);
715 }
716 // Copy mask objects
717 if (item->mask_ref->getObject()) {
718 SPObject *mask = item->mask_ref->getObject();
719 _copyNode(SP_OBJECT_REPR(mask), _doc, _defs);
720 // recurse into the mask for its gradients etc.
721 for (SPObject *o = SP_OBJECT(mask)->children ; o != NULL ; o = o->next) {
722 if (SP_IS_ITEM(o)) {
723 _copyUsedDefs(SP_ITEM(o));
724 }
725 }
726 }
727 // Copy filters
728 if (style->getFilter()) {
729 SPObject *filter = style->getFilter();
730 if (SP_IS_FILTER(filter)) {
731 _copyNode(SP_OBJECT_REPR(filter), _doc, _defs);
732 }
733 }
735 // recurse
736 for (SPObject *o = SP_OBJECT(item)->children ; o != NULL ; o = o->next) {
737 if (SP_IS_ITEM(o)) {
738 _copyUsedDefs(SP_ITEM(o));
739 }
740 }
741 }
744 /**
745 * @brief Copy a single gradient to the clipboard's defs element
746 */
747 void ClipboardManagerImpl::_copyGradient(SPGradient *gradient)
748 {
749 while (gradient) {
750 // climb up the refs, copying each one in the chain
751 _copyNode(SP_OBJECT_REPR(gradient), _doc, _defs);
752 gradient = gradient->ref->getObject();
753 }
754 }
757 /**
758 * @brief Copy a single pattern to the clipboard document's defs element
759 */
760 void ClipboardManagerImpl::_copyPattern(SPPattern *pattern)
761 {
762 // climb up the references, copying each one in the chain
763 while (pattern) {
764 _copyNode(SP_OBJECT_REPR(pattern), _doc, _defs);
766 // items in the pattern may also use gradients and other patterns, so recurse
767 for (SPObject *child = sp_object_first_child(SP_OBJECT(pattern)) ; child != NULL ; child = SP_OBJECT_NEXT(child) ) {
768 if (!SP_IS_ITEM (child)) {
769 continue;
770 }
771 _copyUsedDefs(SP_ITEM(child));
772 }
773 pattern = pattern->ref->getObject();
774 }
775 }
778 /**
779 * @brief Copy a text path to the clipboard's defs element
780 */
781 void ClipboardManagerImpl::_copyTextPath(SPTextPath *tp)
782 {
783 SPItem *path = sp_textpath_get_path_item(tp);
784 if (!path) {
785 return;
786 }
787 Inkscape::XML::Node *path_node = SP_OBJECT_REPR(path);
789 // Do not copy the text path to defs if it's already copied
790 if (sp_repr_lookup_child(_root, "id", path_node->attribute("id"))) {
791 return;
792 }
793 _copyNode(path_node, _doc, _defs);
794 }
797 /**
798 * @brief Copy a single XML node from one document to another
799 * @param node The node to be copied
800 * @param target_doc The document to which the node is to be copied
801 * @param parent The node in the target document which will become the parent of the copied node
802 * @return Pointer to the copied node
803 */
804 Inkscape::XML::Node *ClipboardManagerImpl::_copyNode(Inkscape::XML::Node *node, Inkscape::XML::Document *target_doc, Inkscape::XML::Node *parent)
805 {
806 Inkscape::XML::Node *dup = node->duplicate(target_doc);
807 parent->appendChild(dup);
808 Inkscape::GC::release(dup);
809 return dup;
810 }
813 /**
814 * @brief Paste the contents of a document into the active desktop
815 * @param clipdoc The document to paste
816 * @param in_place Whether to paste the selection where it was when copied
817 * @pre @c clipdoc is not empty and items can be added to the current layer
818 */
819 void ClipboardManagerImpl::_pasteDocument(SPDocument *clipdoc, bool in_place)
820 {
821 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
822 SPDocument *target_document = sp_desktop_document(desktop);
823 Inkscape::XML::Node
824 *root = sp_document_repr_root(clipdoc),
825 *target_parent = SP_OBJECT_REPR(desktop->currentLayer());
826 Inkscape::XML::Document *target_xmldoc = sp_document_repr_doc(target_document);
828 // copy definitions
829 _pasteDefs(clipdoc);
831 // copy objects
832 GSList *pasted_objects = NULL;
833 for (Inkscape::XML::Node *obj = root->firstChild() ; obj ; obj = obj->next()) {
834 // Don't copy metadata, defs, named views and internal clipboard contents to the document
835 if (!strcmp(obj->name(), "svg:defs")) {
836 continue;
837 }
838 if (!strcmp(obj->name(), "svg:metadata")) {
839 continue;
840 }
841 if (!strcmp(obj->name(), "sodipodi:namedview")) {
842 continue;
843 }
844 if (!strcmp(obj->name(), "inkscape:clipboard")) {
845 continue;
846 }
847 Inkscape::XML::Node *obj_copy = _copyNode(obj, target_xmldoc, target_parent);
848 pasted_objects = g_slist_prepend(pasted_objects, (gpointer) obj_copy);
849 }
851 // Change the selection to the freshly pasted objects
852 Inkscape::Selection *selection = sp_desktop_selection(desktop);
853 selection->setReprList(pasted_objects);
855 // invers apply parent transform
856 Geom::Matrix doc2parent = sp_item_i2doc_affine(SP_ITEM(desktop->currentLayer())).inverse();
857 sp_selection_apply_affine(selection, desktop->dt2doc() * doc2parent * desktop->doc2dt(), true, false);
859 // Update (among other things) all curves in paths, for bounds() to work
860 sp_document_ensure_up_to_date(target_document);
862 // move selection either to original position (in_place) or to mouse pointer
863 Geom::OptRect sel_bbox = selection->bounds();
864 if (sel_bbox) {
865 // get offset of selection to original position of copied elements
866 Geom::Point pos_original;
867 Inkscape::XML::Node *clipnode = sp_repr_lookup_name(root, "inkscape:clipboard", 1);
868 if (clipnode) {
869 Geom::Point min, max;
870 sp_repr_get_point(clipnode, "min", &min);
871 sp_repr_get_point(clipnode, "max", &max);
872 pos_original = Geom::Point(min[Geom::X], max[Geom::Y]);
873 }
874 Geom::Point offset = pos_original - sel_bbox->corner(3);
876 if (!in_place) {
877 SnapManager &m = desktop->namedview->snap_manager;
878 m.setup(desktop);
879 sp_event_context_discard_delayed_snap_event(desktop->event_context);
881 // get offset from mouse pointer to bbox center, snap to grid if enabled
882 Geom::Point mouse_offset = desktop->point() - sel_bbox->midpoint();
883 offset = m.multipleOfGridPitch(mouse_offset - offset, sel_bbox->midpoint() + offset) + offset;
884 }
886 sp_selection_move_relative(selection, offset);
887 }
889 g_slist_free(pasted_objects);
890 }
893 /**
894 * @brief Paste SVG defs from the document retrieved from the clipboard into the active document
895 * @param clipdoc The document to paste
896 * @pre @c clipdoc != NULL and pasting into the active document is possible
897 */
898 void ClipboardManagerImpl::_pasteDefs(SPDocument *clipdoc)
899 {
900 // boilerplate vars copied from _pasteDocument
901 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
902 SPDocument *target_document = sp_desktop_document(desktop);
903 Inkscape::XML::Node
904 *root = sp_document_repr_root(clipdoc),
905 *defs = sp_repr_lookup_name(root, "svg:defs", 1),
906 *target_defs = SP_OBJECT_REPR(SP_DOCUMENT_DEFS(target_document));
907 Inkscape::XML::Document *target_xmldoc = sp_document_repr_doc(target_document);
909 prevent_id_clashes(clipdoc, target_document);
911 for (Inkscape::XML::Node *def = defs->firstChild() ; def ; def = def->next()) {
912 _copyNode(def, target_xmldoc, target_defs);
913 }
914 }
917 /**
918 * @brief Retrieve a bitmap image from the clipboard and paste it into the active document
919 */
920 bool ClipboardManagerImpl::_pasteImage()
921 {
922 SPDocument *doc = SP_ACTIVE_DOCUMENT;
923 if ( doc == NULL ) {
924 return false;
925 }
927 // retrieve image data
928 Glib::RefPtr<Gdk::Pixbuf> img = _clipboard->wait_for_image();
929 if (!img) {
930 return false;
931 }
933 // TODO unify with interface.cpp's sp_ui_drag_data_received()
934 // AARGH stupid
935 Inkscape::Extension::DB::InputList o;
936 Inkscape::Extension::db.get_input_list(o);
937 Inkscape::Extension::DB::InputList::const_iterator i = o.begin();
938 while (i != o.end() && strcmp( (*i)->get_mimetype(), "image/png" ) != 0) {
939 ++i;
940 }
941 Inkscape::Extension::Extension *png = *i;
942 bool save = (strcmp(png->get_param_optiongroup("link"), "embed") == 0);
943 png->set_param_optiongroup("link", "embed");
944 png->set_gui(false);
946 gchar *filename = g_build_filename( g_get_tmp_dir(), "inkscape-clipboard-import", NULL );
947 img->save(filename, "png");
948 file_import(doc, filename, png);
949 g_free(filename);
951 png->set_param_optiongroup("link", save ? "embed" : "link");
952 png->set_gui(true);
954 return true;
955 }
957 /**
958 * @brief Paste text into the selected text object or create a new one to hold it
959 */
960 bool ClipboardManagerImpl::_pasteText()
961 {
962 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
963 if ( desktop == NULL ) {
964 return false;
965 }
967 // if the text editing tool is active, paste the text into the active text object
968 if (tools_isactive(desktop, TOOLS_TEXT)) {
969 return sp_text_paste_inline(desktop->event_context);
970 }
972 // try to parse the text as a color and, if successful, apply it as the current style
973 SPCSSAttr *css = _parseColor(_clipboard->wait_for_text());
974 if (css) {
975 sp_desktop_set_style(desktop, css);
976 return true;
977 }
979 return false;
980 }
983 /**
984 * @brief Attempt to parse the passed string as a hexadecimal RGB or RGBA color
985 * @param text The Glib::ustring to parse
986 * @return New CSS style representation if the parsing was successful, NULL otherwise
987 */
988 SPCSSAttr *ClipboardManagerImpl::_parseColor(const Glib::ustring &text)
989 {
990 // TODO reuse existing code instead of replicating here.
991 Glib::ustring::size_type len = text.bytes();
992 char *str = const_cast<char *>(text.data());
993 bool attempt_alpha = false;
994 if ( !str || ( *str == '\0' ) ) {
995 return NULL; // this is OK due to boolean short-circuit
996 }
998 // those conditionals guard against parsing e.g. the string "fab" as "fab000"
999 // (incomplete color) and "45fab71" as "45fab710" (incomplete alpha)
1000 if ( *str == '#' ) {
1001 if ( len < 7 ) {
1002 return NULL;
1003 }
1004 if ( len >= 9 ) {
1005 attempt_alpha = true;
1006 }
1007 } else {
1008 if ( len < 6 ) {
1009 return NULL;
1010 }
1011 if ( len >= 8 ) {
1012 attempt_alpha = true;
1013 }
1014 }
1016 unsigned int color = 0, alpha = 0xff;
1018 // skip a leading #, if present
1019 if ( *str == '#' ) {
1020 ++str;
1021 }
1023 // try to parse first 6 digits
1024 int res = sscanf(str, "%6x", &color);
1025 if ( res && ( res != EOF ) ) {
1026 if (attempt_alpha) {// try to parse alpha if there's enough characters
1027 sscanf(str + 6, "%2x", &alpha);
1028 if ( !res || res == EOF ) {
1029 alpha = 0xff;
1030 }
1031 }
1033 SPCSSAttr *color_css = sp_repr_css_attr_new();
1035 // print and set properties
1036 gchar color_str[16];
1037 g_snprintf(color_str, 16, "#%06x", color);
1038 sp_repr_css_set_property(color_css, "fill", color_str);
1040 float opacity = static_cast<float>(alpha)/static_cast<float>(0xff);
1041 if (opacity > 1.0) {
1042 opacity = 1.0; // safeguard
1043 }
1044 Inkscape::CSSOStringStream opcss;
1045 opcss << opacity;
1046 sp_repr_css_set_property(color_css, "fill-opacity", opcss.str().data());
1047 return color_css;
1048 }
1049 return NULL;
1050 }
1053 /**
1054 * @brief Applies a pasted path effect to a given item
1055 */
1056 void ClipboardManagerImpl::_applyPathEffect(SPItem *item, gchar const *effectstack)
1057 {
1058 if ( item == NULL ) {
1059 return;
1060 }
1061 if ( SP_IS_RECT(item) ) {
1062 return;
1063 }
1065 if (SP_IS_LPE_ITEM(item))
1066 {
1067 SPLPEItem *lpeitem = SP_LPE_ITEM(item);
1068 // for each effect in the stack, check if we need to fork it before adding it to the item
1069 std::istringstream iss(effectstack);
1070 std::string href;
1071 while (std::getline(iss, href, ';'))
1072 {
1073 SPObject *obj = sp_uri_reference_resolve(_clipboardSPDoc, href.c_str());
1074 if (!obj) {
1075 return;
1076 }
1077 // if the effectstack is not used by anyone, we might as well take it
1078 LivePathEffectObject *lpeobj = LIVEPATHEFFECT(obj)->fork_private_if_necessary(1);
1079 sp_lpe_item_add_path_effect(lpeitem, lpeobj);
1080 }
1081 }
1082 }
1085 /**
1086 * @brief Retrieve the clipboard contents as a document
1087 * @return Clipboard contents converted to SPDocument, or NULL if no suitable content was present
1088 */
1089 SPDocument *ClipboardManagerImpl::_retrieveClipboard(Glib::ustring required_target)
1090 {
1091 Glib::ustring best_target;
1092 if ( required_target == "" ) {
1093 best_target = _getBestTarget();
1094 } else {
1095 best_target = required_target;
1096 }
1098 if ( best_target == "" ) {
1099 return NULL;
1100 }
1102 // FIXME: Temporary hack until we add memory input.
1103 // Save the clipboard contents to some file, then read it
1104 gchar *filename = g_build_filename( g_get_tmp_dir(), "inkscape-clipboard-import", NULL );
1106 bool file_saved = false;
1107 Glib::ustring target = best_target;
1109 #ifdef WIN32
1110 if (best_target == CLIPBOARD_WIN32_EMF_TARGET)
1111 { // Try to save clipboard data as en emf file (using win32 api)
1112 if (OpenClipboard(NULL)) {
1113 HGLOBAL hglb = GetClipboardData(CF_ENHMETAFILE);
1114 if (hglb) {
1115 HENHMETAFILE hemf = CopyEnhMetaFile((HENHMETAFILE) hglb, filename);
1116 if (hemf) {
1117 file_saved = true;
1118 target = CLIPBOARD_WIN32_EMF_MIME;
1119 DeleteEnhMetaFile(hemf);
1120 }
1121 }
1122 CloseClipboard();
1123 }
1124 }
1125 #endif
1127 if (!file_saved) {
1128 if ( !_clipboard->wait_is_target_available(best_target) ) {
1129 return NULL;
1130 }
1132 // doing this synchronously makes better sense
1133 // TODO: use another method because this one is badly broken imo.
1134 // from documentation: "Returns: A SelectionData object, which will be invalid if retrieving the given target failed."
1135 // I don't know how to check whether an object is 'valid' or not, unusable if that's not possible...
1136 Gtk::SelectionData sel = _clipboard->wait_for_contents(best_target);
1137 target = sel.get_target(); // this can crash if the result was invalid of last function. No way to check for this :(
1139 // FIXME: Temporary hack until we add memory input.
1140 // Save the clipboard contents to some file, then read it
1141 g_file_set_contents(filename, (const gchar *) sel.get_data(), sel.get_length(), NULL);
1142 }
1144 // there is no specific plain SVG input extension, so if we can paste the Inkscape SVG format,
1145 // we use the image/svg+xml mimetype to look up the input extension
1146 if (target == "image/x-inkscape-svg") {
1147 target = "image/svg+xml";
1148 }
1150 Inkscape::Extension::DB::InputList inlist;
1151 Inkscape::Extension::db.get_input_list(inlist);
1152 Inkscape::Extension::DB::InputList::const_iterator in = inlist.begin();
1153 for (; in != inlist.end() && target != (*in)->get_mimetype() ; ++in) {
1154 };
1155 if ( in == inlist.end() ) {
1156 return NULL; // this shouldn't happen unless _getBestTarget returns something bogus
1157 }
1159 SPDocument *tempdoc = NULL;
1160 try {
1161 tempdoc = (*in)->open(filename);
1162 } catch (...) {
1163 }
1164 g_unlink(filename);
1165 g_free(filename);
1167 return tempdoc;
1168 }
1171 /**
1172 * @brief Callback called when some other application requests data from Inkscape
1173 *
1174 * Finds a suitable output extension to save the internal clipboard document,
1175 * then saves it to memory and sets the clipboard contents.
1176 */
1177 void ClipboardManagerImpl::_onGet(Gtk::SelectionData &sel, guint /*info*/)
1178 {
1179 g_assert( _clipboardSPDoc != NULL );
1181 Glib::ustring target = sel.get_target();
1182 if (target == "") {
1183 return; // this shouldn't happen
1184 }
1186 if (target == CLIPBOARD_TEXT_TARGET) {
1187 target = "image/x-inkscape-svg";
1188 }
1190 Inkscape::Extension::DB::OutputList outlist;
1191 Inkscape::Extension::db.get_output_list(outlist);
1192 Inkscape::Extension::DB::OutputList::const_iterator out = outlist.begin();
1193 for ( ; out != outlist.end() && target != (*out)->get_mimetype() ; ++out) {
1194 };
1195 if ( out == outlist.end() && target != "image/png") {
1196 return; // this also shouldn't happen
1197 }
1199 // FIXME: Temporary hack until we add support for memory output.
1200 // Save to a temporary file, read it back and then set the clipboard contents
1201 gchar *filename = g_build_filename( g_get_tmp_dir(), "inkscape-clipboard-export", NULL );
1202 gsize len; gchar *data;
1204 try {
1205 if (out == outlist.end() && target == "image/png")
1206 {
1207 gdouble dpi = PX_PER_IN;
1208 guint32 bgcolor = 0x00000000;
1210 Geom::Point origin (SP_ROOT(_clipboardSPDoc->root)->x.computed, SP_ROOT(_clipboardSPDoc->root)->y.computed);
1211 Geom::Rect area = Geom::Rect(origin, origin + sp_document_dimensions(_clipboardSPDoc));
1213 unsigned long int width = (unsigned long int) (area.width() * dpi / PX_PER_IN + 0.5);
1214 unsigned long int height = (unsigned long int) (area.height() * dpi / PX_PER_IN + 0.5);
1216 // read from namedview
1217 Inkscape::XML::Node *nv = sp_repr_lookup_name (_clipboardSPDoc->rroot, "sodipodi:namedview");
1218 if (nv && nv->attribute("pagecolor")) {
1219 bgcolor = sp_svg_read_color(nv->attribute("pagecolor"), 0xffffff00);
1220 }
1221 if (nv && nv->attribute("inkscape:pageopacity")) {
1222 bgcolor |= SP_COLOR_F_TO_U(sp_repr_get_double_attribute (nv, "inkscape:pageopacity", 1.0));
1223 }
1225 sp_export_png_file(_clipboardSPDoc, filename, area, width, height, dpi, dpi, bgcolor, NULL, NULL, true, NULL);
1226 }
1227 else
1228 {
1229 if (!(*out)->loaded()) {
1230 // Need to load the extension.
1231 (*out)->set_state(Inkscape::Extension::Extension::STATE_LOADED);
1232 }
1233 (*out)->save(_clipboardSPDoc, filename);
1234 }
1235 g_file_get_contents(filename, &data, &len, NULL);
1237 sel.set(8, (guint8 const *) data, len);
1238 } catch (...) {
1239 }
1241 g_unlink(filename); // delete the temporary file
1242 g_free(filename);
1243 }
1246 /**
1247 * @brief Callback when someone else takes the clipboard
1248 *
1249 * When the clipboard owner changes, this callback clears the internal clipboard document
1250 * to reduce memory usage.
1251 */
1252 void ClipboardManagerImpl::_onClear()
1253 {
1254 // why is this called before _onGet???
1255 //_discardInternalClipboard();
1256 }
1259 /**
1260 * @brief Creates an internal clipboard document from scratch
1261 */
1262 void ClipboardManagerImpl::_createInternalClipboard()
1263 {
1264 if ( _clipboardSPDoc == NULL ) {
1265 _clipboardSPDoc = sp_document_new(NULL, false, true);
1266 //g_assert( _clipboardSPDoc != NULL );
1267 _defs = SP_OBJECT_REPR(SP_DOCUMENT_DEFS(_clipboardSPDoc));
1268 _doc = sp_document_repr_doc(_clipboardSPDoc);
1269 _root = sp_document_repr_root(_clipboardSPDoc);
1271 _clipnode = _doc->createElement("inkscape:clipboard");
1272 _root->appendChild(_clipnode);
1273 Inkscape::GC::release(_clipnode);
1275 // once we create a SVG document, style will be stored in it, so flush _text_style
1276 if (_text_style) {
1277 sp_repr_css_attr_unref(_text_style);
1278 _text_style = NULL;
1279 }
1280 }
1281 }
1284 /**
1285 * @brief Deletes the internal clipboard document
1286 */
1287 void ClipboardManagerImpl::_discardInternalClipboard()
1288 {
1289 if ( _clipboardSPDoc != NULL ) {
1290 sp_document_unref(_clipboardSPDoc);
1291 _clipboardSPDoc = NULL;
1292 _defs = NULL;
1293 _doc = NULL;
1294 _root = NULL;
1295 _clipnode = NULL;
1296 }
1297 }
1300 /**
1301 * @brief Get the scale to resize an item, based on the command and desktop state
1302 */
1303 Geom::Scale ClipboardManagerImpl::_getScale(Geom::Point const &min, Geom::Point const &max, Geom::Rect const &obj_rect, bool apply_x, bool apply_y)
1304 {
1305 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1306 double scale_x = 1.0;
1307 double scale_y = 1.0;
1309 if (apply_x) {
1310 scale_x = (max[Geom::X] - min[Geom::X]) / obj_rect[Geom::X].extent();
1311 }
1312 if (apply_y) {
1313 scale_y = (max[Geom::Y] - min[Geom::Y]) / obj_rect[Geom::Y].extent();
1314 }
1315 // If the "lock aspect ratio" button is pressed and we paste only a single coordinate,
1316 // resize the second one by the same ratio too
1317 if (desktop->isToolboxButtonActive("lock")) {
1318 if (apply_x && !apply_y) {
1319 scale_y = scale_x;
1320 }
1321 if (apply_y && !apply_x) {
1322 scale_x = scale_y;
1323 }
1324 }
1326 return Geom::Scale(scale_x, scale_y);
1327 }
1330 /**
1331 * @brief Find the most suitable clipboard target
1332 */
1333 Glib::ustring ClipboardManagerImpl::_getBestTarget()
1334 {
1335 // GTKmm's wait_for_targets() is broken, see the comment in _inkscape_wait_for_targets()
1336 std::list<Glib::ustring> targets; // = _clipboard->wait_for_targets();
1337 _inkscape_wait_for_targets(targets);
1339 // clipboard target debugging snippet
1340 /*
1341 g_debug("Begin clipboard targets");
1342 for ( std::list<Glib::ustring>::iterator x = targets.begin() ; x != targets.end(); ++x )
1343 g_debug("Clipboard target: %s", (*x).data());
1344 g_debug("End clipboard targets\n");
1345 //*/
1347 for (std::list<Glib::ustring>::iterator i = _preferred_targets.begin() ;
1348 i != _preferred_targets.end() ; ++i)
1349 {
1350 if ( std::find(targets.begin(), targets.end(), *i) != targets.end() ) {
1351 return *i;
1352 }
1353 }
1354 #ifdef WIN32
1355 if (OpenClipboard(NULL))
1356 { // If both bitmap and metafile are present, pick the one that was exported first.
1357 UINT format = EnumClipboardFormats(0);
1358 while (format) {
1359 if (format == CF_ENHMETAFILE || format == CF_DIB || format == CF_BITMAP) {
1360 break;
1361 }
1362 format = EnumClipboardFormats(format);
1363 }
1364 CloseClipboard();
1366 if (format == CF_ENHMETAFILE) {
1367 return CLIPBOARD_WIN32_EMF_TARGET;
1368 }
1369 if (format == CF_DIB || format == CF_BITMAP) {
1370 return CLIPBOARD_GDK_PIXBUF_TARGET;
1371 }
1372 }
1374 if (IsClipboardFormatAvailable(CF_ENHMETAFILE)) {
1375 return CLIPBOARD_WIN32_EMF_TARGET;
1376 }
1377 #endif
1378 if (_clipboard->wait_is_image_available()) {
1379 return CLIPBOARD_GDK_PIXBUF_TARGET;
1380 }
1381 if (_clipboard->wait_is_text_available()) {
1382 return CLIPBOARD_TEXT_TARGET;
1383 }
1385 return "";
1386 }
1389 /**
1390 * @brief Set the clipboard targets to reflect the mimetypes Inkscape can output
1391 */
1392 void ClipboardManagerImpl::_setClipboardTargets()
1393 {
1394 Inkscape::Extension::DB::OutputList outlist;
1395 Inkscape::Extension::db.get_output_list(outlist);
1396 std::list<Gtk::TargetEntry> target_list;
1397 bool plaintextSet = false;
1398 for (Inkscape::Extension::DB::OutputList::const_iterator out = outlist.begin() ; out != outlist.end() ; ++out) {
1399 if ( !(*out)->deactivated() ) {
1400 Glib::ustring mime = (*out)->get_mimetype();
1401 if (mime != CLIPBOARD_TEXT_TARGET) {
1402 if ( !plaintextSet && (mime.find("svg") == Glib::ustring::npos) ) {
1403 target_list.push_back(Gtk::TargetEntry(CLIPBOARD_TEXT_TARGET));
1404 plaintextSet = true;
1405 }
1406 target_list.push_back(Gtk::TargetEntry(mime));
1407 }
1408 }
1409 }
1411 // Add PNG export explicitly since there is no extension for this...
1412 // On Windows, GTK will also present this as a CF_DIB/CF_BITMAP
1413 target_list.push_back(Gtk::TargetEntry( "image/png" ));
1415 _clipboard->set(target_list,
1416 sigc::mem_fun(*this, &ClipboardManagerImpl::_onGet),
1417 sigc::mem_fun(*this, &ClipboardManagerImpl::_onClear));
1419 #ifdef WIN32
1420 // If the "image/x-emf" target handled by the emf extension would be
1421 // presented as a CF_ENHMETAFILE automatically (just like an "image/bmp"
1422 // is presented as a CF_BITMAP) this code would not be needed.. ???
1423 // Or maybe there is some other way to achieve the same?
1425 // Note: Metafile is the only format that is rendered and stored in clipboard
1426 // on Copy, all other formats are rendered only when needed by a Paste command.
1428 // FIXME: This should at least be rewritten to use "delayed rendering".
1429 // If possible make it delayed rendering by using GTK API only.
1431 if (OpenClipboard(NULL)) {
1432 if ( _clipboardSPDoc != NULL ) {
1433 const Glib::ustring target = CLIPBOARD_WIN32_EMF_MIME;
1435 Inkscape::Extension::DB::OutputList outlist;
1436 Inkscape::Extension::db.get_output_list(outlist);
1437 Inkscape::Extension::DB::OutputList::const_iterator out = outlist.begin();
1438 for ( ; out != outlist.end() && target != (*out)->get_mimetype() ; ++out) {
1439 }
1440 if ( out != outlist.end() ) {
1441 // FIXME: Temporary hack until we add support for memory output.
1442 // Save to a temporary file, read it back and then set the clipboard contents
1443 gchar *filename = g_build_filename( g_get_tmp_dir(), "inkscape-clipboard-export.emf", NULL );
1445 try {
1446 (*out)->save(_clipboardSPDoc, filename);
1447 HENHMETAFILE hemf = GetEnhMetaFileA(filename);
1448 if (hemf) {
1449 SetClipboardData(CF_ENHMETAFILE, hemf);
1450 DeleteEnhMetaFile(hemf);
1451 }
1452 } catch (...) {
1453 }
1454 g_unlink(filename); // delete the temporary file
1455 g_free(filename);
1456 }
1457 }
1458 CloseClipboard();
1459 }
1460 #endif
1461 }
1464 /**
1465 * @brief Set the string representation of a 32-bit RGBA color as the clipboard contents
1466 */
1467 void ClipboardManagerImpl::_setClipboardColor(guint32 color)
1468 {
1469 gchar colorstr[16];
1470 g_snprintf(colorstr, 16, "%08x", color);
1471 _clipboard->set_text(colorstr);
1472 }
1475 /**
1476 * @brief Put a notification on the mesage stack
1477 */
1478 void ClipboardManagerImpl::_userWarn(SPDesktop *desktop, char const *msg)
1479 {
1480 desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, msg);
1481 }
1484 // GTKMM's clipboard::wait_for_targets is buggy and might return bogus, see
1485 //
1486 // https://bugs.launchpad.net/inkscape/+bug/296778
1487 // http://mail.gnome.org/archives/gtk-devel-list/2009-June/msg00062.html
1488 //
1489 // for details. Until this has been fixed upstream we will use our own implementation
1490 // of this method, as copied from /gtkmm-2.16.0/gtk/gtkmm/clipboard.cc.
1491 void ClipboardManagerImpl::_inkscape_wait_for_targets(std::list<Glib::ustring> &listTargets)
1492 {
1493 //Get a newly-allocated array of atoms:
1494 GdkAtom* targets = 0;
1495 gint n_targets = 0;
1496 gboolean test = gtk_clipboard_wait_for_targets( gtk_clipboard_get(GDK_SELECTION_CLIPBOARD), &targets, &n_targets );
1497 if (!test) {
1498 n_targets = 0; //otherwise it will be -1.
1499 }
1501 //Add the targets to the C++ container:
1502 for (int i = 0; i < n_targets; i++)
1503 {
1504 //Convert the atom to a string:
1505 gchar* const atom_name = gdk_atom_name(targets[i]);
1507 Glib::ustring target;
1508 if (atom_name) {
1509 target = Glib::ScopedPtr<char>(atom_name).get(); //This frees the gchar*.
1510 }
1512 listTargets.push_back(target);
1513 }
1514 }
1516 /* #######################################
1517 ClipboardManager class
1518 ####################################### */
1520 ClipboardManager *ClipboardManager::_instance = NULL;
1522 ClipboardManager::ClipboardManager() {}
1523 ClipboardManager::~ClipboardManager() {}
1524 ClipboardManager *ClipboardManager::get()
1525 {
1526 if ( _instance == NULL ) {
1527 _instance = new ClipboardManagerImpl;
1528 }
1530 return _instance;
1531 }
1533 } // namespace Inkscape
1534 } // namespace IO
1536 /*
1537 Local Variables:
1538 mode:c++
1539 c-file-style:"stroustrup"
1540 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1541 indent-tabs-mode:nil
1542 fill-column:99
1543 End:
1544 */
1545 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :