1 #define __SP_SELECTION_CHEMISTRY_C__
3 /*
4 * Miscellanous operations on selected items
5 *
6 * Authors:
7 * Lauris Kaplinski <lauris@kaplinski.com>
8 * Frank Felfe <innerspace@iname.com>
9 * MenTaLguY <mental@rydia.net>
10 * bulia byak <buliabyak@users.sf.net>
11 * Andrius R. <knutux@gmail.com>
12 *
13 * Copyright (C) 1999-2006 authors
14 * Copyright (C) 2001-2002 Ximian, Inc.
15 *
16 * Released under GNU GPL, read the file 'COPYING' for more information
17 */
19 #ifdef HAVE_CONFIG_H
20 # include "config.h"
21 #endif
23 #include "selection-chemistry.h"
25 #include <gtkmm/clipboard.h>
27 #include "svg/svg.h"
28 #include "inkscape.h"
29 #include "desktop.h"
30 #include "desktop-style.h"
31 #include "selection.h"
32 #include "tools-switch.h"
33 #include "desktop-handles.h"
34 #include "message-stack.h"
35 #include "sp-item-transform.h"
36 #include "marker.h"
37 #include "sp-use.h"
38 #include "sp-textpath.h"
39 #include "sp-tspan.h"
40 #include "sp-tref.h"
41 #include "sp-flowtext.h"
42 #include "sp-flowregion.h"
43 #include "text-editing.h"
44 #include "text-context.h"
45 #include "connector-context.h"
46 #include "sp-path.h"
47 #include "sp-conn-end.h"
48 #include "dropper-context.h"
49 #include <glibmm/i18n.h>
50 #include "libnr/nr-matrix-rotate-ops.h"
51 #include "libnr/nr-matrix-translate-ops.h"
52 #include "libnr/nr-rotate-fns.h"
53 #include "libnr/nr-scale-ops.h"
54 #include "libnr/nr-scale-translate-ops.h"
55 #include "libnr/nr-translate-matrix-ops.h"
56 #include "libnr/nr-translate-scale-ops.h"
57 #include "xml/repr.h"
58 #include "style.h"
59 #include "document-private.h"
60 #include "sp-gradient.h"
61 #include "sp-gradient-reference.h"
62 #include "sp-linear-gradient-fns.h"
63 #include "sp-pattern.h"
64 #include "sp-radial-gradient-fns.h"
65 #include "sp-namedview.h"
66 #include "prefs-utils.h"
67 #include "sp-offset.h"
68 #include "sp-clippath.h"
69 #include "sp-mask.h"
70 #include "file.h"
71 #include "helper/png-write.h"
72 #include "layer-fns.h"
73 #include "context-fns.h"
74 #include <map>
75 #include <cstring>
76 #include <string>
77 #include "helper/units.h"
78 #include "sp-item.h"
79 #include "box3d.h"
80 #include "unit-constants.h"
81 #include "xml/simple-document.h"
82 #include "sp-filter-reference.h"
83 #include "gradient-drag.h"
84 #include "uri-references.h"
85 #include "live_effects/lpeobject.h"
86 #include "live_effects/parameter/path.h"
87 #include "libnr/nr-convert2geom.h"
89 // For clippath editing
90 #include "tools-switch.h"
91 #include "shape-editor.h"
92 #include "node-context.h"
93 #include "nodepath.h"
95 using NR::X;
96 using NR::Y;
98 /* fixme: find a better place */
99 Inkscape::XML::Document *clipboard_document = NULL;
100 GSList *clipboard = NULL;
101 GSList *defs_clipboard = NULL;
102 SPCSSAttr *style_clipboard = NULL;
103 NR::Maybe<NR::Rect> size_clipboard;
105 static void sp_copy_stuff_used_by_item(GSList **defs_clip, SPItem *item, GSList const *items, Inkscape::XML::Document* xml_doc);
107 /**
108 * Copies repr and its inherited css style elements, along with the accumulated transform 'full_t',
109 * then prepends the copy to 'clip'.
110 */
111 void sp_selection_copy_one (Inkscape::XML::Node *repr, NR::Matrix full_t, GSList **clip, Inkscape::XML::Document* xml_doc)
112 {
113 Inkscape::XML::Node *copy = repr->duplicate(xml_doc);
115 // copy complete inherited style
116 SPCSSAttr *css = sp_repr_css_attr_inherited(repr, "style");
117 sp_repr_css_set(copy, css, "style");
118 sp_repr_css_attr_unref(css);
120 // write the complete accumulated transform passed to us
121 // (we're dealing with unattached repr, so we write to its attr
122 // instead of using sp_item_set_transform)
123 gchar *affinestr=sp_svg_transform_write(full_t);
124 copy->setAttribute("transform", affinestr);
125 g_free(affinestr);
127 *clip = g_slist_prepend(*clip, copy);
128 }
130 void sp_selection_copy_impl (GSList const *items, GSList **clip, GSList **defs_clip, SPCSSAttr **style_clip, Inkscape::XML::Document* xml_doc)
131 {
133 // Copy stuff referenced by all items to defs_clip:
134 if (defs_clip) {
135 for (GSList *i = (GSList *) items; i != NULL; i = i->next) {
136 sp_copy_stuff_used_by_item (defs_clip, SP_ITEM (i->data), items, xml_doc);
137 }
138 *defs_clip = g_slist_reverse(*defs_clip);
139 }
141 // Store style:
142 if (style_clip) {
143 SPItem *item = SP_ITEM (items->data); // take from the first selected item
144 *style_clip = take_style_from_item (item);
145 }
147 if (clip) {
148 // Sort items:
149 GSList *sorted_items = g_slist_copy ((GSList *) items);
150 sorted_items = g_slist_sort((GSList *) sorted_items, (GCompareFunc) sp_object_compare_position);
152 // Copy item reprs:
153 for (GSList *i = (GSList *) sorted_items; i != NULL; i = i->next) {
154 sp_selection_copy_one (SP_OBJECT_REPR (i->data), sp_item_i2doc_affine(SP_ITEM (i->data)), clip, xml_doc);
155 }
157 *clip = g_slist_reverse(*clip);
158 g_slist_free ((GSList *) sorted_items);
159 }
160 }
162 /**
163 * Add gradients/patterns/markers referenced by copied objects to defs.
164 * Iterates through 'defs_clip', and for each item it adds the data
165 * repr into the global defs.
166 */
167 void
168 paste_defs (GSList **defs_clip, SPDocument *doc)
169 {
170 if (!defs_clip)
171 return;
173 for (GSList *gl = *defs_clip; gl != NULL; gl = gl->next) {
174 SPDefs *defs= (SPDefs *) SP_DOCUMENT_DEFS(doc);
175 Inkscape::XML::Node *repr = (Inkscape::XML::Node *) gl->data;
176 gchar const *id = repr->attribute("id");
177 if (!id || !doc->getObjectById(id)) {
178 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(doc);
179 Inkscape::XML::Node *copy = repr->duplicate(xml_doc);
180 SP_OBJECT_REPR(defs)->addChild(copy, NULL);
181 Inkscape::GC::release(copy);
182 }
183 }
184 }
186 GSList *sp_selection_paste_impl (SPDocument *doc, SPObject *parent, GSList **clip, GSList **defs_clip)
187 {
188 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(doc);
189 paste_defs (defs_clip, doc);
191 GSList *copied = NULL;
192 // add objects to document
193 for (GSList *l = *clip; l != NULL; l = l->next) {
194 Inkscape::XML::Node *repr = (Inkscape::XML::Node *) l->data;
195 Inkscape::XML::Node *copy = repr->duplicate(xml_doc);
197 // premultiply the item transform by the accumulated parent transform in the paste layer
198 NR::Matrix local = sp_item_i2doc_affine(SP_ITEM(parent));
199 if (!local.test_identity()) {
200 gchar const *t_str = copy->attribute("transform");
201 NR::Matrix item_t (NR::identity());
202 if (t_str)
203 sp_svg_transform_read(t_str, &item_t);
204 item_t *= local.inverse();
205 // (we're dealing with unattached repr, so we write to its attr instead of using sp_item_set_transform)
206 gchar *affinestr=sp_svg_transform_write(item_t);
207 copy->setAttribute("transform", affinestr);
208 g_free(affinestr);
209 }
211 parent->appendChildRepr(copy);
212 copied = g_slist_prepend(copied, copy);
213 Inkscape::GC::release(copy);
214 }
215 return copied;
216 }
218 void sp_selection_delete_impl(GSList const *items, bool propagate = true, bool propagate_descendants = true)
219 {
220 for (GSList const *i = items ; i ; i = i->next ) {
221 sp_object_ref((SPObject *)i->data, NULL);
222 }
223 for (GSList const *i = items; i != NULL; i = i->next) {
224 SPItem *item = (SPItem *) i->data;
225 SP_OBJECT(item)->deleteObject(propagate, propagate_descendants);
226 sp_object_unref((SPObject *)item, NULL);
227 }
228 }
231 void sp_selection_delete()
232 {
233 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
234 if (desktop == NULL) {
235 return;
236 }
238 if (tools_isactive (desktop, TOOLS_TEXT))
239 if (sp_text_delete_selection(desktop->event_context)) {
240 sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
241 _("Delete text"));
242 return;
243 }
245 Inkscape::Selection *selection = sp_desktop_selection(desktop);
247 // check if something is selected
248 if (selection->isEmpty()) {
249 desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("<b>Nothing</b> was deleted."));
250 return;
251 }
253 GSList const *selected = g_slist_copy(const_cast<GSList *>(selection->itemList()));
254 selection->clear();
255 sp_selection_delete_impl (selected);
256 g_slist_free ((GSList *) selected);
258 /* a tool may have set up private information in it's selection context
259 * that depends on desktop items. I think the only sane way to deal with
260 * this currently is to reset the current tool, which will reset it's
261 * associated selection context. For example: deleting an object
262 * while moving it around the canvas.
263 */
264 tools_switch ( desktop, tools_active ( desktop ) );
266 sp_document_done(sp_desktop_document(desktop), SP_VERB_EDIT_DELETE,
267 _("Delete"));
268 }
270 /* fixme: sequencing */
271 void sp_selection_duplicate()
272 {
273 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
274 if (desktop == NULL)
275 return;
277 Inkscape::XML::Document* xml_doc = sp_document_repr_doc(desktop->doc());
278 Inkscape::Selection *selection = sp_desktop_selection(desktop);
280 // check if something is selected
281 if (selection->isEmpty()) {
282 desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to duplicate."));
283 return;
284 }
286 GSList *reprs = g_slist_copy((GSList *) selection->reprList());
288 selection->clear();
290 // sorting items from different parents sorts each parent's subset without possibly mixing them, just what we need
291 reprs = g_slist_sort(reprs, (GCompareFunc) sp_repr_compare_position);
293 GSList *newsel = NULL;
295 while (reprs) {
296 Inkscape::XML::Node *parent = ((Inkscape::XML::Node *) reprs->data)->parent();
297 Inkscape::XML::Node *copy = ((Inkscape::XML::Node *) reprs->data)->duplicate(xml_doc);
299 parent->appendChild(copy);
301 newsel = g_slist_prepend(newsel, copy);
302 reprs = g_slist_remove(reprs, reprs->data);
303 Inkscape::GC::release(copy);
304 }
306 sp_document_done(sp_desktop_document(desktop), SP_VERB_EDIT_DUPLICATE,
307 _("Duplicate"));
309 selection->setReprList(newsel);
311 g_slist_free(newsel);
312 }
314 void sp_edit_clear_all()
315 {
316 SPDesktop *dt = SP_ACTIVE_DESKTOP;
317 if (!dt)
318 return;
320 SPDocument *doc = sp_desktop_document(dt);
321 sp_desktop_selection(dt)->clear();
323 g_return_if_fail(SP_IS_GROUP(dt->currentLayer()));
324 GSList *items = sp_item_group_item_list(SP_GROUP(dt->currentLayer()));
326 while (items) {
327 SP_OBJECT (items->data)->deleteObject();
328 items = g_slist_remove(items, items->data);
329 }
331 sp_document_done(doc, SP_VERB_EDIT_CLEAR_ALL,
332 _("Delete all"));
333 }
335 GSList *
336 get_all_items (GSList *list, SPObject *from, SPDesktop *desktop, bool onlyvisible, bool onlysensitive, GSList const *exclude)
337 {
338 for (SPObject *child = sp_object_first_child(SP_OBJECT(from)) ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
339 if (SP_IS_ITEM(child) &&
340 !desktop->isLayer(SP_ITEM(child)) &&
341 (!onlysensitive || !SP_ITEM(child)->isLocked()) &&
342 (!onlyvisible || !desktop->itemIsHidden(SP_ITEM(child))) &&
343 (!exclude || !g_slist_find ((GSList *) exclude, child))
344 )
345 {
346 list = g_slist_prepend (list, SP_ITEM(child));
347 }
349 if (SP_IS_ITEM(child) && desktop->isLayer(SP_ITEM(child))) {
350 list = get_all_items (list, child, desktop, onlyvisible, onlysensitive, exclude);
351 }
352 }
354 return list;
355 }
357 void sp_edit_select_all_full (bool force_all_layers, bool invert)
358 {
359 SPDesktop *dt = SP_ACTIVE_DESKTOP;
360 if (!dt)
361 return;
363 Inkscape::Selection *selection = sp_desktop_selection(dt);
365 g_return_if_fail(SP_IS_GROUP(dt->currentLayer()));
367 PrefsSelectionContext inlayer = (PrefsSelectionContext)prefs_get_int_attribute ("options.kbselection", "inlayer", PREFS_SELECTION_LAYER);
368 bool onlyvisible = prefs_get_int_attribute ("options.kbselection", "onlyvisible", 1);
369 bool onlysensitive = prefs_get_int_attribute ("options.kbselection", "onlysensitive", 1);
371 GSList *items = NULL;
373 GSList const *exclude = NULL;
374 if (invert) {
375 exclude = selection->itemList();
376 }
378 if (force_all_layers)
379 inlayer = PREFS_SELECTION_ALL;
381 switch (inlayer) {
382 case PREFS_SELECTION_LAYER: {
383 if ( (onlysensitive && SP_ITEM(dt->currentLayer())->isLocked()) ||
384 (onlyvisible && dt->itemIsHidden(SP_ITEM(dt->currentLayer()))) )
385 return;
387 GSList *all_items = sp_item_group_item_list(SP_GROUP(dt->currentLayer()));
389 for (GSList *i = all_items; i; i = i->next) {
390 SPItem *item = SP_ITEM (i->data);
392 if (item && (!onlysensitive || !item->isLocked())) {
393 if (!onlyvisible || !dt->itemIsHidden(item)) {
394 if (!dt->isLayer(item)) {
395 if (!invert || !g_slist_find ((GSList *) exclude, item)) {
396 items = g_slist_prepend (items, item); // leave it in the list
397 }
398 }
399 }
400 }
401 }
403 g_slist_free (all_items);
404 break;
405 }
406 case PREFS_SELECTION_LAYER_RECURSIVE: {
407 items = get_all_items (NULL, dt->currentLayer(), dt, onlyvisible, onlysensitive, exclude);
408 break;
409 }
410 default: {
411 items = get_all_items (NULL, dt->currentRoot(), dt, onlyvisible, onlysensitive, exclude);
412 break;
413 }
414 }
416 selection->setList (items);
418 if (items) {
419 g_slist_free (items);
420 }
421 }
423 void sp_edit_select_all ()
424 {
425 sp_edit_select_all_full (false, false);
426 }
428 void sp_edit_select_all_in_all_layers ()
429 {
430 sp_edit_select_all_full (true, false);
431 }
433 void sp_edit_invert ()
434 {
435 sp_edit_select_all_full (false, true);
436 }
438 void sp_edit_invert_in_all_layers ()
439 {
440 sp_edit_select_all_full (true, true);
441 }
443 void sp_selection_group()
444 {
445 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
446 if (desktop == NULL)
447 return;
449 SPDocument *doc = sp_desktop_document (desktop);
450 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(doc);
452 Inkscape::Selection *selection = sp_desktop_selection(desktop);
454 // Check if something is selected.
455 if (selection->isEmpty()) {
456 desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>some objects</b> to group."));
457 return;
458 }
460 GSList const *l = (GSList *) selection->reprList();
462 GSList *p = g_slist_copy((GSList *) l);
464 selection->clear();
466 p = g_slist_sort(p, (GCompareFunc) sp_repr_compare_position);
468 // Remember the position and parent of the topmost object.
469 gint topmost = ((Inkscape::XML::Node *) g_slist_last(p)->data)->position();
470 Inkscape::XML::Node *topmost_parent = ((Inkscape::XML::Node *) g_slist_last(p)->data)->parent();
472 Inkscape::XML::Node *group = xml_doc->createElement("svg:g");
474 while (p) {
475 Inkscape::XML::Node *current = (Inkscape::XML::Node *) p->data;
477 if (current->parent() == topmost_parent) {
478 Inkscape::XML::Node *spnew = current->duplicate(xml_doc);
479 sp_repr_unparent(current);
480 group->appendChild(spnew);
481 Inkscape::GC::release(spnew);
482 topmost --; // only reduce count for those items deleted from topmost_parent
483 } else { // move it to topmost_parent first
484 GSList *temp_clip = NULL;
486 // At this point, current may already have no item, due to its being a clone whose original is already moved away
487 // So we copy it artificially calculating the transform from its repr->attr("transform") and the parent transform
488 gchar const *t_str = current->attribute("transform");
489 NR::Matrix item_t (NR::identity());
490 if (t_str)
491 sp_svg_transform_read(t_str, &item_t);
492 item_t *= sp_item_i2doc_affine(SP_ITEM(doc->getObjectByRepr(current->parent())));
493 //FIXME: when moving both clone and original from a transformed group (either by
494 //grouping into another parent, or by cut/paste) the transform from the original's
495 //parent becomes embedded into original itself, and this affects its clones. Fix
496 //this by remembering the transform diffs we write to each item into an array and
497 //then, if this is clone, looking up its original in that array and pre-multiplying
498 //it by the inverse of that original's transform diff.
500 sp_selection_copy_one (current, item_t, &temp_clip, xml_doc);
501 sp_repr_unparent(current);
503 // paste into topmost_parent (temporarily)
504 GSList *copied = sp_selection_paste_impl (doc, doc->getObjectByRepr(topmost_parent), &temp_clip, NULL);
505 if (temp_clip) g_slist_free (temp_clip);
506 if (copied) { // if success,
507 // take pasted object (now in topmost_parent)
508 Inkscape::XML::Node *in_topmost = (Inkscape::XML::Node *) copied->data;
509 // make a copy
510 Inkscape::XML::Node *spnew = in_topmost->duplicate(xml_doc);
511 // remove pasted
512 sp_repr_unparent(in_topmost);
513 // put its copy into group
514 group->appendChild(spnew);
515 Inkscape::GC::release(spnew);
516 g_slist_free (copied);
517 }
518 }
519 p = g_slist_remove(p, current);
520 }
522 // Add the new group to the topmost members' parent
523 topmost_parent->appendChild(group);
525 // Move to the position of the topmost, reduced by the number of items deleted from topmost_parent
526 group->setPosition(topmost + 1);
528 sp_document_done(sp_desktop_document(desktop), SP_VERB_SELECTION_GROUP,
529 _("Group"));
531 selection->set(group);
532 Inkscape::GC::release(group);
533 }
535 void sp_selection_ungroup()
536 {
537 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
538 if (desktop == NULL)
539 return;
541 Inkscape::Selection *selection = sp_desktop_selection(desktop);
543 if (selection->isEmpty()) {
544 desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select a <b>group</b> to ungroup."));
545 return;
546 }
548 GSList *items = g_slist_copy((GSList *) selection->itemList());
549 selection->clear();
551 // Get a copy of current selection.
552 GSList *new_select = NULL;
553 bool ungrouped = false;
554 for (GSList *i = items;
555 i != NULL;
556 i = i->next)
557 {
558 SPItem *group = (SPItem *) i->data;
560 // when ungrouping cloned groups with their originals, some objects that were selected may no more exist due to unlinking
561 if (!SP_IS_OBJECT(group)) {
562 continue;
563 }
565 /* We do not allow ungrouping <svg> etc. (lauris) */
566 if (strcmp(SP_OBJECT_REPR(group)->name(), "svg:g") && strcmp(SP_OBJECT_REPR(group)->name(), "svg:switch")) {
567 // keep the non-group item in the new selection
568 selection->add(group);
569 continue;
570 }
572 GSList *children = NULL;
573 /* This is not strictly required, but is nicer to rely on group ::destroy (lauris) */
574 sp_item_group_ungroup(SP_GROUP(group), &children, false);
575 ungrouped = true;
576 // Add ungrouped items to the new selection.
577 new_select = g_slist_concat(new_select, children);
578 }
580 if (new_select) { // Set new selection.
581 selection->addList(new_select);
582 g_slist_free(new_select);
583 }
584 if (!ungrouped) {
585 desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("<b>No groups</b> to ungroup in the selection."));
586 }
588 g_slist_free(items);
590 sp_document_done(sp_desktop_document(desktop), SP_VERB_SELECTION_UNGROUP,
591 _("Ungroup"));
592 }
594 static SPGroup *
595 sp_item_list_common_parent_group(GSList const *items)
596 {
597 if (!items) {
598 return NULL;
599 }
600 SPObject *parent = SP_OBJECT_PARENT(items->data);
601 /* Strictly speaking this CAN happen, if user selects <svg> from Inkscape::XML editor */
602 if (!SP_IS_GROUP(parent)) {
603 return NULL;
604 }
605 for (items = items->next; items; items = items->next) {
606 if (SP_OBJECT_PARENT(items->data) != parent) {
607 return NULL;
608 }
609 }
611 return SP_GROUP(parent);
612 }
614 /** Finds out the minimum common bbox of the selected items. */
615 static NR::Maybe<NR::Rect>
616 enclose_items(GSList const *items)
617 {
618 g_assert(items != NULL);
620 NR::Maybe<NR::Rect> r = NR::Nothing();
621 for (GSList const *i = items; i; i = i->next) {
622 r = NR::union_bounds(r, sp_item_bbox_desktop((SPItem *) i->data));
623 }
624 return r;
625 }
627 SPObject *
628 prev_sibling(SPObject *child)
629 {
630 SPObject *parent = SP_OBJECT_PARENT(child);
631 if (!SP_IS_GROUP(parent)) {
632 return NULL;
633 }
634 for ( SPObject *i = sp_object_first_child(parent) ; i; i = SP_OBJECT_NEXT(i) ) {
635 if (i->next == child)
636 return i;
637 }
638 return NULL;
639 }
641 void
642 sp_selection_raise()
643 {
644 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
645 if (!desktop)
646 return;
648 Inkscape::Selection *selection = sp_desktop_selection(desktop);
650 GSList const *items = (GSList *) selection->itemList();
651 if (!items) {
652 desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to raise."));
653 return;
654 }
656 SPGroup const *group = sp_item_list_common_parent_group(items);
657 if (!group) {
658 desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("You cannot raise/lower objects from <b>different groups</b> or <b>layers</b>."));
659 return;
660 }
662 Inkscape::XML::Node *grepr = SP_OBJECT_REPR(group);
664 /* Construct reverse-ordered list of selected children. */
665 GSList *rev = g_slist_copy((GSList *) items);
666 rev = g_slist_sort(rev, (GCompareFunc) sp_item_repr_compare_position);
668 // Determine the common bbox of the selected items.
669 NR::Maybe<NR::Rect> selected = enclose_items(items);
671 // Iterate over all objects in the selection (starting from top).
672 if (selected) {
673 while (rev) {
674 SPObject *child = SP_OBJECT(rev->data);
675 // for each selected object, find the next sibling
676 for (SPObject *newref = child->next; newref; newref = newref->next) {
677 // if the sibling is an item AND overlaps our selection,
678 if (SP_IS_ITEM(newref)) {
679 NR::Maybe<NR::Rect> newref_bbox = sp_item_bbox_desktop(SP_ITEM(newref));
680 if ( newref_bbox && selected->intersects(*newref_bbox) ) {
681 // AND if it's not one of our selected objects,
682 if (!g_slist_find((GSList *) items, newref)) {
683 // move the selected object after that sibling
684 grepr->changeOrder(SP_OBJECT_REPR(child), SP_OBJECT_REPR(newref));
685 }
686 break;
687 }
688 }
689 }
690 rev = g_slist_remove(rev, child);
691 }
692 } else {
693 g_slist_free(rev);
694 }
696 sp_document_done(sp_desktop_document(desktop), SP_VERB_SELECTION_RAISE,
697 _("Raise"));
698 }
700 void sp_selection_raise_to_top()
701 {
702 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
703 if (desktop == NULL)
704 return;
706 SPDocument *document = sp_desktop_document(desktop);
707 Inkscape::Selection *selection = sp_desktop_selection(desktop);
709 if (selection->isEmpty()) {
710 desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to raise to top."));
711 return;
712 }
714 GSList const *items = (GSList *) selection->itemList();
716 SPGroup const *group = sp_item_list_common_parent_group(items);
717 if (!group) {
718 desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("You cannot raise/lower objects from <b>different groups</b> or <b>layers</b>."));
719 return;
720 }
722 GSList *rl = g_slist_copy((GSList *) selection->reprList());
723 rl = g_slist_sort(rl, (GCompareFunc) sp_repr_compare_position);
725 for (GSList *l = rl; l != NULL; l = l->next) {
726 Inkscape::XML::Node *repr = (Inkscape::XML::Node *) l->data;
727 repr->setPosition(-1);
728 }
730 g_slist_free(rl);
732 sp_document_done(document, SP_VERB_SELECTION_TO_FRONT,
733 _("Raise to top"));
734 }
736 void
737 sp_selection_lower()
738 {
739 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
740 if (desktop == NULL)
741 return;
743 Inkscape::Selection *selection = sp_desktop_selection(desktop);
745 GSList const *items = (GSList *) selection->itemList();
746 if (!items) {
747 desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to lower."));
748 return;
749 }
751 SPGroup const *group = sp_item_list_common_parent_group(items);
752 if (!group) {
753 desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("You cannot raise/lower objects from <b>different groups</b> or <b>layers</b>."));
754 return;
755 }
757 Inkscape::XML::Node *grepr = SP_OBJECT_REPR(group);
759 // Determine the common bbox of the selected items.
760 NR::Maybe<NR::Rect> selected = enclose_items(items);
762 /* Construct direct-ordered list of selected children. */
763 GSList *rev = g_slist_copy((GSList *) items);
764 rev = g_slist_sort(rev, (GCompareFunc) sp_item_repr_compare_position);
765 rev = g_slist_reverse(rev);
767 // Iterate over all objects in the selection (starting from top).
768 if (selected) {
769 while (rev) {
770 SPObject *child = SP_OBJECT(rev->data);
771 // for each selected object, find the prev sibling
772 for (SPObject *newref = prev_sibling(child); newref; newref = prev_sibling(newref)) {
773 // if the sibling is an item AND overlaps our selection,
774 if (SP_IS_ITEM(newref)) {
775 NR::Maybe<NR::Rect> ref_bbox = sp_item_bbox_desktop(SP_ITEM(newref));
776 if ( ref_bbox && selected->intersects(*ref_bbox) ) {
777 // AND if it's not one of our selected objects,
778 if (!g_slist_find((GSList *) items, newref)) {
779 // move the selected object before that sibling
780 SPObject *put_after = prev_sibling(newref);
781 if (put_after)
782 grepr->changeOrder(SP_OBJECT_REPR(child), SP_OBJECT_REPR(put_after));
783 else
784 SP_OBJECT_REPR(child)->setPosition(0);
785 }
786 break;
787 }
788 }
789 }
790 rev = g_slist_remove(rev, child);
791 }
792 } else {
793 g_slist_free(rev);
794 }
796 sp_document_done(sp_desktop_document(desktop), SP_VERB_SELECTION_LOWER,
797 _("Lower"));
798 }
800 void sp_selection_lower_to_bottom()
801 {
802 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
803 if (desktop == NULL)
804 return;
806 SPDocument *document = sp_desktop_document(desktop);
807 Inkscape::Selection *selection = sp_desktop_selection(desktop);
809 if (selection->isEmpty()) {
810 desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to lower to bottom."));
811 return;
812 }
814 GSList const *items = (GSList *) selection->itemList();
816 SPGroup const *group = sp_item_list_common_parent_group(items);
817 if (!group) {
818 desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("You cannot raise/lower objects from <b>different groups</b> or <b>layers</b>."));
819 return;
820 }
822 GSList *rl;
823 rl = g_slist_copy((GSList *) selection->reprList());
824 rl = g_slist_sort(rl, (GCompareFunc) sp_repr_compare_position);
825 rl = g_slist_reverse(rl);
827 for (GSList *l = rl; l != NULL; l = l->next) {
828 gint minpos;
829 SPObject *pp, *pc;
830 Inkscape::XML::Node *repr = (Inkscape::XML::Node *) l->data;
831 pp = document->getObjectByRepr(sp_repr_parent(repr));
832 minpos = 0;
833 g_assert(SP_IS_GROUP(pp));
834 pc = sp_object_first_child(pp);
835 while (!SP_IS_ITEM(pc)) {
836 minpos += 1;
837 pc = pc->next;
838 }
839 repr->setPosition(minpos);
840 }
842 g_slist_free(rl);
844 sp_document_done(document, SP_VERB_SELECTION_TO_BACK,
845 _("Lower to bottom"));
846 }
848 void
849 sp_undo(SPDesktop *desktop, SPDocument *)
850 {
851 if (!sp_document_undo(sp_desktop_document(desktop)))
852 desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Nothing to undo."));
853 }
855 void
856 sp_redo(SPDesktop *desktop, SPDocument *)
857 {
858 if (!sp_document_redo(sp_desktop_document(desktop)))
859 desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Nothing to redo."));
860 }
862 void sp_selection_cut()
863 {
864 sp_selection_copy();
865 sp_selection_delete();
866 }
868 void sp_copy_gradient (GSList **defs_clip, SPGradient *gradient, Inkscape::XML::Document* xml_doc)
869 {
870 SPGradient *ref = gradient;
872 while (ref) {
873 // climb up the refs, copying each one in the chain
874 Inkscape::XML::Node *grad_repr = SP_OBJECT_REPR(ref)->duplicate(xml_doc);
875 *defs_clip = g_slist_prepend (*defs_clip, grad_repr);
877 ref = ref->ref->getObject();
878 }
879 }
881 void sp_copy_pattern (GSList **defs_clip, SPPattern *pattern, Inkscape::XML::Document* xml_doc)
882 {
883 SPPattern *ref = pattern;
885 while (ref) {
886 // climb up the refs, copying each one in the chain
887 Inkscape::XML::Node *pattern_repr = SP_OBJECT_REPR(ref)->duplicate(xml_doc);
888 *defs_clip = g_slist_prepend (*defs_clip, pattern_repr);
890 // items in the pattern may also use gradients and other patterns, so we need to recurse here as well
891 for (SPObject *child = sp_object_first_child(SP_OBJECT(ref)) ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
892 if (!SP_IS_ITEM (child))
893 continue;
894 sp_copy_stuff_used_by_item (defs_clip, (SPItem *) child, NULL, xml_doc);
895 }
897 ref = ref->ref->getObject();
898 }
899 }
901 void sp_copy_single (GSList **defs_clip, SPObject *thing, Inkscape::XML::Document* xml_doc)
902 {
903 Inkscape::XML::Node *duplicate_repr = SP_OBJECT_REPR(thing)->duplicate(xml_doc);
904 *defs_clip = g_slist_prepend (*defs_clip, duplicate_repr);
905 }
908 void sp_copy_textpath_path (GSList **defs_clip, SPTextPath *tp, GSList const *items, Inkscape::XML::Document* xml_doc)
909 {
910 SPItem *path = sp_textpath_get_path_item (tp);
911 if (!path)
912 return;
913 if (items && g_slist_find ((GSList *) items, path)) // do not copy it to defs if it is already in the list of items copied
914 return;
915 Inkscape::XML::Node *repr = SP_OBJECT_REPR(path)->duplicate(xml_doc);
916 *defs_clip = g_slist_prepend (*defs_clip, repr);
917 }
919 /**
920 * Copies things like patterns, markers, gradients, etc.
921 */
922 void sp_copy_stuff_used_by_item (GSList **defs_clip, SPItem *item, GSList const *items, Inkscape::XML::Document* xml_doc)
923 {
924 SPStyle *style = SP_OBJECT_STYLE (item);
926 if (style && (style->fill.isPaintserver())) {
927 SPObject *server = SP_OBJECT_STYLE_FILL_SERVER(item);
928 if (SP_IS_LINEARGRADIENT (server) || SP_IS_RADIALGRADIENT (server))
929 sp_copy_gradient (defs_clip, SP_GRADIENT(server), xml_doc);
930 if (SP_IS_PATTERN (server))
931 sp_copy_pattern (defs_clip, SP_PATTERN(server), xml_doc);
932 }
934 if (style && (style->stroke.isPaintserver())) {
935 SPObject *server = SP_OBJECT_STYLE_STROKE_SERVER(item);
936 if (SP_IS_LINEARGRADIENT (server) || SP_IS_RADIALGRADIENT (server))
937 sp_copy_gradient (defs_clip, SP_GRADIENT(server), xml_doc);
938 if (SP_IS_PATTERN (server))
939 sp_copy_pattern (defs_clip, SP_PATTERN(server), xml_doc);
940 }
942 // For shapes, copy all of the shape's markers into defs_clip
943 if (SP_IS_SHAPE (item)) {
944 SPShape *shape = SP_SHAPE (item);
945 for (int i = 0 ; i < SP_MARKER_LOC_QTY ; i++) {
946 if (shape->marker[i]) {
947 sp_copy_single (defs_clip, SP_OBJECT (shape->marker[i]), xml_doc);
948 }
949 }
951 // For shapes, also copy liveeffect if applicable
952 if (sp_shape_has_path_effect(shape)) {
953 sp_copy_single (defs_clip, SP_OBJECT(sp_shape_get_livepatheffectobject(shape)), xml_doc);
954 }
955 }
957 if (SP_IS_TEXT_TEXTPATH (item)) {
958 sp_copy_textpath_path (defs_clip, SP_TEXTPATH(sp_object_first_child(SP_OBJECT(item))), items, xml_doc);
959 }
961 if (item->clip_ref->getObject()) {
962 sp_copy_single (defs_clip, item->clip_ref->getObject(), xml_doc);
963 }
965 if (item->mask_ref->getObject()) {
966 SPObject *mask = item->mask_ref->getObject();
967 sp_copy_single (defs_clip, mask, xml_doc);
968 // recurse into the mask for its gradients etc.
969 for (SPObject *o = SP_OBJECT(mask)->children; o != NULL; o = o->next) {
970 if (SP_IS_ITEM(o))
971 sp_copy_stuff_used_by_item (defs_clip, SP_ITEM (o), items, xml_doc);
972 }
973 }
975 if (style->getFilter()) {
976 SPObject *filter = style->getFilter();
977 if (SP_IS_FILTER(filter)) {
978 sp_copy_single (defs_clip, filter, xml_doc);
979 }
980 }
982 // recurse
983 for (SPObject *o = SP_OBJECT(item)->children; o != NULL; o = o->next) {
984 if (SP_IS_ITEM(o))
985 sp_copy_stuff_used_by_item (defs_clip, SP_ITEM (o), items, xml_doc);
986 }
987 }
989 void
990 sp_set_style_clipboard (SPCSSAttr *css)
991 {
992 if (css != NULL) {
993 // clear style clipboard
994 if (style_clipboard) {
995 sp_repr_css_attr_unref (style_clipboard);
996 style_clipboard = NULL;
997 }
998 //sp_repr_css_print (css);
999 style_clipboard = css;
1000 }
1001 }
1003 /**
1004 * \pre item != NULL
1005 */
1006 SPCSSAttr *
1007 take_style_from_item (SPItem *item)
1008 {
1009 // write the complete cascaded style, context-free
1010 SPCSSAttr *css = sp_css_attr_from_object (SP_OBJECT(item), SP_STYLE_FLAG_ALWAYS);
1011 if (css == NULL)
1012 return NULL;
1014 if ((SP_IS_GROUP(item) && SP_OBJECT(item)->children) ||
1015 (SP_IS_TEXT (item) && SP_OBJECT(item)->children && SP_OBJECT(item)->children->next == NULL)) {
1016 // if this is a text with exactly one tspan child, merge the style of that tspan as well
1017 // If this is a group, merge the style of its topmost (last) child with style
1018 for (SPObject *last_element = item->lastChild(); last_element != NULL; last_element = SP_OBJECT_PREV (last_element)) {
1019 if (SP_OBJECT_STYLE (last_element) != NULL) {
1020 SPCSSAttr *temp = sp_css_attr_from_object (last_element, SP_STYLE_FLAG_IFSET);
1021 if (temp) {
1022 sp_repr_css_merge (css, temp);
1023 sp_repr_css_attr_unref (temp);
1024 }
1025 break;
1026 }
1027 }
1028 }
1029 if (!(SP_IS_TEXT (item) || SP_IS_TSPAN (item) || SP_IS_TREF(item) || SP_IS_STRING (item))) {
1030 // do not copy text properties from non-text objects, it's confusing
1031 css = sp_css_attr_unset_text (css);
1032 }
1034 // FIXME: also transform gradient/pattern fills, by forking? NO, this must be nondestructive
1035 double ex = NR::expansion(sp_item_i2doc_affine(item));
1036 if (ex != 1.0) {
1037 css = sp_css_attr_scale (css, ex);
1038 }
1040 return css;
1041 }
1044 void sp_selection_copy()
1045 {
1046 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1047 if (desktop == NULL)
1048 return;
1050 if (!clipboard_document) {
1051 clipboard_document = new Inkscape::XML::SimpleDocument();
1052 }
1054 Inkscape::Selection *selection = sp_desktop_selection(desktop);
1056 if (tools_isactive (desktop, TOOLS_DROPPER)) {
1057 sp_dropper_context_copy(desktop->event_context);
1058 return; // copied color under cursor, nothing else to do
1059 }
1061 if (desktop->event_context->get_drag() && desktop->event_context->get_drag()->copy()) {
1062 return; // copied selected stop(s), nothing else to do
1063 }
1065 // check if something is selected
1066 if (selection->isEmpty()) {
1067 desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Nothing was copied."));
1068 return;
1069 }
1071 GSList const *items = g_slist_copy ((GSList *) selection->itemList());
1073 // 0. Copy text to system clipboard
1074 // FIXME: for non-texts, put serialized Inkscape::XML as text to the clipboard;
1075 //for this sp_repr_write_stream needs to be rewritten with iostream instead of FILE
1076 Glib::ustring text;
1077 if (tools_isactive (desktop, TOOLS_TEXT)) {
1078 text = sp_text_get_selected_text(desktop->event_context);
1079 }
1081 if (text.empty()) {
1082 guint texts = 0;
1083 for (GSList *i = (GSList *) items; i; i = i->next) {
1084 SPItem *item = SP_ITEM (i->data);
1085 if (SP_IS_TEXT (item) || SP_IS_FLOWTEXT(item)) {
1086 if (texts > 0) // if more than one text object is copied, separate them by spaces
1087 text += " ";
1088 gchar *this_text = sp_te_get_string_multiline (item);
1089 if (this_text) {
1090 text += this_text;
1091 g_free(this_text);
1092 }
1093 texts++;
1094 }
1095 }
1096 }
1097 if (!text.empty()) {
1098 Glib::RefPtr<Gtk::Clipboard> refClipboard = Gtk::Clipboard::get();
1099 refClipboard->set_text(text);
1100 }
1102 // clear old defs clipboard
1103 while (defs_clipboard) {
1104 Inkscape::GC::release((Inkscape::XML::Node *) defs_clipboard->data);
1105 defs_clipboard = g_slist_remove (defs_clipboard, defs_clipboard->data);
1106 }
1108 // clear style clipboard
1109 if (style_clipboard) {
1110 sp_repr_css_attr_unref (style_clipboard);
1111 style_clipboard = NULL;
1112 }
1114 //clear main clipboard
1115 while (clipboard) {
1116 Inkscape::GC::release((Inkscape::XML::Node *) clipboard->data);
1117 clipboard = g_slist_remove(clipboard, clipboard->data);
1118 }
1120 sp_selection_copy_impl (items, &clipboard, &defs_clipboard, &style_clipboard, clipboard_document);
1122 if (tools_isactive (desktop, TOOLS_TEXT)) { // take style from cursor/text selection, overwriting the style just set by copy_impl
1123 SPStyle *const query = sp_style_new(SP_ACTIVE_DOCUMENT);
1124 if (sp_desktop_query_style_all (desktop, query)) {
1125 SPCSSAttr *css = sp_css_attr_from_style (query, SP_STYLE_FLAG_ALWAYS);
1126 sp_set_style_clipboard (css);
1127 }
1128 sp_style_unref(query);
1129 }
1131 size_clipboard = selection->bounds();
1133 g_slist_free ((GSList *) items);
1134 }
1137 void sp_selection_copy_lpe_pathparam(Inkscape::LivePathEffect::PathParam * pathparam)
1138 {
1139 if (pathparam == NULL)
1140 return;
1142 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1143 if (desktop == NULL)
1144 return;
1146 if (!clipboard_document) {
1147 clipboard_document = new Inkscape::XML::SimpleDocument();
1148 }
1150 // clear old defs clipboard
1151 while (defs_clipboard) {
1152 Inkscape::GC::release((Inkscape::XML::Node *) defs_clipboard->data);
1153 defs_clipboard = g_slist_remove (defs_clipboard, defs_clipboard->data);
1154 }
1156 // clear style clipboard
1157 if (style_clipboard) {
1158 sp_repr_css_attr_unref (style_clipboard);
1159 style_clipboard = NULL;
1160 }
1162 //clear main clipboard
1163 while (clipboard) {
1164 Inkscape::GC::release((Inkscape::XML::Node *) clipboard->data);
1165 clipboard = g_slist_remove(clipboard, clipboard->data);
1166 }
1168 // make new path node and put svgd as 'd' attribute
1169 Inkscape::XML::Node *newnode = clipboard_document->createElement("svg:path");
1170 gchar * svgd = pathparam->param_writeSVGValue();
1171 newnode->setAttribute("d", svgd);
1172 g_free(svgd);
1174 clipboard = g_slist_prepend(clipboard, newnode);
1176 Geom::Rect bnds = Geom::bounds_exact(*pathparam);
1177 size_clipboard = from_2geom(bnds);
1178 }
1181 //____________________________________________________________________________
1183 /** Paste the bitmap in the clipboard if one is in there.
1184 The bitmap is saved to a PNG file then imported into the document
1186 @return true if a bitmap was detected and pasted; false if no bitmap
1187 */
1188 static bool pastedPicFromClipboard()
1189 {
1190 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1191 SPDocument *doc = SP_ACTIVE_DOCUMENT;
1192 if ( desktop == NULL || doc == NULL)
1193 return false;
1195 Glib::RefPtr<Gtk::Clipboard> refClipboard = Gtk::Clipboard::get();
1196 Glib::RefPtr<Gdk::Pixbuf> pic = refClipboard->wait_for_image();
1198 // Stop if the system clipboard doesn't have a bitmap.
1199 if ( pic == 0 )
1200 {
1201 return false;
1202 } //if
1203 else
1204 {
1205 // Write into a file, then import the file into the document.
1206 // Make a file name based on current time; use the current working dir.
1207 time_t rawtime;
1208 char filename[50];
1209 const char* path;
1211 time ( &rawtime );
1212 strftime (filename,50,"pastedpic_%m%d%Y_%H%M%S.png",localtime( &rawtime ));
1213 path = (char *)prefs_get_string_attribute("dialogs.save_as", "path");
1214 Glib::ustring finalPath = path;
1215 finalPath.append(G_DIR_SEPARATOR_S).append(filename);
1216 pic->save( finalPath, "png" );
1217 file_import(doc, finalPath, NULL);
1219 // Clear the clipboard so that the bitmap in there won't always over
1220 // ride the normal inkscape clipboard.This isn't the ideal solution.
1221 refClipboard->set_text("");
1222 return true;
1223 } //else
1225 return false;
1226 } //pastedPicFromClipboard
1228 //____________________________________________________________________________
1230 void sp_selection_paste(bool in_place)
1231 {
1232 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1234 if (desktop == NULL) {
1235 return;
1236 }
1238 SPDocument *document = sp_desktop_document(desktop);
1240 if (Inkscape::have_viable_layer(desktop, desktop->messageStack()) == false) {
1241 return;
1242 }
1244 Inkscape::Selection *selection = sp_desktop_selection(desktop);
1246 if (tools_isactive (desktop, TOOLS_TEXT)) {
1247 if (sp_text_paste_inline(desktop->event_context))
1248 return; // pasted from system clipboard into text, nothing else to do
1249 }
1251 // check if something is in the clipboard
1253 // Stop if successfully pasted a clipboard bitmap.
1254 if ( pastedPicFromClipboard() )
1255 return;
1258 if (clipboard == NULL) {
1259 desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Nothing in the clipboard."));
1260 return;
1261 }
1263 GSList *copied = sp_selection_paste_impl(document, desktop->currentLayer(), &clipboard, &defs_clipboard);
1264 // add pasted objects to selection
1265 selection->setReprList((GSList const *) copied);
1266 g_slist_free (copied);
1268 if (!in_place) {
1269 sp_document_ensure_up_to_date(document);
1271 NR::Maybe<NR::Rect> sel_bbox = selection->bounds();
1272 NR::Point m( desktop->point() );
1273 if (sel_bbox) {
1274 m -= sel_bbox->midpoint();
1275 }
1277 sp_selection_move_relative(selection, m);
1278 }
1280 sp_document_done(document, SP_VERB_EDIT_PASTE, _("Paste"));
1281 }
1283 void sp_selection_paste_style()
1284 {
1285 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1286 if (desktop == NULL) return;
1288 Inkscape::Selection *selection = sp_desktop_selection(desktop);
1290 // check if something is in the clipboard
1291 if (style_clipboard == NULL) {
1292 desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Nothing on the style clipboard."));
1293 return;
1294 }
1296 // check if something is selected
1297 if (selection->isEmpty()) {
1298 desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to paste style to."));
1299 return;
1300 }
1302 paste_defs (&defs_clipboard, sp_desktop_document(desktop));
1304 sp_desktop_set_style (desktop, style_clipboard);
1306 sp_document_done(sp_desktop_document (desktop), SP_VERB_EDIT_PASTE_STYLE,
1307 _("Paste style"));
1308 }
1310 void sp_selection_paste_livepatheffect()
1311 {
1312 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1313 if (desktop == NULL) return;
1315 Inkscape::Selection *selection = sp_desktop_selection(desktop);
1317 // check if something is in the clipboard
1318 if (clipboard == NULL) {
1319 desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Nothing on the clipboard."));
1320 return;
1321 }
1323 // check if something is selected
1324 if (selection->isEmpty()) {
1325 desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to paste live path effect to."));
1326 return;
1327 }
1329 SPDocument *doc = sp_desktop_document(desktop);
1330 paste_defs (&defs_clipboard, doc);
1332 Inkscape::XML::Node *repr = (Inkscape::XML::Node *) clipboard->data;
1333 char const *effecturi = repr->attribute("inkscape:path-effect");
1334 if (!effecturi) {
1335 SP_ACTIVE_DESKTOP->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Clipboard does not contain a live path effect."));
1336 return;
1337 }
1339 for ( GSList const *itemlist = selection->itemList(); itemlist != NULL; itemlist = g_slist_next(itemlist) ) {
1340 SPItem *item = reinterpret_cast<SPItem*>(itemlist->data);
1341 if ( item && SP_IS_SHAPE(item) ) {
1342 SPShape * shape = SP_SHAPE(item);
1344 // create a private LPE object!
1345 SPObject * obj = sp_uri_reference_resolve(doc, effecturi);
1346 LivePathEffectObject * lpeobj = LIVEPATHEFFECT(obj)->fork_private_if_necessary(0);
1348 sp_shape_set_path_effect(shape, lpeobj);
1350 // set inkscape:original-d for paths. the other shapes don't need this.
1351 if ( SP_IS_PATH(item) ) {
1352 Inkscape::XML::Node *pathrepr = SP_OBJECT_REPR(item);
1353 if ( ! pathrepr->attribute("inkscape:original-d") ) {
1354 pathrepr->setAttribute("inkscape:original-d", pathrepr->attribute("d"));
1355 }
1356 }
1357 }
1358 }
1360 sp_document_done(sp_desktop_document (desktop), SP_VERB_EDIT_PASTE_LIVEPATHEFFECT,
1361 _("Paste live path effect"));
1362 }
1364 void sp_selection_paste_size (bool apply_x, bool apply_y)
1365 {
1366 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1367 if (desktop == NULL) return;
1369 Inkscape::Selection *selection = sp_desktop_selection(desktop);
1371 // check if something is in the clipboard
1372 if (!size_clipboard) {
1373 desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Nothing on the clipboard."));
1374 return;
1375 }
1377 // check if something is selected
1378 if (selection->isEmpty()) {
1379 desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to paste size to."));
1380 return;
1381 }
1383 NR::Maybe<NR::Rect> current = selection->bounds();
1384 if ( !current || current->isEmpty() ) {
1385 return;
1386 }
1388 double scale_x = size_clipboard->extent(NR::X) / current->extent(NR::X);
1389 double scale_y = size_clipboard->extent(NR::Y) / current->extent(NR::Y);
1391 sp_selection_scale_relative (selection, current->midpoint(),
1392 NR::scale(
1393 apply_x? scale_x : (desktop->isToolboxButtonActive ("lock")? scale_y : 1.0),
1394 apply_y? scale_y : (desktop->isToolboxButtonActive ("lock")? scale_x : 1.0)));
1396 sp_document_done(sp_desktop_document (desktop), SP_VERB_EDIT_PASTE_SIZE,
1397 _("Paste size"));
1398 }
1400 void sp_selection_paste_size_separately (bool apply_x, bool apply_y)
1401 {
1402 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1403 if (desktop == NULL) return;
1405 Inkscape::Selection *selection = sp_desktop_selection(desktop);
1407 // check if something is in the clipboard
1408 if ( !size_clipboard ) {
1409 desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Nothing on the clipboard."));
1410 return;
1411 }
1413 // check if something is selected
1414 if (selection->isEmpty()) {
1415 desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to paste size to."));
1416 return;
1417 }
1419 for (GSList const *l = selection->itemList(); l != NULL; l = l->next) {
1420 SPItem *item = SP_ITEM(l->data);
1422 NR::Maybe<NR::Rect> current = sp_item_bbox_desktop(item);
1423 if ( !current || current->isEmpty() ) {
1424 continue;
1425 }
1427 double scale_x = size_clipboard->extent(NR::X) / current->extent(NR::X);
1428 double scale_y = size_clipboard->extent(NR::Y) / current->extent(NR::Y);
1430 sp_item_scale_rel (item,
1431 NR::scale(
1432 apply_x? scale_x : (desktop->isToolboxButtonActive ("lock")? scale_y : 1.0),
1433 apply_y? scale_y : (desktop->isToolboxButtonActive ("lock")? scale_x : 1.0)));
1435 }
1437 sp_document_done(sp_desktop_document (desktop), SP_VERB_EDIT_PASTE_SIZE_SEPARATELY,
1438 _("Paste size separately"));
1439 }
1441 void sp_selection_to_next_layer ()
1442 {
1443 SPDesktop *dt = SP_ACTIVE_DESKTOP;
1445 Inkscape::Selection *selection = sp_desktop_selection(dt);
1447 // check if something is selected
1448 if (selection->isEmpty()) {
1449 dt->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to move to the layer above."));
1450 return;
1451 }
1453 GSList const *items = g_slist_copy ((GSList *) selection->itemList());
1455 bool no_more = false; // Set to true, if no more layers above
1456 SPObject *next=Inkscape::next_layer(dt->currentRoot(), dt->currentLayer());
1457 if (next) {
1458 GSList *temp_clip = NULL;
1459 sp_selection_copy_impl (items, &temp_clip, NULL, NULL, sp_document_repr_doc(dt->doc())); // we're in the same doc, so no need to copy defs
1460 sp_selection_delete_impl (items, false, false);
1461 next=Inkscape::next_layer(dt->currentRoot(), dt->currentLayer()); // Fixes bug 1482973: crash while moving layers
1462 GSList *copied;
1463 if(next) {
1464 copied = sp_selection_paste_impl (sp_desktop_document (dt), next, &temp_clip, NULL);
1465 } else {
1466 copied = sp_selection_paste_impl (sp_desktop_document (dt), dt->currentLayer(), &temp_clip, NULL);
1467 no_more = true;
1468 }
1469 selection->setReprList((GSList const *) copied);
1470 g_slist_free (copied);
1471 if (temp_clip) g_slist_free (temp_clip);
1472 if (next) dt->setCurrentLayer(next);
1473 sp_document_done(sp_desktop_document (dt), SP_VERB_LAYER_MOVE_TO_NEXT,
1474 _("Raise to next layer"));
1475 } else {
1476 no_more = true;
1477 }
1479 if (no_more) {
1480 dt->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("No more layers above."));
1481 }
1483 g_slist_free ((GSList *) items);
1484 }
1486 void sp_selection_to_prev_layer ()
1487 {
1488 SPDesktop *dt = SP_ACTIVE_DESKTOP;
1490 Inkscape::Selection *selection = sp_desktop_selection(dt);
1492 // check if something is selected
1493 if (selection->isEmpty()) {
1494 dt->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to move to the layer below."));
1495 return;
1496 }
1498 GSList const *items = g_slist_copy ((GSList *) selection->itemList());
1500 bool no_more = false; // Set to true, if no more layers below
1501 SPObject *next=Inkscape::previous_layer(dt->currentRoot(), dt->currentLayer());
1502 if (next) {
1503 GSList *temp_clip = NULL;
1504 sp_selection_copy_impl (items, &temp_clip, NULL, NULL, sp_document_repr_doc(dt->doc())); // we're in the same doc, so no need to copy defs
1505 sp_selection_delete_impl (items, false, false);
1506 next=Inkscape::previous_layer(dt->currentRoot(), dt->currentLayer()); // Fixes bug 1482973: crash while moving layers
1507 GSList *copied;
1508 if(next) {
1509 copied = sp_selection_paste_impl (sp_desktop_document (dt), next, &temp_clip, NULL);
1510 } else {
1511 copied = sp_selection_paste_impl (sp_desktop_document (dt), dt->currentLayer(), &temp_clip, NULL);
1512 no_more = true;
1513 }
1514 selection->setReprList((GSList const *) copied);
1515 g_slist_free (copied);
1516 if (temp_clip) g_slist_free (temp_clip);
1517 if (next) dt->setCurrentLayer(next);
1518 sp_document_done(sp_desktop_document (dt), SP_VERB_LAYER_MOVE_TO_PREV,
1519 _("Lower to previous layer"));
1520 } else {
1521 no_more = true;
1522 }
1524 if (no_more) {
1525 dt->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("No more layers below."));
1526 }
1528 g_slist_free ((GSList *) items);
1529 }
1531 bool
1532 selection_contains_original (SPItem *item, Inkscape::Selection *selection)
1533 {
1534 bool contains_original = false;
1536 bool is_use = SP_IS_USE(item);
1537 SPItem *item_use = item;
1538 SPItem *item_use_first = item;
1539 while (is_use && item_use && !contains_original)
1540 {
1541 item_use = sp_use_get_original (SP_USE(item_use));
1542 contains_original |= selection->includes(item_use);
1543 if (item_use == item_use_first)
1544 break;
1545 is_use = SP_IS_USE(item_use);
1546 }
1548 // If it's a tref, check whether the object containing the character
1549 // data is part of the selection
1550 if (!contains_original && SP_IS_TREF(item)) {
1551 contains_original = selection->includes(SP_TREF(item)->getObjectReferredTo());
1552 }
1554 return contains_original;
1555 }
1558 bool
1559 selection_contains_both_clone_and_original (Inkscape::Selection *selection)
1560 {
1561 bool clone_with_original = false;
1562 for (GSList const *l = selection->itemList(); l != NULL; l = l->next) {
1563 SPItem *item = SP_ITEM(l->data);
1564 clone_with_original |= selection_contains_original(item, selection);
1565 if (clone_with_original)
1566 break;
1567 }
1568 return clone_with_original;
1569 }
1572 /** Apply matrix to the selection. \a set_i2d is normally true, which means objects are in the
1573 original transform, synced with their reprs, and need to jump to the new transform in one go. A
1574 value of set_i2d==false is only used by seltrans when it's dragging objects live (not outlines); in
1575 that case, items are already in the new position, but the repr is in the old, and this function
1576 then simply updates the repr from item->transform.
1577 */
1578 void sp_selection_apply_affine(Inkscape::Selection *selection, NR::Matrix const &affine, bool set_i2d)
1579 {
1580 if (selection->isEmpty())
1581 return;
1583 for (GSList const *l = selection->itemList(); l != NULL; l = l->next) {
1584 SPItem *item = SP_ITEM(l->data);
1586 NR::Point old_center(0,0);
1587 if (set_i2d && item->isCenterSet())
1588 old_center = item->getCenter();
1590 #if 0 /* Re-enable this once persistent guides have a graphical indication.
1591 At the time of writing, this is the only place to re-enable. */
1592 sp_item_update_cns(*item, selection->desktop());
1593 #endif
1595 // we're moving both a clone and its original or any ancestor in clone chain?
1596 bool transform_clone_with_original = selection_contains_original(item, selection);
1597 // ...both a text-on-path and its path?
1598 bool transform_textpath_with_path = (SP_IS_TEXT_TEXTPATH(item) && selection->includes( sp_textpath_get_path_item (SP_TEXTPATH(sp_object_first_child(SP_OBJECT(item)))) ));
1599 // ...both a flowtext and its frame?
1600 bool transform_flowtext_with_frame = (SP_IS_FLOWTEXT(item) && selection->includes( SP_FLOWTEXT(item)->get_frame (NULL))); // (only the first frame is checked so far)
1601 // ...both an offset and its source?
1602 bool transform_offset_with_source = (SP_IS_OFFSET(item) && SP_OFFSET (item)->sourceHref) && selection->includes( sp_offset_get_source (SP_OFFSET(item)) );
1604 // If we're moving a connector, we want to detach it
1605 // from shapes that aren't part of the selection, but
1606 // leave it attached if they are
1607 if (cc_item_is_connector(item)) {
1608 SPItem *attItem[2];
1609 SP_PATH(item)->connEndPair.getAttachedItems(attItem);
1611 for (int n = 0; n < 2; ++n) {
1612 if (!selection->includes(attItem[n])) {
1613 sp_conn_end_detach(item, n);
1614 }
1615 }
1616 }
1618 // "clones are unmoved when original is moved" preference
1619 int compensation = prefs_get_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
1620 bool prefs_unmoved = (compensation == SP_CLONE_COMPENSATION_UNMOVED);
1621 bool prefs_parallel = (compensation == SP_CLONE_COMPENSATION_PARALLEL);
1623 // If this is a clone and it's selected along with its original, do not move it; it will feel the
1624 // transform of its original and respond to it itself. Without this, a clone is doubly
1625 // transformed, very unintuitive.
1626 // Same for textpath if we are also doing ANY transform to its path: do not touch textpath,
1627 // letters cannot be squeezed or rotated anyway, they only refill the changed path.
1628 // Same for linked offset if we are also moving its source: do not move it.
1629 if (transform_textpath_with_path || transform_offset_with_source) {
1630 // restore item->transform field from the repr, in case it was changed by seltrans
1631 sp_object_read_attr (SP_OBJECT (item), "transform");
1633 } else if (transform_flowtext_with_frame) {
1634 // apply the inverse of the region's transform to the <use> so that the flow remains
1635 // the same (even though the output itself gets transformed)
1636 for (SPObject *region = item->firstChild() ; region ; region = SP_OBJECT_NEXT(region)) {
1637 if (!SP_IS_FLOWREGION(region) && !SP_IS_FLOWREGIONEXCLUDE(region))
1638 continue;
1639 for (SPObject *use = region->firstChild() ; use ; use = SP_OBJECT_NEXT(use)) {
1640 if (!SP_IS_USE(use)) continue;
1641 sp_item_write_transform(SP_USE(use), SP_OBJECT_REPR(use), item->transform.inverse(), NULL);
1642 }
1643 }
1644 } else if (transform_clone_with_original) {
1645 // We are transforming a clone along with its original. The below matrix juggling is
1646 // necessary to ensure that they transform as a whole, i.e. the clone's induced
1647 // transform and its move compensation are both cancelled out.
1649 // restore item->transform field from the repr, in case it was changed by seltrans
1650 sp_object_read_attr (SP_OBJECT (item), "transform");
1652 // calculate the matrix we need to apply to the clone to cancel its induced transform from its original
1653 NR::Matrix parent_transform = sp_item_i2root_affine(SP_ITEM(SP_OBJECT_PARENT (item)));
1654 NR::Matrix t = parent_transform * matrix_to_desktop (matrix_from_desktop (affine, item), item) * parent_transform.inverse();
1655 NR::Matrix t_inv =parent_transform * matrix_to_desktop (matrix_from_desktop (affine.inverse(), item), item) * parent_transform.inverse();
1656 NR::Matrix result = t_inv * item->transform * t;
1658 if ((prefs_parallel || prefs_unmoved) && affine.is_translation()) {
1659 // we need to cancel out the move compensation, too
1661 // find out the clone move, same as in sp_use_move_compensate
1662 NR::Matrix parent = sp_use_get_parent_transform (SP_USE(item));
1663 NR::Matrix clone_move = parent.inverse() * t * parent;
1665 if (prefs_parallel) {
1666 NR::Matrix move = result * clone_move * t_inv;
1667 sp_item_write_transform(item, SP_OBJECT_REPR(item), move, &move);
1669 } else if (prefs_unmoved) {
1670 //if (SP_IS_USE(sp_use_get_original(SP_USE(item))))
1671 // clone_move = NR::identity();
1672 NR::Matrix move = result * clone_move;
1673 sp_item_write_transform(item, SP_OBJECT_REPR(item), move, &t);
1674 }
1676 } else {
1677 // just apply the result
1678 sp_item_write_transform(item, SP_OBJECT_REPR(item), result, &t);
1679 }
1681 } else {
1682 if (set_i2d) {
1683 sp_item_set_i2d_affine(item, sp_item_i2d_affine(item) * affine);
1684 }
1685 sp_item_write_transform(item, SP_OBJECT_REPR(item), item->transform, NULL);
1686 }
1688 // if we're moving the actual object, not just updating the repr, we can transform the
1689 // center by the same matrix (only necessary for non-translations)
1690 if (set_i2d && item->isCenterSet() && !affine.is_translation()) {
1691 item->setCenter(old_center * affine);
1692 SP_OBJECT(item)->updateRepr();
1693 }
1694 }
1695 }
1697 void sp_selection_remove_transform()
1698 {
1699 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1700 if (desktop == NULL)
1701 return;
1703 Inkscape::Selection *selection = sp_desktop_selection(desktop);
1705 GSList const *l = (GSList *) selection->reprList();
1706 while (l != NULL) {
1707 sp_repr_set_attr((Inkscape::XML::Node*)l->data, "transform", NULL);
1708 l = l->next;
1709 }
1711 sp_document_done(sp_desktop_document(desktop), SP_VERB_OBJECT_FLATTEN,
1712 _("Remove transform"));
1713 }
1715 void
1716 sp_selection_scale_absolute(Inkscape::Selection *selection,
1717 double const x0, double const x1,
1718 double const y0, double const y1)
1719 {
1720 if (selection->isEmpty())
1721 return;
1723 NR::Maybe<NR::Rect> const bbox(selection->bounds());
1724 if ( !bbox || bbox->isEmpty() ) {
1725 return;
1726 }
1728 NR::translate const p2o(-bbox->min());
1730 NR::scale const newSize(x1 - x0,
1731 y1 - y0);
1732 NR::scale const scale( newSize / NR::scale(bbox->dimensions()) );
1733 NR::translate const o2n(x0, y0);
1734 NR::Matrix const final( p2o * scale * o2n );
1736 sp_selection_apply_affine(selection, final);
1737 }
1740 void sp_selection_scale_relative(Inkscape::Selection *selection, NR::Point const &align, NR::scale const &scale)
1741 {
1742 if (selection->isEmpty())
1743 return;
1745 NR::Maybe<NR::Rect> const bbox(selection->bounds());
1747 if ( !bbox || bbox->isEmpty() ) {
1748 return;
1749 }
1751 // FIXME: ARBITRARY LIMIT: don't try to scale above 1 Mpx, it won't display properly and will crash sooner or later anyway
1752 if ( bbox->extent(NR::X) * scale[NR::X] > 1e6 ||
1753 bbox->extent(NR::Y) * scale[NR::Y] > 1e6 )
1754 {
1755 return;
1756 }
1758 NR::translate const n2d(-align);
1759 NR::translate const d2n(align);
1760 NR::Matrix const final( n2d * scale * d2n );
1761 sp_selection_apply_affine(selection, final);
1762 }
1764 void
1765 sp_selection_rotate_relative(Inkscape::Selection *selection, NR::Point const ¢er, gdouble const angle_degrees)
1766 {
1767 NR::translate const d2n(center);
1768 NR::translate const n2d(-center);
1769 NR::rotate const rotate(rotate_degrees(angle_degrees));
1770 NR::Matrix const final( NR::Matrix(n2d) * rotate * d2n );
1771 sp_selection_apply_affine(selection, final);
1772 }
1774 void
1775 sp_selection_skew_relative(Inkscape::Selection *selection, NR::Point const &align, double dx, double dy)
1776 {
1777 NR::translate const d2n(align);
1778 NR::translate const n2d(-align);
1779 NR::Matrix const skew(1, dy,
1780 dx, 1,
1781 0, 0);
1782 NR::Matrix const final( n2d * skew * d2n );
1783 sp_selection_apply_affine(selection, final);
1784 }
1786 void sp_selection_move_relative(Inkscape::Selection *selection, NR::Point const &move)
1787 {
1788 sp_selection_apply_affine(selection, NR::Matrix(NR::translate(move)));
1789 }
1791 void sp_selection_move_relative(Inkscape::Selection *selection, double dx, double dy)
1792 {
1793 sp_selection_apply_affine(selection, NR::Matrix(NR::translate(dx, dy)));
1794 }
1797 /**
1798 * \brief sp_selection_rotate_90
1799 *
1800 * This function rotates selected objects 90 degrees clockwise.
1801 *
1802 */
1804 void sp_selection_rotate_90_cw()
1805 {
1806 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1808 Inkscape::Selection *selection = sp_desktop_selection(desktop);
1810 if (selection->isEmpty())
1811 return;
1813 GSList const *l = selection->itemList();
1814 NR::rotate const rot_neg_90(NR::Point(0, -1));
1815 for (GSList const *l2 = l ; l2 != NULL ; l2 = l2->next) {
1816 SPItem *item = SP_ITEM(l2->data);
1817 sp_item_rotate_rel(item, rot_neg_90);
1818 }
1820 sp_document_done(sp_desktop_document(desktop), SP_VERB_OBJECT_ROTATE_90_CCW,
1821 _("Rotate 90° CW"));
1822 }
1825 /**
1826 * \brief sp_selection_rotate_90_ccw
1827 *
1828 * This function rotates selected objects 90 degrees counter-clockwise.
1829 *
1830 */
1832 void sp_selection_rotate_90_ccw()
1833 {
1834 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1836 Inkscape::Selection *selection = sp_desktop_selection(desktop);
1838 if (selection->isEmpty())
1839 return;
1841 GSList const *l = selection->itemList();
1842 NR::rotate const rot_neg_90(NR::Point(0, 1));
1843 for (GSList const *l2 = l ; l2 != NULL ; l2 = l2->next) {
1844 SPItem *item = SP_ITEM(l2->data);
1845 sp_item_rotate_rel(item, rot_neg_90);
1846 }
1848 sp_document_done(sp_desktop_document(desktop), SP_VERB_OBJECT_ROTATE_90_CW,
1849 _("Rotate 90° CCW"));
1850 }
1852 void
1853 sp_selection_rotate(Inkscape::Selection *selection, gdouble const angle_degrees)
1854 {
1855 if (selection->isEmpty())
1856 return;
1858 NR::Maybe<NR::Point> center = selection->center();
1859 if (!center) {
1860 return;
1861 }
1863 sp_selection_rotate_relative(selection, *center, angle_degrees);
1865 sp_document_maybe_done(sp_desktop_document(selection->desktop()),
1866 ( ( angle_degrees > 0 )
1867 ? "selector:rotate:ccw"
1868 : "selector:rotate:cw" ),
1869 SP_VERB_CONTEXT_SELECT,
1870 _("Rotate"));
1871 }
1873 /**
1874 \param angle the angle in "angular pixels", i.e. how many visible pixels must move the outermost point of the rotated object
1875 */
1876 void
1877 sp_selection_rotate_screen(Inkscape::Selection *selection, gdouble angle)
1878 {
1879 if (selection->isEmpty())
1880 return;
1882 NR::Maybe<NR::Rect> const bbox(selection->bounds());
1883 NR::Maybe<NR::Point> center = selection->center();
1885 if ( !bbox || !center ) {
1886 return;
1887 }
1889 gdouble const zoom = selection->desktop()->current_zoom();
1890 gdouble const zmove = angle / zoom;
1891 gdouble const r = NR::L2(bbox->cornerFarthestFrom(*center) - *center);
1893 gdouble const zangle = 180 * atan2(zmove, r) / M_PI;
1895 sp_selection_rotate_relative(selection, *center, zangle);
1897 sp_document_maybe_done(sp_desktop_document(selection->desktop()),
1898 ( (angle > 0)
1899 ? "selector:rotate:ccw"
1900 : "selector:rotate:cw" ),
1901 SP_VERB_CONTEXT_SELECT,
1902 _("Rotate by pixels"));
1903 }
1905 void
1906 sp_selection_scale(Inkscape::Selection *selection, gdouble grow)
1907 {
1908 if (selection->isEmpty())
1909 return;
1911 NR::Maybe<NR::Rect> const bbox(selection->bounds());
1912 if (!bbox) {
1913 return;
1914 }
1916 NR::Point const center(bbox->midpoint());
1918 // you can't scale "do nizhe pola" (below zero)
1919 double const max_len = bbox->maxExtent();
1920 if ( max_len + grow <= 1e-3 ) {
1921 return;
1922 }
1924 double const times = 1.0 + grow / max_len;
1925 sp_selection_scale_relative(selection, center, NR::scale(times, times));
1927 sp_document_maybe_done(sp_desktop_document(selection->desktop()),
1928 ( (grow > 0)
1929 ? "selector:scale:larger"
1930 : "selector:scale:smaller" ),
1931 SP_VERB_CONTEXT_SELECT,
1932 _("Scale"));
1933 }
1935 void
1936 sp_selection_scale_screen(Inkscape::Selection *selection, gdouble grow_pixels)
1937 {
1938 sp_selection_scale(selection,
1939 grow_pixels / selection->desktop()->current_zoom());
1940 }
1942 void
1943 sp_selection_scale_times(Inkscape::Selection *selection, gdouble times)
1944 {
1945 if (selection->isEmpty())
1946 return;
1948 NR::Maybe<NR::Rect> sel_bbox = selection->bounds();
1950 if (!sel_bbox) {
1951 return;
1952 }
1954 NR::Point const center(sel_bbox->midpoint());
1955 sp_selection_scale_relative(selection, center, NR::scale(times, times));
1956 sp_document_done(sp_desktop_document(selection->desktop()), SP_VERB_CONTEXT_SELECT,
1957 _("Scale by whole factor"));
1958 }
1960 void
1961 sp_selection_move(gdouble dx, gdouble dy)
1962 {
1963 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1964 Inkscape::Selection *selection = sp_desktop_selection(desktop);
1965 if (selection->isEmpty()) {
1966 return;
1967 }
1969 sp_selection_move_relative(selection, dx, dy);
1971 if (dx == 0) {
1972 sp_document_maybe_done(sp_desktop_document(desktop), "selector:move:vertical", SP_VERB_CONTEXT_SELECT,
1973 _("Move vertically"));
1974 } else if (dy == 0) {
1975 sp_document_maybe_done(sp_desktop_document(desktop), "selector:move:horizontal", SP_VERB_CONTEXT_SELECT,
1976 _("Move horizontally"));
1977 } else {
1978 sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_SELECT,
1979 _("Move"));
1980 }
1981 }
1983 void
1984 sp_selection_move_screen(gdouble dx, gdouble dy)
1985 {
1986 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1988 Inkscape::Selection *selection = sp_desktop_selection(desktop);
1989 if (selection->isEmpty()) {
1990 return;
1991 }
1993 // same as sp_selection_move but divide deltas by zoom factor
1994 gdouble const zoom = desktop->current_zoom();
1995 gdouble const zdx = dx / zoom;
1996 gdouble const zdy = dy / zoom;
1997 sp_selection_move_relative(selection, zdx, zdy);
1999 if (dx == 0) {
2000 sp_document_maybe_done(sp_desktop_document(desktop), "selector:move:vertical", SP_VERB_CONTEXT_SELECT,
2001 _("Move vertically by pixels"));
2002 } else if (dy == 0) {
2003 sp_document_maybe_done(sp_desktop_document(desktop), "selector:move:horizontal", SP_VERB_CONTEXT_SELECT,
2004 _("Move horizontally by pixels"));
2005 } else {
2006 sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_SELECT,
2007 _("Move"));
2008 }
2009 }
2011 namespace {
2013 template <typename D>
2014 SPItem *next_item(SPDesktop *desktop, GSList *path, SPObject *root,
2015 bool only_in_viewport, PrefsSelectionContext inlayer, bool onlyvisible, bool onlysensitive);
2017 template <typename D>
2018 SPItem *next_item_from_list(SPDesktop *desktop, GSList const *items, SPObject *root,
2019 bool only_in_viewport, PrefsSelectionContext inlayer, bool onlyvisible, bool onlysensitive);
2021 struct Forward {
2022 typedef SPObject *Iterator;
2024 static Iterator children(SPObject *o) { return sp_object_first_child(o); }
2025 static Iterator siblings_after(SPObject *o) { return SP_OBJECT_NEXT(o); }
2026 static void dispose(Iterator /*i*/) {}
2028 static SPObject *object(Iterator i) { return i; }
2029 static Iterator next(Iterator i) { return SP_OBJECT_NEXT(i); }
2030 };
2032 struct Reverse {
2033 typedef GSList *Iterator;
2035 static Iterator children(SPObject *o) {
2036 return make_list(o->firstChild(), NULL);
2037 }
2038 static Iterator siblings_after(SPObject *o) {
2039 return make_list(SP_OBJECT_PARENT(o)->firstChild(), o);
2040 }
2041 static void dispose(Iterator i) {
2042 g_slist_free(i);
2043 }
2045 static SPObject *object(Iterator i) {
2046 return reinterpret_cast<SPObject *>(i->data);
2047 }
2048 static Iterator next(Iterator i) { return i->next; }
2050 private:
2051 static GSList *make_list(SPObject *object, SPObject *limit) {
2052 GSList *list=NULL;
2053 while ( object != limit ) {
2054 list = g_slist_prepend(list, object);
2055 object = SP_OBJECT_NEXT(object);
2056 }
2057 return list;
2058 }
2059 };
2061 }
2063 void
2064 sp_selection_item_next(void)
2065 {
2066 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
2067 g_return_if_fail(desktop != NULL);
2068 Inkscape::Selection *selection = sp_desktop_selection(desktop);
2070 PrefsSelectionContext inlayer = (PrefsSelectionContext)prefs_get_int_attribute ("options.kbselection", "inlayer", PREFS_SELECTION_LAYER);
2071 bool onlyvisible = prefs_get_int_attribute ("options.kbselection", "onlyvisible", 1);
2072 bool onlysensitive = prefs_get_int_attribute ("options.kbselection", "onlysensitive", 1);
2074 SPObject *root;
2075 if (PREFS_SELECTION_ALL != inlayer) {
2076 root = selection->activeContext();
2077 } else {
2078 root = desktop->currentRoot();
2079 }
2081 SPItem *item=next_item_from_list<Forward>(desktop, selection->itemList(), root, SP_CYCLING == SP_CYCLE_VISIBLE, inlayer, onlyvisible, onlysensitive);
2083 if (item) {
2084 selection->set(item, PREFS_SELECTION_LAYER_RECURSIVE == inlayer);
2085 if ( SP_CYCLING == SP_CYCLE_FOCUS ) {
2086 scroll_to_show_item(desktop, item);
2087 }
2088 }
2089 }
2091 void
2092 sp_selection_item_prev(void)
2093 {
2094 SPDocument *document = SP_ACTIVE_DOCUMENT;
2095 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
2096 g_return_if_fail(document != NULL);
2097 g_return_if_fail(desktop != NULL);
2098 Inkscape::Selection *selection = sp_desktop_selection(desktop);
2100 PrefsSelectionContext inlayer = (PrefsSelectionContext)prefs_get_int_attribute ("options.kbselection", "inlayer", PREFS_SELECTION_LAYER);
2101 bool onlyvisible = prefs_get_int_attribute ("options.kbselection", "onlyvisible", 1);
2102 bool onlysensitive = prefs_get_int_attribute ("options.kbselection", "onlysensitive", 1);
2104 SPObject *root;
2105 if (PREFS_SELECTION_ALL != inlayer) {
2106 root = selection->activeContext();
2107 } else {
2108 root = desktop->currentRoot();
2109 }
2111 SPItem *item=next_item_from_list<Reverse>(desktop, selection->itemList(), root, SP_CYCLING == SP_CYCLE_VISIBLE, inlayer, onlyvisible, onlysensitive);
2113 if (item) {
2114 selection->set(item, PREFS_SELECTION_LAYER_RECURSIVE == inlayer);
2115 if ( SP_CYCLING == SP_CYCLE_FOCUS ) {
2116 scroll_to_show_item(desktop, item);
2117 }
2118 }
2119 }
2121 void sp_selection_next_patheffect_param(SPDesktop * dt)
2122 {
2123 if (!dt) return;
2125 Inkscape::Selection *selection = sp_desktop_selection(dt);
2126 if ( selection && !selection->isEmpty() ) {
2127 SPItem *item = selection->singleItem();
2128 if ( item && SP_IS_SHAPE(item)) {
2129 SPShape *shape = SP_SHAPE(item);
2130 if (sp_shape_has_path_effect(shape)) {
2131 sp_shape_edit_next_param_oncanvas(shape, dt);
2132 } else {
2133 dt->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("The selection has no applied path effect."));
2134 }
2135 }
2136 }
2137 }
2139 void sp_selection_edit_clip_or_mask(SPDesktop * dt, bool clip)
2140 {
2141 if (!dt) return;
2143 Inkscape::Selection *selection = sp_desktop_selection(dt);
2144 if ( selection && !selection->isEmpty() ) {
2145 SPItem *item = selection->singleItem();
2146 if ( item ) {
2147 SPObject *obj = NULL;
2148 if (clip)
2149 obj = item->clip_ref ? SP_OBJECT(item->clip_ref->getObject()) : NULL;
2150 else
2151 obj = item->mask_ref ? SP_OBJECT(item->mask_ref->getObject()) : NULL;
2153 if (obj) {
2154 // obj is a group object, the children are the actual clippers
2155 for ( SPObject *child = obj->children ; child ; child = child->next ) {
2156 if ( SP_IS_ITEM(child) ) {
2157 // If not already in nodecontext, goto it!
2158 if (!tools_isactive(dt, TOOLS_NODES)) {
2159 tools_switch_current(TOOLS_NODES);
2160 }
2162 ShapeEditor * shape_editor = SP_NODE_CONTEXT( dt->event_context )->shape_editor;
2163 shape_editor->set_item(SP_ITEM(child));
2164 Inkscape::NodePath::Path *np = shape_editor->get_nodepath();
2165 if (np) {
2166 np->helperpath_rgba = clip ? 0x0000ffff : 0x800080ff;
2167 np->helperpath_width = 1.0;
2168 sp_nodepath_show_helperpath(np, true);
2169 }
2170 break; // break out of for loop after 1st encountered item
2171 }
2172 }
2173 } else if (clip) {
2174 dt->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("The selection has no applied clip path."));
2175 } else {
2176 dt->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("The selection has no applied mask."));
2177 }
2178 }
2179 }
2180 }
2183 namespace {
2185 template <typename D>
2186 SPItem *next_item_from_list(SPDesktop *desktop, GSList const *items,
2187 SPObject *root, bool only_in_viewport, PrefsSelectionContext inlayer, bool onlyvisible, bool onlysensitive)
2188 {
2189 SPObject *current=root;
2190 while (items) {
2191 SPItem *item=SP_ITEM(items->data);
2192 if ( root->isAncestorOf(item) &&
2193 ( !only_in_viewport || desktop->isWithinViewport(item) ) )
2194 {
2195 current = item;
2196 break;
2197 }
2198 items = items->next;
2199 }
2201 GSList *path=NULL;
2202 while ( current != root ) {
2203 path = g_slist_prepend(path, current);
2204 current = SP_OBJECT_PARENT(current);
2205 }
2207 SPItem *next;
2208 // first, try from the current object
2209 next = next_item<D>(desktop, path, root, only_in_viewport, inlayer, onlyvisible, onlysensitive);
2210 g_slist_free(path);
2212 if (!next) { // if we ran out of objects, start over at the root
2213 next = next_item<D>(desktop, NULL, root, only_in_viewport, inlayer, onlyvisible, onlysensitive);
2214 }
2216 return next;
2217 }
2219 template <typename D>
2220 SPItem *next_item(SPDesktop *desktop, GSList *path, SPObject *root,
2221 bool only_in_viewport, PrefsSelectionContext inlayer, bool onlyvisible, bool onlysensitive)
2222 {
2223 typename D::Iterator children;
2224 typename D::Iterator iter;
2226 SPItem *found=NULL;
2228 if (path) {
2229 SPObject *object=reinterpret_cast<SPObject *>(path->data);
2230 g_assert(SP_OBJECT_PARENT(object) == root);
2231 if (desktop->isLayer(object)) {
2232 found = next_item<D>(desktop, path->next, object, only_in_viewport, inlayer, onlyvisible, onlysensitive);
2233 }
2234 iter = children = D::siblings_after(object);
2235 } else {
2236 iter = children = D::children(root);
2237 }
2239 while ( iter && !found ) {
2240 SPObject *object=D::object(iter);
2241 if (desktop->isLayer(object)) {
2242 if (PREFS_SELECTION_LAYER != inlayer) { // recurse into sublayers
2243 found = next_item<D>(desktop, NULL, object, only_in_viewport, inlayer, onlyvisible, onlysensitive);
2244 }
2245 } else if ( SP_IS_ITEM(object) &&
2246 ( !only_in_viewport || desktop->isWithinViewport(SP_ITEM(object)) ) &&
2247 ( !onlyvisible || !desktop->itemIsHidden(SP_ITEM(object))) &&
2248 ( !onlysensitive || !SP_ITEM(object)->isLocked()) &&
2249 !desktop->isLayer(SP_ITEM(object)) )
2250 {
2251 found = SP_ITEM(object);
2252 }
2253 iter = D::next(iter);
2254 }
2256 D::dispose(children);
2258 return found;
2259 }
2261 }
2263 /**
2264 * If \a item is not entirely visible then adjust visible area to centre on the centre on of
2265 * \a item.
2266 */
2267 void scroll_to_show_item(SPDesktop *desktop, SPItem *item)
2268 {
2269 NR::Rect dbox = desktop->get_display_area();
2270 NR::Maybe<NR::Rect> sbox = sp_item_bbox_desktop(item);
2272 if ( sbox && dbox.contains(*sbox) == false ) {
2273 NR::Point const s_dt = sbox->midpoint();
2274 NR::Point const s_w = desktop->d2w(s_dt);
2275 NR::Point const d_dt = dbox.midpoint();
2276 NR::Point const d_w = desktop->d2w(d_dt);
2277 NR::Point const moved_w( d_w - s_w );
2278 gint const dx = (gint) moved_w[X];
2279 gint const dy = (gint) moved_w[Y];
2280 desktop->scroll_world(dx, dy);
2281 }
2282 }
2285 void
2286 sp_selection_clone()
2287 {
2288 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
2289 if (desktop == NULL)
2290 return;
2292 Inkscape::Selection *selection = sp_desktop_selection(desktop);
2294 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(desktop->doc());
2296 // check if something is selected
2297 if (selection->isEmpty()) {
2298 desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select an <b>object</b> to clone."));
2299 return;
2300 }
2302 GSList *reprs = g_slist_copy((GSList *) selection->reprList());
2304 selection->clear();
2306 // sorting items from different parents sorts each parent's subset without possibly mixing them, just what we need
2307 reprs = g_slist_sort(reprs, (GCompareFunc) sp_repr_compare_position);
2309 GSList *newsel = NULL;
2311 while (reprs) {
2312 Inkscape::XML::Node *sel_repr = (Inkscape::XML::Node *) reprs->data;
2313 Inkscape::XML::Node *parent = sp_repr_parent(sel_repr);
2315 Inkscape::XML::Node *clone = xml_doc->createElement("svg:use");
2316 sp_repr_set_attr(clone, "x", "0");
2317 sp_repr_set_attr(clone, "y", "0");
2318 sp_repr_set_attr(clone, "xlink:href", g_strdup_printf("#%s", sel_repr->attribute("id")));
2320 sp_repr_set_attr(clone, "inkscape:transform-center-x", sel_repr->attribute("inkscape:transform-center-x"));
2321 sp_repr_set_attr(clone, "inkscape:transform-center-y", sel_repr->attribute("inkscape:transform-center-y"));
2323 // add the new clone to the top of the original's parent
2324 parent->appendChild(clone);
2326 newsel = g_slist_prepend(newsel, clone);
2327 reprs = g_slist_remove(reprs, sel_repr);
2328 Inkscape::GC::release(clone);
2329 }
2331 // TRANSLATORS: only translate "string" in "context|string".
2332 // For more details, see http://developer.gnome.org/doc/API/2.0/glib/glib-I18N.html#Q-:CAPS
2333 sp_document_done(sp_desktop_document(desktop), SP_VERB_EDIT_CLONE,
2334 Q_("action|Clone"));
2336 selection->setReprList(newsel);
2338 g_slist_free(newsel);
2339 }
2341 void
2342 sp_selection_unlink()
2343 {
2344 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
2345 if (!desktop)
2346 return;
2348 Inkscape::Selection *selection = sp_desktop_selection(desktop);
2350 if (selection->isEmpty()) {
2351 desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select a <b>clone</b> to unlink."));
2352 return;
2353 }
2355 // Get a copy of current selection.
2356 GSList *new_select = NULL;
2357 bool unlinked = false;
2358 for (GSList *items = g_slist_copy((GSList *) selection->itemList());
2359 items != NULL;
2360 items = items->next)
2361 {
2362 SPItem *item = (SPItem *) items->data;
2364 if (SP_IS_TEXT(item)) {
2365 SPObject *tspan = sp_tref_convert_to_tspan(SP_OBJECT(item));
2367 if (tspan) {
2368 SP_OBJECT(item)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
2369 }
2371 // Set unlink to true, and fall into the next if which
2372 // will include this text item in the new selection
2373 unlinked = true;
2374 }
2376 if (!(SP_IS_USE(item) || SP_IS_TREF(item))) {
2377 // keep the non-use item in the new selection
2378 new_select = g_slist_prepend(new_select, item);
2379 continue;
2380 }
2382 SPItem *unlink;
2383 if (SP_IS_USE(item)) {
2384 unlink = sp_use_unlink(SP_USE(item));
2385 } else /*if (SP_IS_TREF(use))*/ {
2386 unlink = SP_ITEM(sp_tref_convert_to_tspan(SP_OBJECT(item)));
2387 }
2389 unlinked = true;
2390 // Add ungrouped items to the new selection.
2391 new_select = g_slist_prepend(new_select, unlink);
2392 }
2394 if (new_select) { // set new selection
2395 selection->clear();
2396 selection->setList(new_select);
2397 g_slist_free(new_select);
2398 }
2399 if (!unlinked) {
2400 desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("<b>No clones to unlink</b> in the selection."));
2401 }
2403 sp_document_done(sp_desktop_document(desktop), SP_VERB_EDIT_UNLINK_CLONE,
2404 _("Unlink clone"));
2405 }
2407 void
2408 sp_select_clone_original()
2409 {
2410 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
2411 if (desktop == NULL)
2412 return;
2414 Inkscape::Selection *selection = sp_desktop_selection(desktop);
2416 SPItem *item = selection->singleItem();
2418 gchar const *error = _("Select a <b>clone</b> to go to its original. Select a <b>linked offset</b> to go to its source. Select a <b>text on path</b> to go to the path. Select a <b>flowed text</b> to go to its frame.");
2420 // Check if other than two objects are selected
2421 if (g_slist_length((GSList *) selection->itemList()) != 1 || !item) {
2422 desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, error);
2423 return;
2424 }
2426 SPItem *original = NULL;
2427 if (SP_IS_USE(item)) {
2428 original = sp_use_get_original (SP_USE(item));
2429 } else if (SP_IS_OFFSET(item) && SP_OFFSET (item)->sourceHref) {
2430 original = sp_offset_get_source (SP_OFFSET(item));
2431 } else if (SP_IS_TEXT_TEXTPATH(item)) {
2432 original = sp_textpath_get_path_item (SP_TEXTPATH(sp_object_first_child(SP_OBJECT(item))));
2433 } else if (SP_IS_FLOWTEXT(item)) {
2434 original = SP_FLOWTEXT(item)->get_frame (NULL); // first frame only
2435 } else { // it's an object that we don't know what to do with
2436 desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, error);
2437 return;
2438 }
2440 if (!original) {
2441 desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("<b>Cannot find</b> the object to select (orphaned clone, offset, textpath, flowed text?)"));
2442 return;
2443 }
2445 for (SPObject *o = original; o && !SP_IS_ROOT(o); o = SP_OBJECT_PARENT (o)) {
2446 if (SP_IS_DEFS (o)) {
2447 desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("The object you're trying to select is <b>not visible</b> (it is in <defs>)"));
2448 return;
2449 }
2450 }
2452 if (original) {
2453 selection->clear();
2454 selection->set(original);
2455 if (SP_CYCLING == SP_CYCLE_FOCUS) {
2456 scroll_to_show_item(desktop, original);
2457 }
2458 }
2459 }
2462 void sp_selection_to_marker(bool apply)
2463 {
2464 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
2465 if (desktop == NULL)
2466 return;
2468 SPDocument *doc = sp_desktop_document(desktop);
2469 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(doc);
2471 Inkscape::Selection *selection = sp_desktop_selection(desktop);
2473 // check if something is selected
2474 if (selection->isEmpty()) {
2475 desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to convert to marker."));
2476 return;
2477 }
2479 sp_document_ensure_up_to_date(doc);
2480 NR::Maybe<NR::Rect> r = selection->bounds();
2481 NR::Maybe<NR::Point> c = selection->center();
2482 if ( !r || !c || r->isEmpty() ) {
2483 return;
2484 }
2486 // calculate the transform to be applied to objects to move them to 0,0
2487 NR::Point move_p = NR::Point(0, sp_document_height(doc)) - *c;
2488 move_p[NR::Y] = -move_p[NR::Y];
2489 NR::Matrix move = NR::Matrix (NR::translate (move_p));
2491 GSList *items = g_slist_copy((GSList *) selection->itemList());
2493 items = g_slist_sort (items, (GCompareFunc) sp_object_compare_position);
2495 // bottommost object, after sorting
2496 SPObject *parent = SP_OBJECT_PARENT (items->data);
2498 NR::Matrix parent_transform = sp_item_i2root_affine(SP_ITEM(parent));
2500 // remember the position of the first item
2501 gint pos = SP_OBJECT_REPR (items->data)->position();
2502 (void)pos; // TODO check why this was remembered
2504 // create a list of duplicates
2505 GSList *repr_copies = NULL;
2506 for (GSList *i = items; i != NULL; i = i->next) {
2507 Inkscape::XML::Node *dup = (SP_OBJECT_REPR (i->data))->duplicate(xml_doc);
2508 repr_copies = g_slist_prepend (repr_copies, dup);
2509 }
2511 NR::Rect bounds(desktop->dt2doc(r->min()), desktop->dt2doc(r->max()));
2513 if (apply) {
2514 // delete objects so that their clones don't get alerted; this object will be restored shortly
2515 for (GSList *i = items; i != NULL; i = i->next) {
2516 SPObject *item = SP_OBJECT (i->data);
2517 item->deleteObject (false);
2518 }
2519 }
2521 // Hack: Temporarily set clone compensation to unmoved, so that we can move clone-originals
2522 // without disturbing clones.
2523 // See ActorAlign::on_button_click() in src/ui/dialog/align-and-distribute.cpp
2524 int saved_compensation = prefs_get_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
2525 prefs_set_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
2527 gchar const *mark_id = generate_marker(repr_copies, bounds, doc,
2528 ( NR::Matrix(NR::translate(desktop->dt2doc(NR::Point(r->min()[NR::X],
2529 r->max()[NR::Y]))))
2530 * parent_transform.inverse() ),
2531 parent_transform * move);
2532 (void)mark_id;
2534 // restore compensation setting
2535 prefs_set_int_attribute("options.clonecompensation", "value", saved_compensation);
2538 g_slist_free (items);
2540 sp_document_done (doc, SP_VERB_EDIT_SELECTION_2_MARKER,
2541 _("Objects to marker"));
2542 }
2544 static void sp_selection_to_guides_recursive(SPItem *item, bool deleteitem) {
2545 if (SP_IS_GROUP(item) && !SP_IS_BOX3D(item)) {
2546 for (GSList *i = sp_item_group_item_list (SP_GROUP(item)); i != NULL; i = i->next) {
2547 sp_selection_to_guides_recursive(SP_ITEM(i->data), deleteitem);
2548 }
2549 } else {
2550 sp_item_convert_item_to_guides(item);
2552 if (deleteitem) {
2553 SP_OBJECT(item)->deleteObject(true);
2554 }
2555 }
2556 }
2558 void sp_selection_to_guides()
2559 {
2560 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
2561 if (desktop == NULL)
2562 return;
2564 SPDocument *doc = sp_desktop_document(desktop);
2565 Inkscape::Selection *selection = sp_desktop_selection(desktop);
2566 // we need to copy the list because it gets reset when objects are deleted
2567 GSList *items = g_slist_copy((GSList *) selection->itemList());
2569 if (!items) {
2570 desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to convert to guides."));
2571 return;
2572 }
2574 bool deleteitem = (prefs_get_int_attribute("tools", "cvg_keep_objects", 0) == 0);
2576 for (GSList const *i = items; i != NULL; i = i->next) {
2577 sp_selection_to_guides_recursive(SP_ITEM(i->data), deleteitem);
2578 }
2580 sp_document_done (doc, SP_VERB_EDIT_SELECTION_2_GUIDES, _("Objects to guides"));
2581 }
2583 void
2584 sp_selection_tile(bool apply)
2585 {
2586 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
2587 if (desktop == NULL)
2588 return;
2590 SPDocument *doc = sp_desktop_document(desktop);
2591 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(doc);
2593 Inkscape::Selection *selection = sp_desktop_selection(desktop);
2595 // check if something is selected
2596 if (selection->isEmpty()) {
2597 desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to convert to pattern."));
2598 return;
2599 }
2601 sp_document_ensure_up_to_date(doc);
2602 NR::Maybe<NR::Rect> r = selection->bounds();
2603 if ( !r || r->isEmpty() ) {
2604 return;
2605 }
2607 // calculate the transform to be applied to objects to move them to 0,0
2608 NR::Point move_p = NR::Point(0, sp_document_height(doc)) - (r->min() + NR::Point (0, r->extent(NR::Y)));
2609 move_p[NR::Y] = -move_p[NR::Y];
2610 NR::Matrix move = NR::Matrix (NR::translate (move_p));
2612 GSList *items = g_slist_copy((GSList *) selection->itemList());
2614 items = g_slist_sort (items, (GCompareFunc) sp_object_compare_position);
2616 // bottommost object, after sorting
2617 SPObject *parent = SP_OBJECT_PARENT (items->data);
2619 NR::Matrix parent_transform = sp_item_i2root_affine(SP_ITEM(parent));
2621 // remember the position of the first item
2622 gint pos = SP_OBJECT_REPR (items->data)->position();
2624 // create a list of duplicates
2625 GSList *repr_copies = NULL;
2626 for (GSList *i = items; i != NULL; i = i->next) {
2627 Inkscape::XML::Node *dup = (SP_OBJECT_REPR (i->data))->duplicate(xml_doc);
2628 repr_copies = g_slist_prepend (repr_copies, dup);
2629 }
2631 NR::Rect bounds(desktop->dt2doc(r->min()), desktop->dt2doc(r->max()));
2633 if (apply) {
2634 // delete objects so that their clones don't get alerted; this object will be restored shortly
2635 for (GSList *i = items; i != NULL; i = i->next) {
2636 SPObject *item = SP_OBJECT (i->data);
2637 item->deleteObject (false);
2638 }
2639 }
2641 // Hack: Temporarily set clone compensation to unmoved, so that we can move clone-originals
2642 // without disturbing clones.
2643 // See ActorAlign::on_button_click() in src/ui/dialog/align-and-distribute.cpp
2644 int saved_compensation = prefs_get_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
2645 prefs_set_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
2647 gchar const *pat_id = pattern_tile(repr_copies, bounds, doc,
2648 ( NR::Matrix(NR::translate(desktop->dt2doc(NR::Point(r->min()[NR::X],
2649 r->max()[NR::Y]))))
2650 * parent_transform.inverse() ),
2651 parent_transform * move);
2653 // restore compensation setting
2654 prefs_set_int_attribute("options.clonecompensation", "value", saved_compensation);
2656 if (apply) {
2657 Inkscape::XML::Node *rect = xml_doc->createElement("svg:rect");
2658 rect->setAttribute("style", g_strdup_printf("stroke:none;fill:url(#%s)", pat_id));
2660 NR::Point min = bounds.min() * parent_transform.inverse();
2661 NR::Point max = bounds.max() * parent_transform.inverse();
2663 sp_repr_set_svg_double(rect, "width", max[NR::X] - min[NR::X]);
2664 sp_repr_set_svg_double(rect, "height", max[NR::Y] - min[NR::Y]);
2665 sp_repr_set_svg_double(rect, "x", min[NR::X]);
2666 sp_repr_set_svg_double(rect, "y", min[NR::Y]);
2668 // restore parent and position
2669 SP_OBJECT_REPR (parent)->appendChild(rect);
2670 rect->setPosition(pos > 0 ? pos : 0);
2671 SPItem *rectangle = (SPItem *) sp_desktop_document (desktop)->getObjectByRepr(rect);
2673 Inkscape::GC::release(rect);
2675 selection->clear();
2676 selection->set(rectangle);
2677 }
2679 g_slist_free (items);
2681 sp_document_done (doc, SP_VERB_EDIT_TILE,
2682 _("Objects to pattern"));
2683 }
2685 void
2686 sp_selection_untile()
2687 {
2688 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
2689 if (desktop == NULL)
2690 return;
2692 SPDocument *doc = sp_desktop_document(desktop);
2693 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(doc);
2695 Inkscape::Selection *selection = sp_desktop_selection(desktop);
2697 // check if something is selected
2698 if (selection->isEmpty()) {
2699 desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select an <b>object with pattern fill</b> to extract objects from."));
2700 return;
2701 }
2703 GSList *new_select = NULL;
2705 bool did = false;
2707 for (GSList *items = g_slist_copy((GSList *) selection->itemList());
2708 items != NULL;
2709 items = items->next) {
2711 SPItem *item = (SPItem *) items->data;
2713 SPStyle *style = SP_OBJECT_STYLE (item);
2715 if (!style || !style->fill.isPaintserver())
2716 continue;
2718 SPObject *server = SP_OBJECT_STYLE_FILL_SERVER(item);
2720 if (!SP_IS_PATTERN(server))
2721 continue;
2723 did = true;
2725 SPPattern *pattern = pattern_getroot (SP_PATTERN (server));
2727 NR::Matrix pat_transform = pattern_patternTransform (SP_PATTERN (server));
2728 pat_transform *= item->transform;
2730 for (SPObject *child = sp_object_first_child(SP_OBJECT(pattern)) ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
2731 Inkscape::XML::Node *copy = SP_OBJECT_REPR(child)->duplicate(xml_doc);
2732 SPItem *i = SP_ITEM (desktop->currentLayer()->appendChildRepr(copy));
2734 // FIXME: relink clones to the new canvas objects
2735 // use SPObject::setid when mental finishes it to steal ids of
2737 // this is needed to make sure the new item has curve (simply requestDisplayUpdate does not work)
2738 sp_document_ensure_up_to_date (doc);
2740 NR::Matrix transform( i->transform * pat_transform );
2741 sp_item_write_transform(i, SP_OBJECT_REPR(i), transform);
2743 new_select = g_slist_prepend(new_select, i);
2744 }
2746 SPCSSAttr *css = sp_repr_css_attr_new ();
2747 sp_repr_css_set_property (css, "fill", "none");
2748 sp_repr_css_change (SP_OBJECT_REPR (item), css, "style");
2749 }
2751 if (!did) {
2752 desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("<b>No pattern fills</b> in the selection."));
2753 } else {
2754 sp_document_done(sp_desktop_document(desktop), SP_VERB_EDIT_UNTILE,
2755 _("Pattern to objects"));
2756 selection->setList(new_select);
2757 }
2758 }
2760 void
2761 sp_selection_get_export_hints (Inkscape::Selection *selection, char const **filename, float *xdpi, float *ydpi)
2762 {
2763 if (selection->isEmpty()) {
2764 return;
2765 }
2767 GSList const *reprlst = selection->reprList();
2768 bool filename_search = TRUE;
2769 bool xdpi_search = TRUE;
2770 bool ydpi_search = TRUE;
2772 for(; reprlst != NULL &&
2773 filename_search &&
2774 xdpi_search &&
2775 ydpi_search;
2776 reprlst = reprlst->next) {
2777 gchar const *dpi_string;
2778 Inkscape::XML::Node * repr = (Inkscape::XML::Node *)reprlst->data;
2780 if (filename_search) {
2781 *filename = repr->attribute("inkscape:export-filename");
2782 if (*filename != NULL)
2783 filename_search = FALSE;
2784 }
2786 if (xdpi_search) {
2787 dpi_string = NULL;
2788 dpi_string = repr->attribute("inkscape:export-xdpi");
2789 if (dpi_string != NULL) {
2790 *xdpi = atof(dpi_string);
2791 xdpi_search = FALSE;
2792 }
2793 }
2795 if (ydpi_search) {
2796 dpi_string = NULL;
2797 dpi_string = repr->attribute("inkscape:export-ydpi");
2798 if (dpi_string != NULL) {
2799 *ydpi = atof(dpi_string);
2800 ydpi_search = FALSE;
2801 }
2802 }
2803 }
2804 }
2806 void
2807 sp_document_get_export_hints (SPDocument *doc, char const **filename, float *xdpi, float *ydpi)
2808 {
2809 Inkscape::XML::Node * repr = sp_document_repr_root(doc);
2810 gchar const *dpi_string;
2812 *filename = repr->attribute("inkscape:export-filename");
2814 dpi_string = NULL;
2815 dpi_string = repr->attribute("inkscape:export-xdpi");
2816 if (dpi_string != NULL) {
2817 *xdpi = atof(dpi_string);
2818 }
2820 dpi_string = NULL;
2821 dpi_string = repr->attribute("inkscape:export-ydpi");
2822 if (dpi_string != NULL) {
2823 *ydpi = atof(dpi_string);
2824 }
2825 }
2827 void
2828 sp_selection_create_bitmap_copy ()
2829 {
2830 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
2831 if (desktop == NULL)
2832 return;
2834 SPDocument *document = sp_desktop_document(desktop);
2835 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(document);
2837 Inkscape::Selection *selection = sp_desktop_selection(desktop);
2839 // check if something is selected
2840 if (selection->isEmpty()) {
2841 desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to make a bitmap copy."));
2842 return;
2843 }
2845 // Get the bounding box of the selection
2846 NRRect bbox;
2847 sp_document_ensure_up_to_date (document);
2848 selection->bounds(&bbox);
2849 if (NR_RECT_DFLS_TEST_EMPTY(&bbox)) {
2850 return; // exceptional situation, so not bother with a translatable error message, just quit quietly
2851 }
2853 // List of the items to show; all others will be hidden
2854 GSList *items = g_slist_copy ((GSList *) selection->itemList());
2856 // Sort items so that the topmost comes last
2857 items = g_slist_sort(items, (GCompareFunc) sp_item_repr_compare_position);
2859 // Generate a random value from the current time (you may create bitmap from the same object(s)
2860 // multiple times, and this is done so that they don't clash)
2861 GTimeVal cu;
2862 g_get_current_time (&cu);
2863 guint current = (int) (cu.tv_sec * 1000000 + cu.tv_usec) % 1024;
2865 // Create the filename
2866 gchar *filename = g_strdup_printf ("%s-%s-%u.png", document->name, SP_OBJECT_REPR(items->data)->attribute("id"), current);
2867 // Imagemagick is known not to handle spaces in filenames, so we replace anything but letters,
2868 // digits, and a few other chars, with "_"
2869 filename = g_strcanon (filename, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.=+~$#@^&!?", '_');
2870 // Build the complete path by adding document->base if set
2871 gchar *filepath = g_build_filename (document->base?document->base:"", filename, NULL);
2873 //g_print ("%s\n", filepath);
2875 // Remember parent and z-order of the topmost one
2876 gint pos = SP_OBJECT_REPR(g_slist_last(items)->data)->position();
2877 SPObject *parent_object = SP_OBJECT_PARENT(g_slist_last(items)->data);
2878 Inkscape::XML::Node *parent = SP_OBJECT_REPR(parent_object);
2880 // Calculate resolution
2881 double res;
2882 int const prefs_res = prefs_get_int_attribute ("options.createbitmap", "resolution", 0);
2883 int const prefs_min = prefs_get_int_attribute ("options.createbitmap", "minsize", 0);
2884 if (0 < prefs_res) {
2885 // If it's given explicitly in prefs, take it
2886 res = prefs_res;
2887 } else if (0 < prefs_min) {
2888 // If minsize is given, look up minimum bitmap size (default 250 pixels) and calculate resolution from it
2889 res = PX_PER_IN * prefs_min / MIN ((bbox.x1 - bbox.x0), (bbox.y1 - bbox.y0));
2890 } else {
2891 float hint_xdpi = 0, hint_ydpi = 0;
2892 char const *hint_filename;
2893 // take resolution hint from the selected objects
2894 sp_selection_get_export_hints (selection, &hint_filename, &hint_xdpi, &hint_ydpi);
2895 if (hint_xdpi != 0) {
2896 res = hint_xdpi;
2897 } else {
2898 // take resolution hint from the document
2899 sp_document_get_export_hints (document, &hint_filename, &hint_xdpi, &hint_ydpi);
2900 if (hint_xdpi != 0) {
2901 res = hint_xdpi;
2902 } else {
2903 // if all else fails, take the default 90 dpi
2904 res = PX_PER_IN;
2905 }
2906 }
2907 }
2909 // The width and height of the bitmap in pixels
2910 unsigned width = (unsigned) floor ((bbox.x1 - bbox.x0) * res / PX_PER_IN);
2911 unsigned height =(unsigned) floor ((bbox.y1 - bbox.y0) * res / PX_PER_IN);
2913 // Find out if we have to run a filter
2914 gchar const *run = NULL;
2915 gchar const *filter = prefs_get_string_attribute ("options.createbitmap", "filter");
2916 if (filter) {
2917 // filter command is given;
2918 // see if we have a parameter to pass to it
2919 gchar const *param1 = prefs_get_string_attribute ("options.createbitmap", "filter_param1");
2920 if (param1) {
2921 if (param1[strlen(param1) - 1] == '%') {
2922 // if the param string ends with %, interpret it as a percentage of the image's max dimension
2923 gchar p1[256];
2924 g_ascii_dtostr (p1, 256, ceil (g_ascii_strtod (param1, NULL) * MAX(width, height) / 100));
2925 // the first param is always the image filename, the second is param1
2926 run = g_strdup_printf ("%s \"%s\" %s", filter, filepath, p1);
2927 } else {
2928 // otherwise pass the param1 unchanged
2929 run = g_strdup_printf ("%s \"%s\" %s", filter, filepath, param1);
2930 }
2931 } else {
2932 // run without extra parameter
2933 run = g_strdup_printf ("%s \"%s\"", filter, filepath);
2934 }
2935 }
2937 // Calculate the matrix that will be applied to the image so that it exactly overlaps the source objects
2938 NR::Matrix eek = sp_item_i2d_affine (SP_ITEM(parent_object));
2939 NR::Matrix t;
2941 double shift_x = bbox.x0;
2942 double shift_y = bbox.y1;
2943 if (res == PX_PER_IN) { // for default 90 dpi, snap it to pixel grid
2944 shift_x = round (shift_x);
2945 shift_y = -round (-shift_y); // this gets correct rounding despite coordinate inversion, remove the negations when the inversion is gone
2946 }
2947 t = NR::scale(1, -1) * NR::translate (shift_x, shift_y) * eek.inverse();
2949 // Do the export
2950 sp_export_png_file(document, filepath,
2951 bbox.x0, bbox.y0, bbox.x1, bbox.y1,
2952 width, height, res, res,
2953 (guint32) 0xffffff00,
2954 NULL, NULL,
2955 true, /*bool force_overwrite,*/
2956 items);
2958 g_slist_free (items);
2960 // Run filter, if any
2961 if (run) {
2962 g_print ("Running external filter: %s\n", run);
2963 system (run);
2964 }
2966 // Import the image back
2967 GdkPixbuf *pb = gdk_pixbuf_new_from_file (filepath, NULL);
2968 if (pb) {
2969 // Create the repr for the image
2970 Inkscape::XML::Node * repr = xml_doc->createElement("svg:image");
2971 repr->setAttribute("xlink:href", filename);
2972 repr->setAttribute("sodipodi:absref", filepath);
2973 if (res == PX_PER_IN) { // for default 90 dpi, snap it to pixel grid
2974 sp_repr_set_svg_double(repr, "width", width);
2975 sp_repr_set_svg_double(repr, "height", height);
2976 } else {
2977 sp_repr_set_svg_double(repr, "width", (bbox.x1 - bbox.x0));
2978 sp_repr_set_svg_double(repr, "height", (bbox.y1 - bbox.y0));
2979 }
2981 // Write transform
2982 gchar *c=sp_svg_transform_write(t);
2983 repr->setAttribute("transform", c);
2984 g_free(c);
2986 // add the new repr to the parent
2987 parent->appendChild(repr);
2989 // move to the saved position
2990 repr->setPosition(pos > 0 ? pos + 1 : 1);
2992 // Set selection to the new image
2993 selection->clear();
2994 selection->add(repr);
2996 // Clean up
2997 Inkscape::GC::release(repr);
2998 gdk_pixbuf_unref (pb);
3000 // Complete undoable transaction
3001 sp_document_done (document, SP_VERB_SELECTION_CREATE_BITMAP,
3002 _("Create bitmap"));
3003 }
3005 g_free (filename);
3006 g_free (filepath);
3007 }
3009 /**
3010 * \brief sp_selection_set_mask
3011 *
3012 * This function creates a mask or clipPath from selection
3013 * Two different modes:
3014 * if applyToLayer, all selection is moved to DEFS as mask/clippath
3015 * and is applied to current layer
3016 * otherwise, topmost object is used as mask for other objects
3017 * If \a apply_clip_path parameter is true, clipPath is created, otherwise mask
3018 *
3019 */
3020 void
3021 sp_selection_set_mask(bool apply_clip_path, bool apply_to_layer)
3022 {
3023 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
3024 if (desktop == NULL)
3025 return;
3027 SPDocument *doc = sp_desktop_document(desktop);
3028 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(doc);
3030 Inkscape::Selection *selection = sp_desktop_selection(desktop);
3032 // check if something is selected
3033 bool is_empty = selection->isEmpty();
3034 if ( apply_to_layer && is_empty) {
3035 desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to create clippath or mask from."));
3036 return;
3037 } else if (!apply_to_layer && ( is_empty || NULL == selection->itemList()->next )) {
3038 desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select mask object and <b>object(s)</b> to apply clippath or mask to."));
3039 return;
3040 }
3042 // FIXME: temporary patch to prevent crash!
3043 // Remove this when bboxes are fixed to not blow up on an item clipped/masked with its own clone
3044 bool clone_with_original = selection_contains_both_clone_and_original (selection);
3045 if (clone_with_original) {
3046 return; // in this version, you cannot clip/mask an object with its own clone
3047 }
3048 // /END FIXME
3050 sp_document_ensure_up_to_date(doc);
3052 GSList *items = g_slist_copy((GSList *) selection->itemList());
3054 items = g_slist_sort (items, (GCompareFunc) sp_object_compare_position);
3056 // create a list of duplicates
3057 GSList *mask_items = NULL;
3058 GSList *apply_to_items = NULL;
3059 GSList *items_to_delete = NULL;
3060 bool topmost = prefs_get_int_attribute ("options.maskobject", "topmost", 1);
3061 bool remove_original = prefs_get_int_attribute ("options.maskobject", "remove", 1);
3063 if (apply_to_layer) {
3064 // all selected items are used for mask, which is applied to a layer
3065 apply_to_items = g_slist_prepend (apply_to_items, desktop->currentLayer());
3067 for (GSList *i = items; i != NULL; i = i->next) {
3068 Inkscape::XML::Node *dup = (SP_OBJECT_REPR (i->data))->duplicate(xml_doc);
3069 mask_items = g_slist_prepend (mask_items, dup);
3071 if (remove_original) {
3072 SPObject *item = SP_OBJECT (i->data);
3073 items_to_delete = g_slist_prepend (items_to_delete, item);
3074 }
3075 }
3076 } else if (!topmost) {
3077 // topmost item is used as a mask, which is applied to other items in a selection
3078 GSList *i = items;
3079 Inkscape::XML::Node *dup = (SP_OBJECT_REPR (i->data))->duplicate(xml_doc);
3080 mask_items = g_slist_prepend (mask_items, dup);
3082 if (remove_original) {
3083 SPObject *item = SP_OBJECT (i->data);
3084 items_to_delete = g_slist_prepend (items_to_delete, item);
3085 }
3087 for (i = i->next; i != NULL; i = i->next) {
3088 apply_to_items = g_slist_prepend (apply_to_items, i->data);
3089 }
3090 } else {
3091 GSList *i = NULL;
3092 for (i = items; NULL != i->next; i = i->next) {
3093 apply_to_items = g_slist_prepend (apply_to_items, i->data);
3094 }
3096 Inkscape::XML::Node *dup = (SP_OBJECT_REPR (i->data))->duplicate(xml_doc);
3097 mask_items = g_slist_prepend (mask_items, dup);
3099 if (remove_original) {
3100 SPObject *item = SP_OBJECT (i->data);
3101 items_to_delete = g_slist_prepend (items_to_delete, item);
3102 }
3103 }
3105 g_slist_free (items);
3106 items = NULL;
3108 gchar const *attributeName = apply_clip_path ? "clip-path" : "mask";
3109 for (GSList *i = apply_to_items; NULL != i; i = i->next) {
3110 SPItem *item = reinterpret_cast<SPItem *>(i->data);
3111 // inverted object transform should be applied to a mask object,
3112 // as mask is calculated in user space (after applying transform)
3113 NR::Matrix maskTransform (item->transform.inverse());
3115 GSList *mask_items_dup = NULL;
3116 for (GSList *mask_item = mask_items; NULL != mask_item; mask_item = mask_item->next) {
3117 Inkscape::XML::Node *dup = reinterpret_cast<Inkscape::XML::Node *>(mask_item->data)->duplicate(xml_doc);
3118 mask_items_dup = g_slist_prepend (mask_items_dup, dup);
3119 }
3121 gchar const *mask_id = NULL;
3122 if (apply_clip_path) {
3123 mask_id = sp_clippath_create(mask_items_dup, doc, &maskTransform);
3124 } else {
3125 mask_id = sp_mask_create(mask_items_dup, doc, &maskTransform);
3126 }
3128 g_slist_free (mask_items_dup);
3129 mask_items_dup = NULL;
3131 SP_OBJECT_REPR(i->data)->setAttribute(attributeName, g_strdup_printf("url(#%s)", mask_id));
3132 }
3134 g_slist_free (mask_items);
3135 g_slist_free (apply_to_items);
3137 for (GSList *i = items_to_delete; NULL != i; i = i->next) {
3138 SPObject *item = SP_OBJECT (i->data);
3139 item->deleteObject (false);
3140 }
3141 g_slist_free (items_to_delete);
3143 if (apply_clip_path)
3144 sp_document_done (doc, SP_VERB_OBJECT_SET_CLIPPATH, _("Set clipping path"));
3145 else
3146 sp_document_done (doc, SP_VERB_OBJECT_SET_MASK, _("Set mask"));
3147 }
3149 void sp_selection_unset_mask(bool apply_clip_path) {
3150 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
3151 if (desktop == NULL)
3152 return;
3154 SPDocument *doc = sp_desktop_document(desktop);
3155 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(doc);
3156 Inkscape::Selection *selection = sp_desktop_selection(desktop);
3158 // check if something is selected
3159 if (selection->isEmpty()) {
3160 desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to remove clippath or mask from."));
3161 return;
3162 }
3164 bool remove_original = prefs_get_int_attribute ("options.maskobject", "remove", 1);
3165 sp_document_ensure_up_to_date(doc);
3167 gchar const *attributeName = apply_clip_path ? "clip-path" : "mask";
3168 std::map<SPObject*,SPItem*> referenced_objects;
3169 for (GSList const *i = selection->itemList(); NULL != i; i = i->next) {
3170 if (remove_original) {
3171 // remember referenced mask/clippath, so orphaned masks can be moved back to document
3172 SPItem *item = reinterpret_cast<SPItem *>(i->data);
3173 Inkscape::URIReference *uri_ref = NULL;
3175 if (apply_clip_path) {
3176 uri_ref = item->clip_ref;
3177 } else {
3178 uri_ref = item->mask_ref;
3179 }
3181 // collect distinct mask object (and associate with item to apply transform)
3182 if (NULL != uri_ref && NULL != uri_ref->getObject()) {
3183 referenced_objects[uri_ref->getObject()] = item;
3184 }
3185 }
3187 SP_OBJECT_REPR(i->data)->setAttribute(attributeName, "none");
3188 }
3190 // restore mask objects into a document
3191 for ( std::map<SPObject*,SPItem*>::iterator it = referenced_objects.begin() ; it != referenced_objects.end() ; ++it) {
3192 SPObject *obj = (*it).first;
3193 GSList *items_to_move = NULL;
3194 for (SPObject *child = sp_object_first_child(obj) ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
3195 Inkscape::XML::Node *copy = SP_OBJECT_REPR(child)->duplicate(xml_doc);
3196 items_to_move = g_slist_prepend (items_to_move, copy);
3197 }
3199 if (!obj->isReferenced()) {
3200 // delete from defs if no other object references this mask
3201 obj->deleteObject(false);
3202 }
3204 // remember parent and position of the item to which the clippath/mask was applied
3205 Inkscape::XML::Node *parent = SP_OBJECT_REPR((*it).second)->parent();
3206 gint pos = SP_OBJECT_REPR((*it).second)->position();
3208 for (GSList *i = items_to_move; NULL != i; i = i->next) {
3209 Inkscape::XML::Node *repr = (Inkscape::XML::Node *)i->data;
3211 // insert into parent, restore pos
3212 parent->appendChild(repr);
3213 repr->setPosition((pos + 1) > 0 ? (pos + 1) : 0);
3215 SPItem *mask_item = (SPItem *) sp_desktop_document (desktop)->getObjectByRepr(repr);
3216 selection->add(repr);
3218 // transform mask, so it is moved the same spot where mask was applied
3219 NR::Matrix transform (mask_item->transform);
3220 transform *= (*it).second->transform;
3221 sp_item_write_transform(mask_item, SP_OBJECT_REPR(mask_item), transform);
3222 }
3224 g_slist_free (items_to_move);
3225 }
3227 if (apply_clip_path)
3228 sp_document_done (doc, SP_VERB_OBJECT_UNSET_CLIPPATH, _("Release clipping path"));
3229 else
3230 sp_document_done (doc, SP_VERB_OBJECT_UNSET_MASK, _("Release mask"));
3231 }
3233 void fit_canvas_to_selection(SPDesktop *desktop) {
3234 g_return_if_fail(desktop != NULL);
3235 SPDocument *doc = sp_desktop_document(desktop);
3237 g_return_if_fail(doc != NULL);
3238 g_return_if_fail(desktop->selection != NULL);
3240 if (desktop->selection->isEmpty()) {
3241 desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to fit canvas to."));
3242 return;
3243 }
3244 NR::Maybe<NR::Rect> const bbox(desktop->selection->bounds());
3245 if (bbox && !bbox->isEmpty()) {
3246 doc->fitToRect(*bbox);
3247 }
3248 };
3250 void fit_canvas_to_drawing(SPDocument *doc) {
3251 g_return_if_fail(doc != NULL);
3253 sp_document_ensure_up_to_date(doc);
3254 SPItem const *const root = SP_ITEM(doc->root);
3255 NR::Maybe<NR::Rect> const bbox(root->getBounds(sp_item_i2r_affine(root)));
3256 if (bbox && !bbox->isEmpty()) {
3257 doc->fitToRect(*bbox);
3258 }
3259 };
3261 void fit_canvas_to_selection_or_drawing(SPDesktop *desktop) {
3262 g_return_if_fail(desktop != NULL);
3263 SPDocument *doc = sp_desktop_document(desktop);
3265 g_return_if_fail(doc != NULL);
3266 g_return_if_fail(desktop->selection != NULL);
3268 if (desktop->selection->isEmpty()) {
3269 fit_canvas_to_drawing(doc);
3270 } else {
3271 fit_canvas_to_selection(desktop);
3272 }
3274 sp_document_done(doc, SP_VERB_FIT_CANVAS_TO_DRAWING,
3275 _("Fit page to selection"));
3276 };
3278 static void itemtree_map(void (*f)(SPItem *, SPDesktop *), SPObject *root, SPDesktop *desktop) {
3279 // don't operate on layers
3280 if (SP_IS_ITEM(root) && !desktop->isLayer(SP_ITEM(root))) {
3281 f(SP_ITEM(root), desktop);
3282 }
3283 for ( SPObject::SiblingIterator iter = root->firstChild() ; iter ; ++iter ) {
3284 //don't recurse into locked layers
3285 if (!(SP_IS_ITEM(&*iter) && desktop->isLayer(SP_ITEM(&*iter)) && SP_ITEM(&*iter)->isLocked())) {
3286 itemtree_map(f, iter, desktop);
3287 }
3288 }
3289 }
3291 static void unlock(SPItem *item, SPDesktop */*desktop*/) {
3292 if (item->isLocked()) {
3293 item->setLocked(FALSE);
3294 }
3295 }
3297 static void unhide(SPItem *item, SPDesktop *desktop) {
3298 if (desktop->itemIsHidden(item)) {
3299 item->setExplicitlyHidden(FALSE);
3300 }
3301 }
3303 static void process_all(void (*f)(SPItem *, SPDesktop *), SPDesktop *dt, bool layer_only) {
3304 if (!dt) return;
3306 SPObject *root;
3307 if (layer_only) {
3308 root = dt->currentLayer();
3309 } else {
3310 root = dt->currentRoot();
3311 }
3313 itemtree_map(f, root, dt);
3314 }
3316 void unlock_all(SPDesktop *dt) {
3317 process_all(&unlock, dt, true);
3318 }
3320 void unlock_all_in_all_layers(SPDesktop *dt) {
3321 process_all(&unlock, dt, false);
3322 }
3324 void unhide_all(SPDesktop *dt) {
3325 process_all(&unhide, dt, true);
3326 }
3328 void unhide_all_in_all_layers(SPDesktop *dt) {
3329 process_all(&unhide, dt, false);
3330 }
3333 GSList * sp_selection_get_clipboard() {
3334 return clipboard;
3335 }
3338 /*
3339 Local Variables:
3340 mode:c++
3341 c-file-style:"stroustrup"
3342 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
3343 indent-tabs-mode:nil
3344 fill-column:99
3345 End:
3346 */
3347 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :