Code

Connector tool: make connectors avoid the convex hull of shapes.
[inkscape.git] / src / selection-chemistry.cpp
index b2ba56f620e53ea6173ccccf2dda36b5022a24de..e55bba2a5926079ef2e3a2fd99f300d7b8e791c2 100644 (file)
@@ -1,9 +1,9 @@
 #define __SP_SELECTION_CHEMISTRY_C__
 
-/*
- * Miscellanous operations on selected items
- *
- * Authors:
+/** @file
+ * @brief Miscellanous operations on selected items
+ */
+/* Authors:
  *   Lauris Kaplinski <lauris@kaplinski.com>
  *   Frank Felfe <innerspace@iname.com>
  *   MenTaLguY <mental@rydia.net>
 # include "config.h"
 #endif
 
+#include "selection-chemistry.h"
+
 #include <gtkmm/clipboard.h>
 
 #include "svg/svg.h"
-#include "inkscape.h"
 #include "desktop.h"
 #include "desktop-style.h"
+#include "dir-util.h"
 #include "selection.h"
 #include "tools-switch.h"
 #include "desktop-handles.h"
@@ -35,6 +37,7 @@
 #include "sp-use.h"
 #include "sp-textpath.h"
 #include "sp-tspan.h"
+#include "sp-tref.h"
 #include "sp-flowtext.h"
 #include "sp-flowregion.h"
 #include "text-editing.h"
 #include <glibmm/i18n.h>
 #include "libnr/nr-matrix-rotate-ops.h"
 #include "libnr/nr-matrix-translate-ops.h"
-#include "libnr/nr-rotate-fns.h"
 #include "libnr/nr-scale-ops.h"
-#include "libnr/nr-scale-translate-ops.h"
-#include "libnr/nr-translate-matrix-ops.h"
-#include "libnr/nr-translate-scale-ops.h"
+#include <libnr/nr-matrix-ops.h>
+#include <2geom/transforms.h>
 #include "xml/repr.h"
+#include "xml/rebase-hrefs.h"
 #include "style.h"
 #include "document-private.h"
 #include "sp-gradient.h"
@@ -60,7 +62,7 @@
 #include "sp-pattern.h"
 #include "sp-radial-gradient-fns.h"
 #include "sp-namedview.h"
-#include "prefs-utils.h"
+#include "preferences.h"
 #include "sp-offset.h"
 #include "sp-clippath.h"
 #include "sp-mask.h"
 #include "layer-fns.h"
 #include "context-fns.h"
 #include <map>
+#include <cstring>
+#include <string>
 #include "helper/units.h"
 #include "sp-item.h"
+#include "box3d.h"
 #include "unit-constants.h"
-using NR::X;
-using NR::Y;
+#include "xml/simple-document.h"
+#include "sp-filter-reference.h"
+#include "gradient-drag.h"
+#include "uri-references.h"
+#include "libnr/nr-convert2geom.h"
+#include "display/curve.h"
+#include "display/canvas-bpath.h"
+#include "inkscape-private.h"
+
+// For clippath editing
+#include "tools-switch.h"
+#include "shape-editor.h"
+#include "node-context.h"
+#include "nodepath.h"
 
-#include "selection-chemistry.h"
+#include "ui/clipboard.h"
 
-/* fixme: find a better place */
-GSList *clipboard = NULL;
-GSList *defs_clipboard = NULL;
-SPCSSAttr *style_clipboard = NULL;
-NR::Maybe<NR::Rect> size_clipboard;
+using Geom::X;
+using Geom::Y;
 
-static void sp_copy_stuff_used_by_item(GSList **defs_clip, SPItem *item, const GSList *items);
+/* The clipboard handling is in ui/clipboard.cpp now. There are some legacy functions left here,
+because the layer manipulation code uses them. It should be rewritten specifically
+for that purpose. */
 
 /**
  * Copies repr and its inherited css style elements, along with the accumulated transform 'full_t',
  * then prepends the copy to 'clip'.
  */
-void sp_selection_copy_one (Inkscape::XML::Node *repr, NR::Matrix full_t, GSList **clip)
+void sp_selection_copy_one(Inkscape::XML::Node *repr, Geom::Matrix full_t, GSList **clip, Inkscape::XML::Document* xml_doc)
 {
-    Inkscape::XML::Node *copy = repr->duplicate();
+    Inkscape::XML::Node *copy = repr->duplicate(xml_doc);
 
     // copy complete inherited style
     SPCSSAttr *css = sp_repr_css_attr_inherited(repr, "style");
@@ -99,7 +115,7 @@ void sp_selection_copy_one (Inkscape::XML::Node *repr, NR::Matrix full_t, GSList
     sp_repr_css_attr_unref(css);
 
     // write the complete accumulated transform passed to us
-    // (we're dealing with unattached repr, so we write to its attr 
+    // (we're dealing with unattached repr, so we write to its attr
     // instead of using sp_item_set_transform)
     gchar *affinestr=sp_svg_transform_write(full_t);
     copy->setAttribute("transform", affinestr);
@@ -108,76 +124,36 @@ void sp_selection_copy_one (Inkscape::XML::Node *repr, NR::Matrix full_t, GSList
     *clip = g_slist_prepend(*clip, copy);
 }
 
-void sp_selection_copy_impl (const GSList *items, GSList **clip, GSList **defs_clip, SPCSSAttr **style_clip)
+void sp_selection_copy_impl(GSList const *items, GSList **clip, Inkscape::XML::Document* xml_doc)
 {
+    // Sort items:
+    GSList *sorted_items = g_slist_copy((GSList *) items);
+    sorted_items = g_slist_sort((GSList *) sorted_items, (GCompareFunc) sp_object_compare_position);
 
-    // Copy stuff referenced by all items to defs_clip:
-    if (defs_clip) {
-        for (GSList *i = (GSList *) items; i != NULL; i = i->next) {
-            sp_copy_stuff_used_by_item (defs_clip, SP_ITEM (i->data), items);
-        }
-        *defs_clip = g_slist_reverse(*defs_clip);
-    }
-
-    // Store style:
-    if (style_clip) {
-        SPItem *item = SP_ITEM (items->data); // take from the first selected item
-        *style_clip = take_style_from_item (item);
-    }
-
-    if (clip) {
-        // Sort items:
-        GSList *sorted_items = g_slist_copy ((GSList *) items);
-        sorted_items = g_slist_sort((GSList *) sorted_items, (GCompareFunc) sp_object_compare_position);
-
-        // Copy item reprs:
-        for (GSList *i = (GSList *) sorted_items; i != NULL; i = i->next) {
-            sp_selection_copy_one (SP_OBJECT_REPR (i->data), sp_item_i2doc_affine(SP_ITEM (i->data)), clip);
-        }
-
-        *clip = g_slist_reverse(*clip);
-        g_slist_free ((GSList *) sorted_items);
+    // Copy item reprs:
+    for (GSList *i = (GSList *) sorted_items; i != NULL; i = i->next) {
+        sp_selection_copy_one(SP_OBJECT_REPR(i->data), sp_item_i2doc_affine(SP_ITEM(i->data)), clip, xml_doc);
     }
-}
-
-/**
- * Add gradients/patterns/markers referenced by copied objects to defs.
- * Iterates through 'defs_clip', and for each item it adds the data
- * repr into the global defs.
- */
-void
-paste_defs (GSList **defs_clip, SPDocument *doc)
-{
-    if (!defs_clip)
-        return;
 
-    for (GSList *gl = *defs_clip; gl != NULL; gl = gl->next) {
-        SPDefs *defs= (SPDefs *) SP_DOCUMENT_DEFS(doc);
-        Inkscape::XML::Node *repr = (Inkscape::XML::Node *) gl->data;
-        gchar const *id = repr->attribute("id");
-        if (!id || !doc->getObjectById(id)) {
-            Inkscape::XML::Node *copy = repr->duplicate();
-            SP_OBJECT_REPR(defs)->addChild(copy, NULL);
-            Inkscape::GC::release(copy);
-        }
-    }
+    *clip = g_slist_reverse(*clip);
+    g_slist_free((GSList *) sorted_items);
 }
 
-GSList *sp_selection_paste_impl (SPDocument *document, SPObject *parent, GSList **clip, GSList **defs_clip)
+GSList *sp_selection_paste_impl(SPDocument *doc, SPObject *parent, GSList **clip)
 {
-    paste_defs (defs_clip, document);
+    Inkscape::XML::Document *xml_doc = sp_document_repr_doc(doc);
 
     GSList *copied = NULL;
     // add objects to document
     for (GSList *l = *clip; l != NULL; l = l->next) {
         Inkscape::XML::Node *repr = (Inkscape::XML::Node *) l->data;
-        Inkscape::XML::Node *copy = repr->duplicate();
+        Inkscape::XML::Node *copy = repr->duplicate(xml_doc);
 
         // premultiply the item transform by the accumulated parent transform in the paste layer
-        NR::Matrix local = sp_item_i2doc_affine(SP_ITEM(parent));
-        if (!local.test_identity()) {
+        Geom::Matrix local(sp_item_i2doc_affine(SP_ITEM(parent)));
+        if (!local.isIdentity()) {
             gchar const *t_str = copy->attribute("transform");
-            NR::Matrix item_t (NR::identity());
+            Geom::Matrix item_t(Geom::identity());
             if (t_str)
                 sp_svg_transform_read(t_str, &item_t);
             item_t *= local.inverse();
@@ -194,27 +170,26 @@ GSList *sp_selection_paste_impl (SPDocument *document, SPObject *parent, GSList
     return copied;
 }
 
-void sp_selection_delete_impl(const GSList *items)
+void sp_selection_delete_impl(GSList const *items, bool propagate = true, bool propagate_descendants = true)
 {
-    for (const GSList *i = items ; i ; i = i->next ) {
+    for (GSList const *i = items ; i ; i = i->next ) {
         sp_object_ref((SPObject *)i->data, NULL);
     }
-    for (const GSList *i = items; i != NULL; i = i->next) {
+    for (GSList const *i = items; i != NULL; i = i->next) {
         SPItem *item = (SPItem *) i->data;
-        SP_OBJECT(item)->deleteObject();
+        SP_OBJECT(item)->deleteObject(propagate, propagate_descendants);
         sp_object_unref((SPObject *)item, NULL);
     }
 }
 
 
-void sp_selection_delete()
+void sp_selection_delete(SPDesktop *desktop)
 {
-    SPDesktop *desktop = SP_ACTIVE_DESKTOP;
     if (desktop == NULL) {
         return;
     }
 
-    if (tools_isactive (desktop, TOOLS_TEXT))
+    if (tools_isactive(desktop, TOOLS_TEXT))
         if (sp_text_delete_selection(desktop->event_context)) {
             sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
                              _("Delete text"));
@@ -229,10 +204,10 @@ void sp_selection_delete()
         return;
     }
 
-    const GSList *selected = g_slist_copy(const_cast<GSList *>(selection->itemList()));
+    GSList const *selected = g_slist_copy(const_cast<GSList *>(selection->itemList()));
     selection->clear();
-    sp_selection_delete_impl (selected);
-    g_slist_free ((GSList *) selected);
+    sp_selection_delete_impl(selected);
+    g_slist_free((GSList *) selected);
 
     /* a tool may have set up private information in it's selection context
      * that depends on desktop items.  I think the only sane way to deal with
@@ -240,19 +215,33 @@ void sp_selection_delete()
      * associated selection context.  For example: deleting an object
      * while moving it around the canvas.
      */
-    tools_switch ( desktop, tools_active ( desktop ) );
+    tools_switch( desktop, tools_active( desktop ) );
 
-    sp_document_done(sp_desktop_document(desktop), SP_VERB_EDIT_DELETE, 
+    sp_document_done(sp_desktop_document(desktop), SP_VERB_EDIT_DELETE,
                      _("Delete"));
 }
 
-/* fixme: sequencing */
-void sp_selection_duplicate()
+void add_ids_recursive(std::vector<const gchar *> &ids, SPObject *obj)
+{
+    if (!obj)
+        return;
+
+    ids.push_back(SP_OBJECT_ID(obj));
+
+    if (SP_IS_GROUP(obj)) {
+        for (SPObject *child = sp_object_first_child(obj) ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
+            add_ids_recursive(ids, child);
+        }
+    }
+}
+
+void sp_selection_duplicate(SPDesktop *desktop, bool suppressDone)
 {
-    SPDesktop *desktop = SP_ACTIVE_DESKTOP;
     if (desktop == NULL)
         return;
 
+    SPDocument *doc = desktop->doc();
+    Inkscape::XML::Document* xml_doc = sp_document_repr_doc(doc);
     Inkscape::Selection *selection = sp_desktop_selection(desktop);
 
     // check if something is selected
@@ -265,33 +254,75 @@ void sp_selection_duplicate()
 
     selection->clear();
 
-    // sorting items from different parents sorts each parent's subset without possibly mixing them, just what we need
+    // sorting items from different parents sorts each parent's subset without possibly mixing
+    // them, just what we need
     reprs = g_slist_sort(reprs, (GCompareFunc) sp_repr_compare_position);
 
     GSList *newsel = NULL;
 
+    std::vector<const gchar *> old_ids;
+    std::vector<const gchar *> new_ids;
+    Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+    bool relink_clones = prefs->getBool("/options/relinkclonesonduplicate/value");
+
     while (reprs) {
-        Inkscape::XML::Node *parent = ((Inkscape::XML::Node *) reprs->data)->parent();
-        Inkscape::XML::Node *copy = ((Inkscape::XML::Node *) reprs->data)->duplicate();
+        Inkscape::XML::Node *old_repr = (Inkscape::XML::Node *) reprs->data;
+        Inkscape::XML::Node *parent = old_repr->parent();
+        Inkscape::XML::Node *copy = old_repr->duplicate(xml_doc);
 
         parent->appendChild(copy);
 
+        if (relink_clones) {
+            SPObject *old_obj = doc->getObjectByRepr(old_repr);
+            SPObject *new_obj = doc->getObjectByRepr(copy);
+            add_ids_recursive(old_ids, old_obj);
+            add_ids_recursive(new_ids, new_obj);
+        }
+
         newsel = g_slist_prepend(newsel, copy);
         reprs = g_slist_remove(reprs, reprs->data);
         Inkscape::GC::release(copy);
     }
 
-    sp_document_done(sp_desktop_document(desktop), SP_VERB_EDIT_DUPLICATE, 
-                     _("Duplicate"));
+    if (relink_clones) {
+
+        g_assert(old_ids.size() == new_ids.size());
+
+        for (unsigned int i = 0; i < old_ids.size(); i++) {
+            const gchar *id = old_ids[i];
+            SPObject *old_clone = doc->getObjectById(id);
+            if (SP_IS_USE(old_clone)) {
+                SPItem *orig = sp_use_get_original(SP_USE(old_clone));
+                if (!orig) // orphaned
+                    continue;
+                for (unsigned int j = 0; j < old_ids.size(); j++) {
+                    if (!strcmp(SP_OBJECT_ID(orig), old_ids[j])) {
+                        // we have both orig and clone in selection, relink
+                        // std::cout << id  << " old, its ori: " << SP_OBJECT_ID(orig) << "; will relink:" << new_ids[i] << " to " << new_ids[j] << "\n";
+                        gchar *newref = g_strdup_printf("#%s", new_ids[j]);
+                        SPObject *new_clone = doc->getObjectById(new_ids[i]);
+                        SP_OBJECT_REPR(new_clone)->setAttribute("xlink:href", newref);
+                        new_clone->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
+                        g_free(newref);
+                    }
+                }
+            }
+        }
+    }
+
+
+    if ( !suppressDone ) {
+        sp_document_done(sp_desktop_document(desktop), SP_VERB_EDIT_DUPLICATE,
+                         _("Duplicate"));
+    }
 
     selection->setReprList(newsel);
 
     g_slist_free(newsel);
 }
 
-void sp_edit_clear_all()
+void sp_edit_clear_all(SPDesktop *dt)
 {
-    SPDesktop *dt = SP_ACTIVE_DESKTOP;
     if (!dt)
         return;
 
@@ -302,7 +333,7 @@ void sp_edit_clear_all()
     GSList *items = sp_item_group_item_list(SP_GROUP(dt->currentLayer()));
 
     while (items) {
-        SP_OBJECT (items->data)->deleteObject();
+        SP_OBJECT(items->data)->deleteObject();
         items = g_slist_remove(items, items->data);
     }
 
@@ -311,30 +342,29 @@ void sp_edit_clear_all()
 }
 
 GSList *
-get_all_items (GSList *list, SPObject *from, SPDesktop *desktop, bool onlyvisible, bool onlysensitive, const GSList *exclude)
+get_all_items(GSList *list, SPObject *from, SPDesktop *desktop, bool onlyvisible, bool onlysensitive, GSList const *exclude)
 {
     for (SPObject *child = sp_object_first_child(SP_OBJECT(from)) ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
         if (SP_IS_ITEM(child) &&
             !desktop->isLayer(SP_ITEM(child)) &&
             (!onlysensitive || !SP_ITEM(child)->isLocked()) &&
             (!onlyvisible || !desktop->itemIsHidden(SP_ITEM(child))) &&
-            (!exclude || !g_slist_find ((GSList *) exclude, child))
+            (!exclude || !g_slist_find((GSList *) exclude, child))
             )
         {
-            list = g_slist_prepend (list, SP_ITEM(child));
+            list = g_slist_prepend(list, SP_ITEM(child));
         }
 
         if (SP_IS_ITEM(child) && desktop->isLayer(SP_ITEM(child))) {
-            list = get_all_items (list, child, desktop, onlyvisible, onlysensitive, exclude);
+            list = get_all_items(list, child, desktop, onlyvisible, onlysensitive, exclude);
         }
     }
 
     return list;
 }
 
-void sp_edit_select_all_full (bool force_all_layers, bool invert)
+void sp_edit_select_all_full(SPDesktop *dt, bool force_all_layers, bool invert)
 {
-    SPDesktop *dt = SP_ACTIVE_DESKTOP;
     if (!dt)
         return;
 
@@ -342,13 +372,14 @@ void sp_edit_select_all_full (bool force_all_layers, bool invert)
 
     g_return_if_fail(SP_IS_GROUP(dt->currentLayer()));
 
-    PrefsSelectionContext inlayer = (PrefsSelectionContext)prefs_get_int_attribute ("options.kbselection", "inlayer", PREFS_SELECTION_LAYER);
-    bool onlyvisible = prefs_get_int_attribute ("options.kbselection", "onlyvisible", 1);
-    bool onlysensitive = prefs_get_int_attribute ("options.kbselection", "onlysensitive", 1);
+    Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+    PrefsSelectionContext inlayer = (PrefsSelectionContext) prefs->getInt("/options/kbselection/inlayer", PREFS_SELECTION_LAYER);
+    bool onlyvisible = prefs->getBool("/options/kbselection/onlyvisible", true);
+    bool onlysensitive = prefs->getBool("/options/kbselection/onlysensitive", true);
 
     GSList *items = NULL;
 
-    const GSList *exclude = NULL;
+    GSList const *exclude = NULL;
     if (invert) {
         exclude = selection->itemList();
     }
@@ -365,67 +396,66 @@ void sp_edit_select_all_full (bool force_all_layers, bool invert)
         GSList *all_items = sp_item_group_item_list(SP_GROUP(dt->currentLayer()));
 
         for (GSList *i = all_items; i; i = i->next) {
-            SPItem *item = SP_ITEM (i->data);
+            SPItem *item = SP_ITEM(i->data);
 
             if (item && (!onlysensitive || !item->isLocked())) {
                 if (!onlyvisible || !dt->itemIsHidden(item)) {
                     if (!dt->isLayer(item)) {
-                        if (!invert || !g_slist_find ((GSList *) exclude, item)) {
-                            items = g_slist_prepend (items, item); // leave it in the list
+                        if (!invert || !g_slist_find((GSList *) exclude, item)) {
+                            items = g_slist_prepend(items, item); // leave it in the list
                         }
                     }
                 }
             }
         }
 
-        g_slist_free (all_items);
+        g_slist_free(all_items);
             break;
         }
         case PREFS_SELECTION_LAYER_RECURSIVE: {
-            items = get_all_items (NULL, dt->currentLayer(), dt, onlyvisible, onlysensitive, exclude);
+            items = get_all_items(NULL, dt->currentLayer(), dt, onlyvisible, onlysensitive, exclude);
             break;
         }
         default: {
-        items = get_all_items (NULL, dt->currentRoot(), dt, onlyvisible, onlysensitive, exclude);
+        items = get_all_items(NULL, dt->currentRoot(), dt, onlyvisible, onlysensitive, exclude);
             break;
     }
     }
 
-    selection->setList (items);
+    selection->setList(items);
 
     if (items) {
-        g_slist_free (items);
+        g_slist_free(items);
     }
 }
 
-void sp_edit_select_all ()
+void sp_edit_select_all(SPDesktop *desktop)
 {
-    sp_edit_select_all_full (false, false);
+    sp_edit_select_all_full(desktop, false, false);
 }
 
-void sp_edit_select_all_in_all_layers ()
+void sp_edit_select_all_in_all_layers(SPDesktop *desktop)
 {
-    sp_edit_select_all_full (true, false);
+    sp_edit_select_all_full(desktop, true, false);
 }
 
-void sp_edit_invert ()
+void sp_edit_invert(SPDesktop *desktop)
 {
-    sp_edit_select_all_full (false, true);
+    sp_edit_select_all_full(desktop, false, true);
 }
 
-void sp_edit_invert_in_all_layers ()
+void sp_edit_invert_in_all_layers(SPDesktop *desktop)
 {
-    sp_edit_select_all_full (true, true);
+    sp_edit_select_all_full(desktop, true, true);
 }
 
-void sp_selection_group()
+void sp_selection_group(SPDesktop *desktop)
 {
-    SPDesktop *desktop = SP_ACTIVE_DESKTOP;
     if (desktop == NULL)
         return;
 
-    SPDocument *document = sp_desktop_document (desktop);
-    Inkscape::XML::Document *xml_doc = sp_document_repr_doc(document);
+    SPDocument *doc = sp_desktop_document(desktop);
+    Inkscape::XML::Document *xml_doc = sp_document_repr_doc(doc);
 
     Inkscape::Selection *selection = sp_desktop_selection(desktop);
 
@@ -453,46 +483,46 @@ void sp_selection_group()
         Inkscape::XML::Node *current = (Inkscape::XML::Node *) p->data;
 
         if (current->parent() == topmost_parent) {
-            Inkscape::XML::Node *spnew = current->duplicate();
+            Inkscape::XML::Node *spnew = current->duplicate(xml_doc);
             sp_repr_unparent(current);
             group->appendChild(spnew);
             Inkscape::GC::release(spnew);
             topmost --; // only reduce count for those items deleted from topmost_parent
         } else { // move it to topmost_parent first
-                GSList *temp_clip = NULL;
-
-                // At this point, current may already have no item, due to its being a clone whose original is already moved away
-                // So we copy it artificially calculating the transform from its repr->attr("transform") and the parent transform
-                gchar const *t_str = current->attribute("transform");
-                NR::Matrix item_t (NR::identity());
-                if (t_str)
-                    sp_svg_transform_read(t_str, &item_t);
-                item_t *= sp_item_i2doc_affine(SP_ITEM(document->getObjectByRepr(current->parent())));
-                //FIXME: when moving both clone and original from a transformed group (either by
-                //grouping into another parent, or by cut/paste) the transform from the original's
-                //parent becomes embedded into original itself, and this affects its clones. Fix
-                //this by remembering the transform diffs we write to each item into an array and
-                //then, if this is clone, looking up its original in that array and pre-multiplying
-                //it by the inverse of that original's transform diff.
-
-                sp_selection_copy_one (current, item_t, &temp_clip);
-                sp_repr_unparent(current);
-
-                // paste into topmost_parent (temporarily)
-                GSList *copied = sp_selection_paste_impl (document, document->getObjectByRepr(topmost_parent), &temp_clip, NULL);
-                if (temp_clip) g_slist_free (temp_clip);
-                if (copied) { // if success,
-                    // take pasted object (now in topmost_parent)
-                    Inkscape::XML::Node *in_topmost = (Inkscape::XML::Node *) copied->data;
-                    // make a copy
-                    Inkscape::XML::Node *spnew = in_topmost->duplicate();
-                    // remove pasted
-                    sp_repr_unparent(in_topmost);
-                    // put its copy into group
-                    group->appendChild(spnew);
-                    Inkscape::GC::release(spnew);
-                    g_slist_free (copied);
-                }
+            GSList *temp_clip = NULL;
+
+            // At this point, current may already have no item, due to its being a clone whose original is already moved away
+            // So we copy it artificially calculating the transform from its repr->attr("transform") and the parent transform
+            gchar const *t_str = current->attribute("transform");
+            Geom::Matrix item_t(Geom::identity());
+            if (t_str)
+                sp_svg_transform_read(t_str, &item_t);
+            item_t *= sp_item_i2doc_affine(SP_ITEM(doc->getObjectByRepr(current->parent())));
+            // FIXME: when moving both clone and original from a transformed group (either by
+            // grouping into another parent, or by cut/paste) the transform from the original's
+            // parent becomes embedded into original itself, and this affects its clones. Fix
+            // this by remembering the transform diffs we write to each item into an array and
+            // then, if this is clone, looking up its original in that array and pre-multiplying
+            // it by the inverse of that original's transform diff.
+
+            sp_selection_copy_one(current, item_t, &temp_clip, xml_doc);
+            sp_repr_unparent(current);
+
+            // paste into topmost_parent (temporarily)
+            GSList *copied = sp_selection_paste_impl(doc, doc->getObjectByRepr(topmost_parent), &temp_clip);
+            if (temp_clip) g_slist_free(temp_clip);
+            if (copied) { // if success,
+                // take pasted object (now in topmost_parent)
+                Inkscape::XML::Node *in_topmost = (Inkscape::XML::Node *) copied->data;
+                // make a copy
+                Inkscape::XML::Node *spnew = in_topmost->duplicate(xml_doc);
+                // remove pasted
+                sp_repr_unparent(in_topmost);
+                // put its copy into group
+                group->appendChild(spnew);
+                Inkscape::GC::release(spnew);
+                g_slist_free(copied);
+            }
         }
         p = g_slist_remove(p, current);
     }
@@ -503,16 +533,15 @@ void sp_selection_group()
     // Move to the position of the topmost, reduced by the number of items deleted from topmost_parent
     group->setPosition(topmost + 1);
 
-    sp_document_done(sp_desktop_document(desktop), SP_VERB_SELECTION_GROUP, 
+    sp_document_done(sp_desktop_document(desktop), SP_VERB_SELECTION_GROUP,
                      _("Group"));
 
     selection->set(group);
     Inkscape::GC::release(group);
 }
 
-void sp_selection_ungroup()
+void sp_selection_ungroup(SPDesktop *desktop)
 {
-    SPDesktop *desktop = SP_ACTIVE_DESKTOP;
     if (desktop == NULL)
         return;
 
@@ -565,18 +594,48 @@ void sp_selection_ungroup()
 
     g_slist_free(items);
 
-    sp_document_done(sp_desktop_document(desktop), SP_VERB_SELECTION_UNGROUP, 
+    sp_document_done(sp_desktop_document(desktop), SP_VERB_SELECTION_UNGROUP,
                      _("Ungroup"));
 }
 
+/** Replace all groups in the list with their member objects, recursively; returns a new list, frees old */
+GSList *
+sp_degroup_list(GSList *items)
+{
+    GSList *out = NULL;
+    bool has_groups = false;
+    for (GSList *item = items; item; item = item->next) {
+        if (!SP_IS_GROUP(item->data)) {
+            out = g_slist_prepend(out, item->data);
+        } else {
+            has_groups = true;
+            GSList *members = sp_item_group_item_list(SP_GROUP(item->data));
+            for (GSList *member = members; member; member = member->next) {
+                out = g_slist_prepend(out, member->data);
+            }
+            g_slist_free(members);
+        }
+    }
+    out = g_slist_reverse(out);
+    g_slist_free(items);
+
+    if (has_groups) { // recurse if we unwrapped a group - it may have contained others
+        out = sp_degroup_list(out);
+    }
+
+    return out;
+}
+
+
+/** If items in the list have a common parent, return it, otherwise return NULL */
 static SPGroup *
-sp_item_list_common_parent_group(const GSList *items)
+sp_item_list_common_parent_group(GSList const *items)
 {
     if (!items) {
         return NULL;
     }
     SPObject *parent = SP_OBJECT_PARENT(items->data);
-    /* Strictly speaking this CAN happen, if user selects <svg> from XML editor */
+    /* Strictly speaking this CAN happen, if user selects <svg> from Inkscape::XML editor */
     if (!SP_IS_GROUP(parent)) {
         return NULL;
     }
@@ -589,16 +648,15 @@ sp_item_list_common_parent_group(const GSList *items)
     return SP_GROUP(parent);
 }
 
-/** Finds out the minimum common bbox of the selected items
- */
-static NR::Maybe<NR::Rect>
-enclose_items(const GSList *items)
+/** Finds out the minimum common bbox of the selected items. */
+static Geom::OptRect
+enclose_items(GSList const *items)
 {
     g_assert(items != NULL);
 
-    NR::Maybe<NR::Rect> r = NR::Nothing();
+    Geom::OptRect r;
     for (GSList const *i = items; i; i = i->next) {
-        r = NR::union_bounds(r, sp_item_bbox_desktop((SPItem *) i->data));
+        r = Geom::unify(r, sp_item_bbox_desktop((SPItem *) i->data));
     }
     return r;
 }
@@ -618,9 +676,8 @@ prev_sibling(SPObject *child)
 }
 
 void
-sp_selection_raise()
+sp_selection_raise(SPDesktop *desktop)
 {
-    SPDesktop *desktop = SP_ACTIVE_DESKTOP;
     if (!desktop)
         return;
 
@@ -640,14 +697,14 @@ sp_selection_raise()
 
     Inkscape::XML::Node *grepr = SP_OBJECT_REPR(group);
 
-    /* construct reverse-ordered list of selected children */
+    /* Construct reverse-ordered list of selected children. */
     GSList *rev = g_slist_copy((GSList *) items);
     rev = g_slist_sort(rev, (GCompareFunc) sp_item_repr_compare_position);
 
-    // find out the common bbox of the selected items
-    NR::Maybe<NR::Rect> selected = enclose_items(items);
+    // Determine the common bbox of the selected items.
+    Geom::OptRect selected = enclose_items(items);
 
-    // for all objects in the selection (starting from top)
+    // Iterate over all objects in the selection (starting from top).
     if (selected) {
         while (rev) {
             SPObject *child = SP_OBJECT(rev->data);
@@ -655,7 +712,7 @@ sp_selection_raise()
             for (SPObject *newref = child->next; newref; newref = newref->next) {
                 // if the sibling is an item AND overlaps our selection,
                 if (SP_IS_ITEM(newref)) {
-                    NR::Maybe<NR::Rect> newref_bbox = sp_item_bbox_desktop(SP_ITEM(newref));
+                    Geom::OptRect newref_bbox = sp_item_bbox_desktop(SP_ITEM(newref));
                     if ( newref_bbox && selected->intersects(*newref_bbox) ) {
                         // AND if it's not one of our selected objects,
                         if (!g_slist_find((GSList *) items, newref)) {
@@ -673,12 +730,14 @@ sp_selection_raise()
     }
 
     sp_document_done(sp_desktop_document(desktop), SP_VERB_SELECTION_RAISE,
-                     _("Raise"));
+                     //TRANSLATORS: only translate "string" in "context|string".
+                     // For more details, see http://developer.gnome.org/doc/API/2.0/glib/glib-I18N.html#Q-:CAPS
+                     // "Raise" means "to raise an object" in the undo history
+                     Q_("undo_action|Raise"));
 }
 
-void sp_selection_raise_to_top()
+void sp_selection_raise_to_top(SPDesktop *desktop)
 {
-    SPDesktop *desktop = SP_ACTIVE_DESKTOP;
     if (desktop == NULL)
         return;
 
@@ -708,14 +767,13 @@ void sp_selection_raise_to_top()
 
     g_slist_free(rl);
 
-    sp_document_done(document, SP_VERB_SELECTION_TO_FRONT, 
+    sp_document_done(document, SP_VERB_SELECTION_TO_FRONT,
                      _("Raise to top"));
 }
 
 void
-sp_selection_lower()
+sp_selection_lower(SPDesktop *desktop)
 {
-    SPDesktop *desktop = SP_ACTIVE_DESKTOP;
     if (desktop == NULL)
         return;
 
@@ -735,15 +793,15 @@ sp_selection_lower()
 
     Inkscape::XML::Node *grepr = SP_OBJECT_REPR(group);
 
-    // find out the common bbox of the selected items
-    NR::Maybe<NR::Rect> selected = enclose_items(items);
+    // Determine the common bbox of the selected items.
+    Geom::OptRect selected = enclose_items(items);
 
-    /* construct direct-ordered list of selected children */
+    /* Construct direct-ordered list of selected children. */
     GSList *rev = g_slist_copy((GSList *) items);
     rev = g_slist_sort(rev, (GCompareFunc) sp_item_repr_compare_position);
     rev = g_slist_reverse(rev);
 
-    // for all objects in the selection (starting from top)
+    // Iterate over all objects in the selection (starting from top).
     if (selected) {
         while (rev) {
             SPObject *child = SP_OBJECT(rev->data);
@@ -751,7 +809,7 @@ sp_selection_lower()
             for (SPObject *newref = prev_sibling(child); newref; newref = prev_sibling(newref)) {
                 // if the sibling is an item AND overlaps our selection,
                 if (SP_IS_ITEM(newref)) {
-                    NR::Maybe<NR::Rect> ref_bbox = sp_item_bbox_desktop(SP_ITEM(newref));
+                    Geom::OptRect ref_bbox = sp_item_bbox_desktop(SP_ITEM(newref));
                     if ( ref_bbox && selected->intersects(*ref_bbox) ) {
                         // AND if it's not one of our selected objects,
                         if (!g_slist_find((GSList *) items, newref)) {
@@ -772,13 +830,12 @@ sp_selection_lower()
         g_slist_free(rev);
     }
 
-    sp_document_done(sp_desktop_document(desktop), SP_VERB_SELECTION_LOWER, 
+    sp_document_done(sp_desktop_document(desktop), SP_VERB_SELECTION_LOWER,
                      _("Lower"));
 }
 
-void sp_selection_lower_to_bottom()
+void sp_selection_lower_to_bottom(SPDesktop *desktop)
 {
-    SPDesktop *desktop = SP_ACTIVE_DESKTOP;
     if (desktop == NULL)
         return;
 
@@ -820,7 +877,7 @@ void sp_selection_lower_to_bottom()
 
     g_slist_free(rl);
 
-    sp_document_done(document, SP_VERB_SELECTION_TO_BACK, 
+    sp_document_done(document, SP_VERB_SELECTION_TO_BACK,
                      _("Lower to bottom"));
 }
 
@@ -838,163 +895,47 @@ sp_redo(SPDesktop *desktop, SPDocument *)
             desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Nothing to redo."));
 }
 
-void sp_selection_cut()
+void sp_selection_cut(SPDesktop *desktop)
 {
     sp_selection_copy();
-    sp_selection_delete();
-}
-
-void sp_copy_gradient (GSList **defs_clip, SPGradient *gradient)
-{
-    SPGradient *ref = gradient;
-
-    while (ref) {
-        // climb up the refs, copying each one in the chain
-        Inkscape::XML::Node *grad_repr = SP_OBJECT_REPR(ref)->duplicate();
-        *defs_clip = g_slist_prepend (*defs_clip, grad_repr);
-
-        ref = ref->ref->getObject();
-    }
-}
-
-void sp_copy_pattern (GSList **defs_clip, SPPattern *pattern)
-{
-    SPPattern *ref = pattern;
-
-    while (ref) {
-        // climb up the refs, copying each one in the chain
-        Inkscape::XML::Node *pattern_repr = SP_OBJECT_REPR(ref)->duplicate();
-        *defs_clip = g_slist_prepend (*defs_clip, pattern_repr);
-
-        // items in the pattern may also use gradients and other patterns, so we need to recurse here as well
-        for (SPObject *child = sp_object_first_child(SP_OBJECT(ref)) ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
-            if (!SP_IS_ITEM (child))
-                continue;
-            sp_copy_stuff_used_by_item (defs_clip, (SPItem *) child, NULL);
-        }
-
-        ref = ref->ref->getObject();
-    }
-}
-
-void sp_copy_single (GSList **defs_clip, SPObject *thing)
-{
-    Inkscape::XML::Node *duplicate_repr = SP_OBJECT_REPR(thing)->duplicate();
-    *defs_clip = g_slist_prepend (*defs_clip, duplicate_repr);
-}
-
-
-void sp_copy_textpath_path (GSList **defs_clip, SPTextPath *tp, const GSList *items)
-{
-    SPItem *path = sp_textpath_get_path_item (tp);
-    if (!path)
-        return;
-    if (items && g_slist_find ((GSList *) items, path)) // do not copy it to defs if it is already in the list of items copied
-        return;
-    Inkscape::XML::Node *repr = SP_OBJECT_REPR(path)->duplicate();
-    *defs_clip = g_slist_prepend (*defs_clip, repr);
-}
-
-/**
- * Copies things like patterns, markers, gradients, etc.
- */
-void sp_copy_stuff_used_by_item (GSList **defs_clip, SPItem *item, const GSList *items)
-{
-    SPStyle *style = SP_OBJECT_STYLE (item);
-
-    if (style && (style->fill.type == SP_PAINT_TYPE_PAINTSERVER)) {
-        SPObject *server = SP_OBJECT_STYLE_FILL_SERVER(item);
-        if (SP_IS_LINEARGRADIENT (server) || SP_IS_RADIALGRADIENT (server))
-            sp_copy_gradient (defs_clip, SP_GRADIENT(server));
-        if (SP_IS_PATTERN (server))
-            sp_copy_pattern (defs_clip, SP_PATTERN(server));
-    }
-
-    if (style && (style->stroke.type == SP_PAINT_TYPE_PAINTSERVER)) {
-        SPObject *server = SP_OBJECT_STYLE_STROKE_SERVER(item);
-        if (SP_IS_LINEARGRADIENT (server) || SP_IS_RADIALGRADIENT (server))
-            sp_copy_gradient (defs_clip, SP_GRADIENT(server));
-        if (SP_IS_PATTERN (server))
-            sp_copy_pattern (defs_clip, SP_PATTERN(server));
-    }
-
-    // For shapes, copy all of the shape's markers into defs_clip
-    if (SP_IS_SHAPE (item)) {
-        SPShape *shape = SP_SHAPE (item);
-        for (int i = 0 ; i < SP_MARKER_LOC_QTY ; i++) {
-            if (shape->marker[i]) {
-                sp_copy_single (defs_clip, SP_OBJECT (shape->marker[i]));
-            }
-        }
-    }
-
-    if (SP_IS_TEXT_TEXTPATH (item)) {
-        sp_copy_textpath_path (defs_clip, SP_TEXTPATH(sp_object_first_child(SP_OBJECT(item))), items);
-    }
-
-    if (item->clip_ref->getObject()) {
-        sp_copy_single (defs_clip, item->clip_ref->getObject());
-    }
-
-    if (item->mask_ref->getObject()) {
-        SPObject *mask = item->mask_ref->getObject();
-        sp_copy_single (defs_clip, mask);
-        // recurse into the mask for its gradients etc.
-        for (SPObject *o = SP_OBJECT(mask)->children; o != NULL; o = o->next) {
-            if (SP_IS_ITEM(o))
-                sp_copy_stuff_used_by_item (defs_clip, SP_ITEM (o), items);
-        }
-    }
-
-    if (style->filter.filter) {
-        SPObject *filter = style->filter.filter;
-        if (SP_IS_FILTER(filter)) {
-            sp_copy_single (defs_clip, filter);
-        }
-    }
-
-    // recurse
-    for (SPObject *o = SP_OBJECT(item)->children; o != NULL; o = o->next) {
-        if (SP_IS_ITEM(o))
-            sp_copy_stuff_used_by_item (defs_clip, SP_ITEM (o), items);
-    }
+    sp_selection_delete(desktop);
 }
 
 /**
  * \pre item != NULL
  */
 SPCSSAttr *
-take_style_from_item (SPItem *item)
+take_style_from_item(SPItem *item)
 {
     // write the complete cascaded style, context-free
-    SPCSSAttr *css = sp_css_attr_from_object (SP_OBJECT(item), SP_STYLE_FLAG_ALWAYS);
+    SPCSSAttr *css = sp_css_attr_from_object(SP_OBJECT(item), SP_STYLE_FLAG_ALWAYS);
     if (css == NULL)
         return NULL;
 
     if ((SP_IS_GROUP(item) && SP_OBJECT(item)->children) ||
-        (SP_IS_TEXT (item) && SP_OBJECT(item)->children && SP_OBJECT(item)->children->next == NULL)) {
+        (SP_IS_TEXT(item) && SP_OBJECT(item)->children && SP_OBJECT(item)->children->next == NULL)) {
         // if this is a text with exactly one tspan child, merge the style of that tspan as well
         // If this is a group, merge the style of its topmost (last) child with style
-        for (SPObject *last_element = item->lastChild(); last_element != NULL; last_element = SP_OBJECT_PREV (last_element)) {
-            if (SP_OBJECT_STYLE (last_element) != NULL) {
-                SPCSSAttr *temp = sp_css_attr_from_object (last_element, SP_STYLE_FLAG_IFSET);
+        for (SPObject *last_element = item->lastChild(); last_element != NULL; last_element = SP_OBJECT_PREV(last_element)) {
+            if (SP_OBJECT_STYLE(last_element) != NULL) {
+                SPCSSAttr *temp = sp_css_attr_from_object(last_element, SP_STYLE_FLAG_IFSET);
                 if (temp) {
-                    sp_repr_css_merge (css, temp);
-                    sp_repr_css_attr_unref (temp);
+                    sp_repr_css_merge(css, temp);
+                    sp_repr_css_attr_unref(temp);
                 }
                 break;
             }
         }
     }
-    if (!(SP_IS_TEXT (item) || SP_IS_TSPAN (item) || SP_IS_STRING (item))) {
+    if (!(SP_IS_TEXT(item) || SP_IS_TSPAN(item) || SP_IS_TREF(item) || SP_IS_STRING(item))) {
         // do not copy text properties from non-text objects, it's confusing
-        css = sp_css_attr_unset_text (css);
+        css = sp_css_attr_unset_text(css);
     }
 
     // FIXME: also transform gradient/pattern fills, by forking? NO, this must be nondestructive
-    double ex = NR::expansion(sp_item_i2doc_affine(item));
+    double ex = to_2geom(sp_item_i2doc_affine(item)).descrim();
     if (ex != 1.0) {
-        css = sp_css_attr_scale (css, ex);
+        css = sp_css_attr_scale(css, ex);
     }
 
     return css;
@@ -1003,259 +944,105 @@ take_style_from_item (SPItem *item)
 
 void sp_selection_copy()
 {
-    SPDesktop *desktop = SP_ACTIVE_DESKTOP;
-    if (desktop == NULL)
-        return;
-
-    Inkscape::Selection *selection = sp_desktop_selection(desktop);
-
-    if (tools_isactive (desktop, TOOLS_DROPPER)) {
-        sp_dropper_context_copy(desktop->event_context);
-        return; // copied color under cursor, nothing else to do
-    }
-
-    // check if something is selected
-    if (selection->isEmpty()) {
-        desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Nothing was copied."));
-        return;
-    }
-
-    const GSList *items = g_slist_copy ((GSList *) selection->itemList());
-
-    // 0. Copy text to system clipboard
-    // FIXME: for non-texts, put serialized XML as text to the clipboard;
-    //for this sp_repr_write_stream needs to be rewritten with iostream instead of FILE
-    Glib::ustring text;
-    if (tools_isactive (desktop, TOOLS_TEXT)) {
-        text = sp_text_get_selected_text(desktop->event_context);
-    }
-
-    if (text.empty()) {
-        guint texts = 0;
-        for (GSList *i = (GSList *) items; i; i = i->next) {
-            SPItem *item = SP_ITEM (i->data);
-            if (SP_IS_TEXT (item) || SP_IS_FLOWTEXT(item)) {
-                if (texts > 0) // if more than one text object is copied, separate them by spaces
-                    text += " ";
-                gchar *this_text = sp_te_get_string_multiline (item);
-                if (this_text) {
-                    text += this_text;
-                    g_free(this_text);
-                }
-                texts++;
-            }
-        }
-    }
-    if (!text.empty()) {
-        Glib::RefPtr<Gtk::Clipboard> refClipboard = Gtk::Clipboard::get();
-        refClipboard->set_text(text);
-    }
-
-    // clear old defs clipboard
-    while (defs_clipboard) {
-        Inkscape::GC::release((Inkscape::XML::Node *) defs_clipboard->data);
-        defs_clipboard = g_slist_remove (defs_clipboard, defs_clipboard->data);
-    }
-
-    // clear style clipboard
-    if (style_clipboard) {
-        sp_repr_css_attr_unref (style_clipboard);
-        style_clipboard = NULL;
-    }
-
-    //clear main clipboard
-    while (clipboard) {
-        Inkscape::GC::release((Inkscape::XML::Node *) clipboard->data);
-        clipboard = g_slist_remove(clipboard, clipboard->data);
-    }
-
-    sp_selection_copy_impl (items, &clipboard, &defs_clipboard, &style_clipboard);
-
-    if (tools_isactive (desktop, TOOLS_TEXT)) { // take style from cursor/text selection, overwriting the style just set by copy_impl
-        SPStyle *const query = sp_style_new();
-        if (sp_desktop_query_style_all (desktop, query)) {
-            SPCSSAttr *css = sp_css_attr_from_style (query, SP_STYLE_FLAG_ALWAYS);
-            if (css != NULL) {
-                // clear style clipboard
-                if (style_clipboard) {
-                    sp_repr_css_attr_unref (style_clipboard);
-                    style_clipboard = NULL;
-                }
-                //sp_repr_css_print (css);
-                style_clipboard = css;
-            }
-        }
-        g_free (query);
-    }
-
-    size_clipboard = selection->bounds();
-
-    g_slist_free ((GSList *) items);
+    Inkscape::UI::ClipboardManager *cm = Inkscape::UI::ClipboardManager::get();
+    cm->copy();
 }
 
-void sp_selection_paste(bool in_place)
+void sp_selection_paste(SPDesktop *desktop, bool in_place)
 {
-    SPDesktop *desktop = SP_ACTIVE_DESKTOP;
-
-    if (desktop == NULL) {
-        return;
-    }
-
-    SPDocument *document = sp_desktop_document(desktop);
-
-    if (Inkscape::have_viable_layer(desktop, desktop->messageStack()) == false) {
-        return;
-    }
-
-    Inkscape::Selection *selection = sp_desktop_selection(desktop);
-
-    if (tools_isactive (desktop, TOOLS_TEXT)) {
-        if (sp_text_paste_inline(desktop->event_context))
-            return; // pasted from system clipboard into text, nothing else to do
-    }
-
-    // check if something is in the clipboard
-    if (clipboard == NULL) {
-        desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Nothing on the clipboard."));
-        return;
-    }
-
-    GSList *copied = sp_selection_paste_impl(document, desktop->currentLayer(), &clipboard, &defs_clipboard);
-    // add pasted objects to selection
-    selection->setReprList((GSList const *) copied);
-    g_slist_free (copied);
-
-    if (!in_place) {
-        sp_document_ensure_up_to_date(document);
-
-        NR::Maybe<NR::Rect> sel_bbox = selection->bounds();
-        NR::Point m( desktop->point() );
-        if (sel_bbox) {
-            m -= sel_bbox->midpoint();
-        }
-
-        /* Snap the offset of the new item(s) to the grid */
-        /* FIXME: this gridsnap fiddling is a hack. */
-        Inkscape::GridSnapper &s = desktop->namedview->snap_manager.grid;
-        gdouble const curr_gridsnap = s.getDistance();
-        s.setDistance(NR_HUGE);
-        m = s.freeSnap(Inkscape::Snapper::SNAP_POINT, m, NULL).getPoint();
-        s.setDistance(curr_gridsnap);
-        sp_selection_move_relative(selection, m);
-    }
-
-    sp_document_done(document, SP_VERB_EDIT_PASTE, 
-                     _("Paste"));
+    Inkscape::UI::ClipboardManager *cm = Inkscape::UI::ClipboardManager::get();
+    if (cm->paste(in_place))
+        sp_document_done(sp_desktop_document(desktop), SP_VERB_EDIT_PASTE, _("Paste"));
 }
 
-void sp_selection_paste_style()
+void sp_selection_paste_style(SPDesktop *desktop)
 {
-    SPDesktop *desktop = SP_ACTIVE_DESKTOP;
-    if (desktop == NULL) return;
-
-    Inkscape::Selection *selection = sp_desktop_selection(desktop);
-
-    // check if something is in the clipboard
-    if (clipboard == NULL) {
-        desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Nothing on the clipboard."));
-        return;
-    }
+    Inkscape::UI::ClipboardManager *cm = Inkscape::UI::ClipboardManager::get();
+    if (cm->pasteStyle())
+        sp_document_done(sp_desktop_document(desktop), SP_VERB_EDIT_PASTE_STYLE, _("Paste style"));
+}
 
-    // check if something is selected
-    if (selection->isEmpty()) {
-        desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to paste style to."));
-        return;
-    }
 
-    paste_defs (&defs_clipboard, sp_desktop_document(desktop));
+void sp_selection_paste_livepatheffect(SPDesktop *desktop)
+{
+    Inkscape::UI::ClipboardManager *cm = Inkscape::UI::ClipboardManager::get();
+    if (cm->pastePathEffect())
+        sp_document_done(sp_desktop_document(desktop), SP_VERB_EDIT_PASTE_LIVEPATHEFFECT,
+                         _("Paste live path effect"));
+}
 
-    sp_desktop_set_style (desktop, style_clipboard);
 
-    sp_document_done(sp_desktop_document (desktop), SP_VERB_EDIT_PASTE_STYLE,
-                     _("Paste style"));
+void sp_selection_remove_livepatheffect_impl(SPItem *item)
+{
+    if ( item && SP_IS_LPE_ITEM(item) &&
+         sp_lpe_item_has_path_effect(SP_LPE_ITEM(item))) {
+        sp_lpe_item_remove_all_path_effects(SP_LPE_ITEM(item), false);
+    }
 }
 
-void sp_selection_paste_size (bool apply_x, bool apply_y)
+void sp_selection_remove_livepatheffect(SPDesktop *desktop)
 {
-    SPDesktop *desktop = SP_ACTIVE_DESKTOP;
     if (desktop == NULL) return;
 
     Inkscape::Selection *selection = sp_desktop_selection(desktop);
 
-    // check if something is in the clipboard
-    if (!size_clipboard) {
-        desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Nothing on the clipboard."));
-        return;
-    }
-
     // check if something is selected
     if (selection->isEmpty()) {
-        desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to paste size to."));
+        desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to remove live path effects from."));
         return;
     }
 
-    NR::Maybe<NR::Rect> current = selection->bounds();
-    if ( !current || current->extent(NR::X) < 1e-6 || current->extent(NR::Y) < 1e-6 ) {
-        return;
-    }
+    for ( GSList const *itemlist = selection->itemList(); itemlist != NULL; itemlist = g_slist_next(itemlist) ) {
+        SPItem *item = reinterpret_cast<SPItem*>(itemlist->data);
 
-    double scale_x = size_clipboard->extent(NR::X) / current->extent(NR::X);
-    double scale_y = size_clipboard->extent(NR::Y) / current->extent(NR::Y);
+        sp_selection_remove_livepatheffect_impl(item);
 
-    sp_selection_scale_relative (selection, current->midpoint(),
-                                 NR::scale(
-                                     apply_x? scale_x : (desktop->isToolboxButtonActive ("lock")? scale_y : 1.0),
-                                     apply_y? scale_y : (desktop->isToolboxButtonActive ("lock")? scale_x : 1.0)));
+    }
 
-    sp_document_done(sp_desktop_document (desktop), SP_VERB_EDIT_PASTE_SIZE,
-                     _("Paste size"));
+    sp_document_done(sp_desktop_document(desktop), SP_VERB_EDIT_REMOVE_LIVEPATHEFFECT,
+                     _("Remove live path effect"));
 }
 
-void sp_selection_paste_size_separately (bool apply_x, bool apply_y)
+void sp_selection_remove_filter(SPDesktop *desktop)
 {
-    SPDesktop *desktop = SP_ACTIVE_DESKTOP;
     if (desktop == NULL) return;
 
     Inkscape::Selection *selection = sp_desktop_selection(desktop);
 
-    // check if something is in the clipboard
-    if ( !size_clipboard ) {
-        desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Nothing on the clipboard."));
-        return;
-    }
-
     // check if something is selected
     if (selection->isEmpty()) {
-        desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to paste size to."));
+        desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to remove filters from."));
         return;
     }
 
-    for (GSList const *l = selection->itemList(); l != NULL; l = l->next) {
-        SPItem *item = SP_ITEM(l->data);
-
-        NR::Maybe<NR::Rect> current = sp_item_bbox_desktop(item);
-        if ( !current || current->extent(NR::X) < 1e-6 || current->extent(NR::Y) < 1e-6 ) {
-            continue;
-        }
+    SPCSSAttr *css = sp_repr_css_attr_new();
+    sp_repr_css_unset_property(css, "filter");
+    sp_desktop_set_style(desktop, css);
+    sp_repr_css_attr_unref(css);
 
-        double scale_x = size_clipboard->extent(NR::X) / current->extent(NR::X);
-        double scale_y = size_clipboard->extent(NR::Y) / current->extent(NR::Y);
+    sp_document_done(sp_desktop_document(desktop), SP_VERB_EDIT_REMOVE_FILTER,
+                     _("Remove filter"));
+}
 
-        sp_item_scale_rel (item,
-                                 NR::scale(
-                                     apply_x? scale_x : (desktop->isToolboxButtonActive ("lock")? scale_y : 1.0),
-                                     apply_y? scale_y : (desktop->isToolboxButtonActive ("lock")? scale_x : 1.0)));
 
-    }
-
-    sp_document_done(sp_desktop_document (desktop), SP_VERB_EDIT_PASTE_SIZE_SEPARATELY,
-                     _("Paste size separately"));
+void sp_selection_paste_size(SPDesktop *desktop, bool apply_x, bool apply_y)
+{
+    Inkscape::UI::ClipboardManager *cm = Inkscape::UI::ClipboardManager::get();
+    if (cm->pasteSize(false, apply_x, apply_y))
+        sp_document_done(sp_desktop_document(desktop), SP_VERB_EDIT_PASTE_SIZE,
+                         _("Paste size"));
 }
 
-void sp_selection_to_next_layer ()
+void sp_selection_paste_size_separately(SPDesktop *desktop, bool apply_x, bool apply_y)
 {
-    SPDesktop *dt = SP_ACTIVE_DESKTOP;
+    Inkscape::UI::ClipboardManager *cm = Inkscape::UI::ClipboardManager::get();
+    if (cm->pasteSize(true, apply_x, apply_y))
+        sp_document_done(sp_desktop_document(desktop), SP_VERB_EDIT_PASTE_SIZE_SEPARATELY,
+                         _("Paste size separately"));
+}
 
+void sp_selection_to_next_layer(SPDesktop *dt, bool suppressDone)
+{
     Inkscape::Selection *selection = sp_desktop_selection(dt);
 
     // check if something is selected
@@ -1264,28 +1051,30 @@ void sp_selection_to_next_layer ()
         return;
     }
 
-    const GSList *items = g_slist_copy ((GSList *) selection->itemList());
+    GSList const *items = g_slist_copy((GSList *) selection->itemList());
 
     bool no_more = false; // Set to true, if no more layers above
     SPObject *next=Inkscape::next_layer(dt->currentRoot(), dt->currentLayer());
     if (next) {
         GSList *temp_clip = NULL;
-        sp_selection_copy_impl (items, &temp_clip, NULL, NULL); // we're in the same doc, so no need to copy defs
-        sp_selection_delete_impl (items);
+        sp_selection_copy_impl(items, &temp_clip, sp_document_repr_doc(dt->doc()));
+        sp_selection_delete_impl(items, false, false);
         next=Inkscape::next_layer(dt->currentRoot(), dt->currentLayer()); // Fixes bug 1482973: crash while moving layers
         GSList *copied;
-        if(next) {
-            copied = sp_selection_paste_impl (sp_desktop_document (dt), next, &temp_clip, NULL);
+        if (next) {
+            copied = sp_selection_paste_impl(sp_desktop_document(dt), next, &temp_clip);
         } else {
-            copied = sp_selection_paste_impl (sp_desktop_document (dt), dt->currentLayer(), &temp_clip, NULL);
+            copied = sp_selection_paste_impl(sp_desktop_document(dt), dt->currentLayer(), &temp_clip);
             no_more = true;
         }
         selection->setReprList((GSList const *) copied);
-        g_slist_free (copied);
-        if (temp_clip) g_slist_free (temp_clip);
+        g_slist_free(copied);
+        if (temp_clip) g_slist_free(temp_clip);
         if (next) dt->setCurrentLayer(next);
-        sp_document_done(sp_desktop_document (dt), SP_VERB_LAYER_MOVE_TO_NEXT, 
-                         _("Raise to next layer"));
+        if ( !suppressDone ) {
+            sp_document_done(sp_desktop_document(dt), SP_VERB_LAYER_MOVE_TO_NEXT,
+                             _("Raise to next layer"));
+        }
     } else {
         no_more = true;
     }
@@ -1294,13 +1083,11 @@ void sp_selection_to_next_layer ()
         dt->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("No more layers above."));
     }
 
-    g_slist_free ((GSList *) items);
+    g_slist_free((GSList *) items);
 }
 
-void sp_selection_to_prev_layer ()
+void sp_selection_to_prev_layer(SPDesktop *dt, bool suppressDone)
 {
-    SPDesktop *dt = SP_ACTIVE_DESKTOP;
-
     Inkscape::Selection *selection = sp_desktop_selection(dt);
 
     // check if something is selected
@@ -1309,28 +1096,30 @@ void sp_selection_to_prev_layer ()
         return;
     }
 
-    const GSList *items = g_slist_copy ((GSList *) selection->itemList());
+    GSList const *items = g_slist_copy((GSList *) selection->itemList());
 
     bool no_more = false; // Set to true, if no more layers below
     SPObject *next=Inkscape::previous_layer(dt->currentRoot(), dt->currentLayer());
     if (next) {
         GSList *temp_clip = NULL;
-        sp_selection_copy_impl (items, &temp_clip, NULL, NULL); // we're in the same doc, so no need to copy defs
-        sp_selection_delete_impl (items);
+        sp_selection_copy_impl(items, &temp_clip, sp_document_repr_doc(dt->doc())); // we're in the same doc, so no need to copy defs
+        sp_selection_delete_impl(items, false, false);
         next=Inkscape::previous_layer(dt->currentRoot(), dt->currentLayer()); // Fixes bug 1482973: crash while moving layers
         GSList *copied;
-        if(next) {
-            copied = sp_selection_paste_impl (sp_desktop_document (dt), next, &temp_clip, NULL);
+        if (next) {
+            copied = sp_selection_paste_impl(sp_desktop_document(dt), next, &temp_clip);
         } else {
-            copied = sp_selection_paste_impl (sp_desktop_document (dt), dt->currentLayer(), &temp_clip, NULL);
+            copied = sp_selection_paste_impl(sp_desktop_document(dt), dt->currentLayer(), &temp_clip);
             no_more = true;
         }
         selection->setReprList((GSList const *) copied);
-        g_slist_free (copied);
-        if (temp_clip) g_slist_free (temp_clip);
+        g_slist_free(copied);
+        if (temp_clip) g_slist_free(temp_clip);
         if (next) dt->setCurrentLayer(next);
-        sp_document_done(sp_desktop_document (dt), SP_VERB_LAYER_MOVE_TO_PREV,
-                         _("Lower to previous layer"));
+        if ( !suppressDone ) {
+            sp_document_done(sp_desktop_document(dt), SP_VERB_LAYER_MOVE_TO_PREV,
+                             _("Lower to previous layer"));
+        }
     } else {
         no_more = true;
     }
@@ -1339,36 +1128,44 @@ void sp_selection_to_prev_layer ()
         dt->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("No more layers below."));
     }
 
-    g_slist_free ((GSList *) items);
+    g_slist_free((GSList *) items);
 }
 
 bool
-selection_contains_original (SPItem *item, Inkscape::Selection *selection)
+selection_contains_original(SPItem *item, Inkscape::Selection *selection)
 {
     bool contains_original = false;
+
     bool is_use = SP_IS_USE(item);
     SPItem *item_use = item;
     SPItem *item_use_first = item;
     while (is_use && item_use && !contains_original)
     {
-        item_use = sp_use_get_original (SP_USE(item_use));
+        item_use = sp_use_get_original(SP_USE(item_use));
         contains_original |= selection->includes(item_use);
         if (item_use == item_use_first)
             break;
         is_use = SP_IS_USE(item_use);
-    }   
+    }
+
+    // If it's a tref, check whether the object containing the character
+    // data is part of the selection
+    if (!contains_original && SP_IS_TREF(item)) {
+        contains_original = selection->includes(SP_TREF(item)->getObjectReferredTo());
+    }
+
     return contains_original;
 }
 
 
 bool
-selection_contains_both_clone_and_original (Inkscape::Selection *selection)
+selection_contains_both_clone_and_original(Inkscape::Selection *selection)
 {
     bool clone_with_original = false;
     for (GSList const *l = selection->itemList(); l != NULL; l = l->next) {
         SPItem *item = SP_ITEM(l->data);
         clone_with_original |= selection_contains_original(item, selection);
-        if (clone_with_original) 
+        if (clone_with_original)
             break;
     }
     return clone_with_original;
@@ -1381,7 +1178,7 @@ value of set_i2d==false is only used by seltrans when it's dragging objects live
 that case, items are already in the new position, but the repr is in the old, and this function
 then simply updates the repr from item->transform.
  */
-void sp_selection_apply_affine(Inkscape::Selection *selection, NR::Matrix const &affine, bool set_i2d)
+void sp_selection_apply_affine(Inkscape::Selection *selection, Geom::Matrix const &affine, bool set_i2d, bool compensate)
 {
     if (selection->isEmpty())
         return;
@@ -1389,53 +1186,54 @@ void sp_selection_apply_affine(Inkscape::Selection *selection, NR::Matrix const
     for (GSList const *l = selection->itemList(); l != NULL; l = l->next) {
         SPItem *item = SP_ITEM(l->data);
 
-        NR::Point old_center(0,0);
+        Geom::Point old_center(0,0);
         if (set_i2d && item->isCenterSet())
             old_center = item->getCenter();
 
 #if 0 /* Re-enable this once persistent guides have a graphical indication.
-        At the time of writing, this is the only place to re-enable. */
+         At the time of writing, this is the only place to re-enable. */
         sp_item_update_cns(*item, selection->desktop());
 #endif
 
         // we're moving both a clone and its original or any ancestor in clone chain?
         bool transform_clone_with_original = selection_contains_original(item, selection);
         // ...both a text-on-path and its path?
-        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)))) ));
+        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)))) ));
         // ...both a flowtext and its frame?
-        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)
+        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)
         // ...both an offset and its source?
-        bool transform_offset_with_source = (SP_IS_OFFSET(item) && SP_OFFSET (item)->sourceHref) && selection->includes( sp_offset_get_source (SP_OFFSET(item)) );
-       
+        bool transform_offset_with_source = (SP_IS_OFFSET(item) && SP_OFFSET(item)->sourceHref) && selection->includes( sp_offset_get_source(SP_OFFSET(item)) );
+
         // If we're moving a connector, we want to detach it
         // from shapes that aren't part of the selection, but
         // leave it attached if they are
         if (cc_item_is_connector(item)) {
             SPItem *attItem[2];
             SP_PATH(item)->connEndPair.getAttachedItems(attItem);
-            
+
             for (int n = 0; n < 2; ++n) {
                 if (!selection->includes(attItem[n])) {
                     sp_conn_end_detach(item, n);
                 }
             }
         }
-        
+
         // "clones are unmoved when original is moved" preference
-        int compensation = prefs_get_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
+        Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+        int compensation = prefs->getInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_UNMOVED);
         bool prefs_unmoved = (compensation == SP_CLONE_COMPENSATION_UNMOVED);
         bool prefs_parallel = (compensation == SP_CLONE_COMPENSATION_PARALLEL);
 
-       // If this is a clone and it's selected along with its original, do not move it; it will feel the
-       // transform of its original and respond to it itself. Without this, a clone is doubly
-       // transformed, very unintuitive.
-      // Same for textpath if we are also doing ANY transform to its path: do not touch textpath,
-      // letters cannot be squeezed or rotated anyway, they only refill the changed path.
-      // Same for linked offset if we are also moving its source: do not move it.
+        /* If this is a clone and it's selected along with its original, do not move it;
+         * it will feel the transform of its original and respond to it itself.
+         * Without this, a clone is doubly transformed, very unintuitive.
+         *
+         * Same for textpath if we are also doing ANY transform to its path: do not touch textpath,
+         * letters cannot be squeezed or rotated anyway, they only refill the changed path.
+         * Same for linked offset if we are also moving its source: do not move it. */
         if (transform_textpath_with_path || transform_offset_with_source) {
-               // restore item->transform field from the repr, in case it was changed by seltrans
-            sp_object_read_attr (SP_OBJECT (item), "transform");
-
+            // Restore item->transform field from the repr, in case it was changed by seltrans.
+            sp_object_read_attr(SP_OBJECT(item), "transform");
         } else if (transform_flowtext_with_frame) {
             // apply the inverse of the region's transform to the <use> so that the flow remains
             // the same (even though the output itself gets transformed)
@@ -1444,7 +1242,7 @@ void sp_selection_apply_affine(Inkscape::Selection *selection, NR::Matrix const
                     continue;
                 for (SPObject *use = region->firstChild() ; use ; use = SP_OBJECT_NEXT(use)) {
                     if (!SP_IS_USE(use)) continue;
-                    sp_item_write_transform(SP_USE(use), SP_OBJECT_REPR(use), item->transform.inverse(), NULL);
+                    sp_item_write_transform(SP_USE(use), SP_OBJECT_REPR(use), item->transform.inverse(), NULL, compensate);
                 }
             }
         } else if (transform_clone_with_original) {
@@ -1453,56 +1251,55 @@ void sp_selection_apply_affine(Inkscape::Selection *selection, NR::Matrix const
             // transform and its move compensation are both cancelled out.
 
             // restore item->transform field from the repr, in case it was changed by seltrans
-            sp_object_read_attr (SP_OBJECT (item), "transform");
+            sp_object_read_attr(SP_OBJECT(item), "transform");
 
             // calculate the matrix we need to apply to the clone to cancel its induced transform from its original
-            NR::Matrix parent_transform = sp_item_i2root_affine(SP_ITEM(SP_OBJECT_PARENT (item)));
-            NR::Matrix t = parent_transform * matrix_to_desktop (matrix_from_desktop (affine, item), item) * parent_transform.inverse();
-            NR::Matrix t_inv =parent_transform * matrix_to_desktop (matrix_from_desktop (affine.inverse(), item), item) * parent_transform.inverse();
-            NR::Matrix result = t_inv * item->transform * t;
+            Geom::Matrix parent2dt = sp_item_i2d_affine(SP_ITEM(SP_OBJECT_PARENT(item)));
+            Geom::Matrix t = parent2dt * affine * parent2dt.inverse();
+            Geom::Matrix t_inv = t.inverse();
+            Geom::Matrix result = t_inv * item->transform * t;
 
-            if ((prefs_parallel || prefs_unmoved) && affine.is_translation()) {
+            if ((prefs_parallel || prefs_unmoved) && affine.isTranslation()) {
                 // we need to cancel out the move compensation, too
 
                 // find out the clone move, same as in sp_use_move_compensate
-                NR::Matrix parent = sp_use_get_parent_transform (SP_USE(item));
-                NR::Matrix clone_move = parent.inverse() * t * parent;
+                Geom::Matrix parent = sp_use_get_parent_transform(SP_USE(item));
+                Geom::Matrix clone_move = parent.inverse() * t * parent;
 
                 if (prefs_parallel) {
-                    NR::Matrix move = result * clone_move * t_inv;
-                    sp_item_write_transform(item, SP_OBJECT_REPR(item), move, &move);
+                    Geom::Matrix move = result * clone_move * t_inv;
+                    sp_item_write_transform(item, SP_OBJECT_REPR(item), move, &move, compensate);
 
                 } else if (prefs_unmoved) {
                     //if (SP_IS_USE(sp_use_get_original(SP_USE(item))))
-                    //    clone_move = NR::identity();
-                    NR::Matrix move = result * clone_move;
-                    sp_item_write_transform(item, SP_OBJECT_REPR(item), move, &t);
+                    //    clone_move = Geom::identity();
+                    Geom::Matrix move = result * clone_move;
+                    sp_item_write_transform(item, SP_OBJECT_REPR(item), move, &t, compensate);
                 }
 
             } else {
                 // just apply the result
-                sp_item_write_transform(item, SP_OBJECT_REPR(item), result, &t);
+                sp_item_write_transform(item, SP_OBJECT_REPR(item), result, &t, compensate);
             }
 
         } else {
             if (set_i2d) {
-                sp_item_set_i2d_affine(item, sp_item_i2d_affine(item) * affine);
+                sp_item_set_i2d_affine(item, sp_item_i2d_affine(item) * (Geom::Matrix)affine);
             }
-            sp_item_write_transform(item, SP_OBJECT_REPR(item), item->transform, NULL);
+            sp_item_write_transform(item, SP_OBJECT_REPR(item), item->transform, NULL, compensate);
         }
 
         // if we're moving the actual object, not just updating the repr, we can transform the
         // center by the same matrix (only necessary for non-translations)
-        if (set_i2d && item->isCenterSet() && !affine.is_translation()) {
+        if (set_i2d && item->isCenterSet() && !(affine.isTranslation() || affine.isIdentity())) {
             item->setCenter(old_center * affine);
             SP_OBJECT(item)->updateRepr();
         }
     }
 }
 
-void sp_selection_remove_transform()
+void sp_selection_remove_transform(SPDesktop *desktop)
 {
-    SPDesktop *desktop = SP_ACTIVE_DESKTOP;
     if (desktop == NULL)
         return;
 
@@ -1510,11 +1307,11 @@ void sp_selection_remove_transform()
 
     GSList const *l = (GSList *) selection->reprList();
     while (l != NULL) {
-        sp_repr_set_attr((Inkscape::XML::Node*)l->data, "transform", NULL);
+        ((Inkscape::XML::Node*)l->data)->setAttribute("transform", NULL, false);
         l = l->next;
     }
 
-    sp_document_done(sp_desktop_document(desktop), SP_VERB_OBJECT_FLATTEN, 
+    sp_document_done(sp_desktop_document(desktop), SP_VERB_OBJECT_FLATTEN,
                      _("Remove transform"));
 }
 
@@ -1526,133 +1323,99 @@ sp_selection_scale_absolute(Inkscape::Selection *selection,
     if (selection->isEmpty())
         return;
 
-    NR::Maybe<NR::Rect> const bbox(selection->bounds());
+    Geom::OptRect const bbox(selection->bounds());
     if ( !bbox ) {
         return;
     }
 
-    NR::translate const p2o(-bbox->min());
+    Geom::Translate const p2o(-bbox->min());
 
-    NR::scale const newSize(x1 - x0,
-                            y1 - y0);
-    NR::scale const scale( newSize / NR::scale(bbox->dimensions()) );
-    NR::translate const o2n(x0, y0);
-    NR::Matrix const final( p2o * scale * o2n );
+    Geom::Scale const newSize(x1 - x0,
+                              y1 - y0);
+    Geom::Scale const scale( newSize * Geom::Scale(bbox->dimensions()).inverse() );
+    Geom::Translate const o2n(x0, y0);
+    Geom::Matrix const final( p2o * scale * o2n );
 
     sp_selection_apply_affine(selection, final);
 }
 
 
-void sp_selection_scale_relative(Inkscape::Selection *selection, NR::Point const &align, NR::scale const &scale)
+void sp_selection_scale_relative(Inkscape::Selection *selection, Geom::Point const &align, Geom::Scale const &scale)
 {
     if (selection->isEmpty())
         return;
 
-    NR::Maybe<NR::Rect> const bbox(selection->bounds());
+    Geom::OptRect const bbox(selection->bounds());
 
     if ( !bbox ) {
         return;
     }
 
     // FIXME: ARBITRARY LIMIT: don't try to scale above 1 Mpx, it won't display properly and will crash sooner or later anyway
-    if ( bbox->extent(NR::X) * scale[NR::X] > 1e6  ||
-         bbox->extent(NR::Y) * scale[NR::Y] > 1e6 )
+    if ( bbox->dimensions()[Geom::X] * scale[Geom::X] > 1e6  ||
+         bbox->dimensions()[Geom::Y] * scale[Geom::Y] > 1e6 )
     {
         return;
     }
 
-    NR::translate const n2d(-align);
-    NR::translate const d2n(align);
-    NR::Matrix const final( n2d * scale * d2n );
+    Geom::Translate const n2d(-align);
+    Geom::Translate const d2n(align);
+    Geom::Matrix const final( n2d * scale * d2n );
     sp_selection_apply_affine(selection, final);
 }
 
 void
-sp_selection_rotate_relative(Inkscape::Selection *selection, NR::Point const &center, gdouble const angle_degrees)
+sp_selection_rotate_relative(Inkscape::Selection *selection, Geom::Point const &center, gdouble const angle_degrees)
 {
-    NR::translate const d2n(center);
-    NR::translate const n2d(-center);
-    NR::rotate const rotate(rotate_degrees(angle_degrees));
-    NR::Matrix const final( NR::Matrix(n2d) * rotate * d2n );
+    Geom::Translate const d2n(center);
+    Geom::Translate const n2d(-center);
+    Geom::Rotate const rotate(Geom::Rotate::from_degrees(angle_degrees));
+    Geom::Matrix const final( Geom::Matrix(n2d) * rotate * d2n );
     sp_selection_apply_affine(selection, final);
 }
 
 void
-sp_selection_skew_relative(Inkscape::Selection *selection, NR::Point const &align, double dx, double dy)
-{
-    NR::translate const d2n(align);
-    NR::translate const n2d(-align);
-    NR::Matrix const skew(1, dy,
-                          dx, 1,
-                          0, 0);
-    NR::Matrix const final( n2d * skew * d2n );
+sp_selection_skew_relative(Inkscape::Selection *selection, Geom::Point const &align, double dx, double dy)
+{
+    Geom::Translate const d2n(align);
+    Geom::Translate const n2d(-align);
+    Geom::Matrix const skew(1, dy,
+                            dx, 1,
+                            0, 0);
+    Geom::Matrix const final( n2d * skew * d2n );
     sp_selection_apply_affine(selection, final);
 }
 
-void sp_selection_move_relative(Inkscape::Selection *selection, NR::Point const &move)
+void sp_selection_move_relative(Inkscape::Selection *selection, Geom::Point const &move, bool compensate)
 {
-    sp_selection_apply_affine(selection, NR::Matrix(NR::translate(move)));
+    sp_selection_apply_affine(selection, Geom::Matrix(Geom::Translate(move)), true, compensate);
 }
 
 void sp_selection_move_relative(Inkscape::Selection *selection, double dx, double dy)
 {
-    sp_selection_apply_affine(selection, NR::Matrix(NR::translate(dx, dy)));
-}
-
-
-/**
- * \brief sp_selection_rotate_90
- *
- * This function rotates selected objects 90 degrees clockwise.
- *
- */
-
-void sp_selection_rotate_90_cw()
-{
-    SPDesktop *desktop = SP_ACTIVE_DESKTOP;
-
-    Inkscape::Selection *selection = sp_desktop_selection(desktop);
-
-    if (selection->isEmpty())
-        return;
-
-    GSList const *l = selection->itemList();
-    NR::rotate const rot_neg_90(NR::Point(0, -1));
-    for (GSList const *l2 = l ; l2 != NULL ; l2 = l2->next) {
-        SPItem *item = SP_ITEM(l2->data);
-        sp_item_rotate_rel(item, rot_neg_90);
-    }
-
-    sp_document_done(sp_desktop_document(desktop), SP_VERB_OBJECT_ROTATE_90_CCW, 
-                     _("Rotate 90&#176; CW"));
+    sp_selection_apply_affine(selection, Geom::Matrix(Geom::Translate(dx, dy)));
 }
 
-
 /**
- * \brief sp_selection_rotate_90_ccw
- *
- * This function rotates selected objects 90 degrees counter-clockwise.
- *
+ * @brief Rotates selected objects 90 degrees, either clock-wise or counter-clockwise, depending on the value of ccw
  */
-
-void sp_selection_rotate_90_ccw()
+void sp_selection_rotate_90(SPDesktop *desktop, bool ccw)
 {
-    SPDesktop *desktop = SP_ACTIVE_DESKTOP;
-
     Inkscape::Selection *selection = sp_desktop_selection(desktop);
 
     if (selection->isEmpty())
         return;
 
     GSList const *l = selection->itemList();
-    NR::rotate const rot_neg_90(NR::Point(0, 1));
+    Geom::Rotate const rot_90(Geom::Point(0, ccw ? 1 : -1)); // pos. or neg. rotation, depending on the value of ccw
     for (GSList const *l2 = l ; l2 != NULL ; l2 = l2->next) {
         SPItem *item = SP_ITEM(l2->data);
-        sp_item_rotate_rel(item, rot_neg_90);
+        sp_item_rotate_rel(item, rot_90);
     }
 
-    sp_document_done(sp_desktop_document(desktop), SP_VERB_OBJECT_ROTATE_90_CW,
-                     _("Rotate 90&#176; CCW"));
+    sp_document_done(sp_desktop_document(desktop),
+                     ccw ? SP_VERB_OBJECT_ROTATE_90_CCW : SP_VERB_OBJECT_ROTATE_90_CW,
+                     ccw ? _("Rotate 90&#176; CCW") : _("Rotate 90&#176; CW"));
 }
 
 void
@@ -1661,7 +1424,7 @@ sp_selection_rotate(Inkscape::Selection *selection, gdouble const angle_degrees)
     if (selection->isEmpty())
         return;
 
-    NR::Maybe<NR::Point> center = selection->center();
+    boost::optional<Geom::Point> center = selection->center();
     if (!center) {
         return;
     }
@@ -1671,11 +1434,26 @@ sp_selection_rotate(Inkscape::Selection *selection, gdouble const angle_degrees)
     sp_document_maybe_done(sp_desktop_document(selection->desktop()),
                            ( ( angle_degrees > 0 )
                              ? "selector:rotate:ccw"
-                             : "selector:rotate:cw" ), 
-                           SP_VERB_CONTEXT_SELECT, 
+                             : "selector:rotate:cw" ),
+                           SP_VERB_CONTEXT_SELECT,
                            _("Rotate"));
 }
 
+// helper function:
+static
+Geom::Point
+cornerFarthestFrom(Geom::Rect const &r, Geom::Point const &p){
+    Geom::Point m = r.midpoint();
+    unsigned i = 0;
+    if (p[X] < m[X]) {
+        i = 1;
+    }
+    if (p[Y] < m[Y]) {
+        i = 3 - i;
+    }
+    return r.corner(i);
+}
+
 /**
 \param  angle   the angle in "angular pixels", i.e. how many visible pixels must move the outermost point of the rotated object
 */
@@ -1685,8 +1463,8 @@ sp_selection_rotate_screen(Inkscape::Selection *selection, gdouble angle)
     if (selection->isEmpty())
         return;
 
-    NR::Maybe<NR::Rect> const bbox(selection->bounds());
-    NR::Maybe<NR::Point> center = selection->center();
+    Geom::OptRect const bbox(selection->bounds());
+    boost::optional<Geom::Point> center = selection->center();
 
     if ( !bbox || !center ) {
         return;
@@ -1694,7 +1472,7 @@ sp_selection_rotate_screen(Inkscape::Selection *selection, gdouble angle)
 
     gdouble const zoom = selection->desktop()->current_zoom();
     gdouble const zmove = angle / zoom;
-    gdouble const r = NR::L2(bbox->max() - *center);
+    gdouble const r = Geom::L2(cornerFarthestFrom(*bbox, *center) - *center);
 
     gdouble const zangle = 180 * atan2(zmove, r) / M_PI;
 
@@ -1704,7 +1482,7 @@ sp_selection_rotate_screen(Inkscape::Selection *selection, gdouble angle)
                            ( (angle > 0)
                              ? "selector:rotate:ccw"
                              : "selector:rotate:cw" ),
-                           SP_VERB_CONTEXT_SELECT, 
+                           SP_VERB_CONTEXT_SELECT,
                            _("Rotate by pixels"));
 }
 
@@ -1714,12 +1492,12 @@ sp_selection_scale(Inkscape::Selection *selection, gdouble grow)
     if (selection->isEmpty())
         return;
 
-    NR::Maybe<NR::Rect> const bbox(selection->bounds());
+    Geom::OptRect const bbox(selection->bounds());
     if (!bbox) {
         return;
     }
 
-    NR::Point const center(bbox->midpoint());
+    Geom::Point const center(bbox->midpoint());
 
     // you can't scale "do nizhe pola" (below zero)
     double const max_len = bbox->maxExtent();
@@ -1728,7 +1506,7 @@ sp_selection_scale(Inkscape::Selection *selection, gdouble grow)
     }
 
     double const times = 1.0 + grow / max_len;
-    sp_selection_scale_relative(selection, center, NR::scale(times, times));
+    sp_selection_scale_relative(selection, center, Geom::Scale(times, times));
 
     sp_document_maybe_done(sp_desktop_document(selection->desktop()),
                            ( (grow > 0)
@@ -1751,22 +1529,21 @@ sp_selection_scale_times(Inkscape::Selection *selection, gdouble times)
     if (selection->isEmpty())
         return;
 
-    NR::Maybe<NR::Rect> sel_bbox = selection->bounds();
+    Geom::OptRect sel_bbox = selection->bounds();
 
     if (!sel_bbox) {
         return;
     }
 
-    NR::Point const center(sel_bbox->midpoint());
-    sp_selection_scale_relative(selection, center, NR::scale(times, times));
-    sp_document_done(sp_desktop_document(selection->desktop()), SP_VERB_CONTEXT_SELECT, 
+    Geom::Point const center(sel_bbox->midpoint());
+    sp_selection_scale_relative(selection, center, Geom::Scale(times, times));
+    sp_document_done(sp_desktop_document(selection->desktop()), SP_VERB_CONTEXT_SELECT,
                      _("Scale by whole factor"));
 }
 
 void
-sp_selection_move(gdouble dx, gdouble dy)
+sp_selection_move(SPDesktop *desktop, gdouble dx, gdouble dy)
 {
-    SPDesktop *desktop = SP_ACTIVE_DESKTOP;
     Inkscape::Selection *selection = sp_desktop_selection(desktop);
     if (selection->isEmpty()) {
         return;
@@ -1775,22 +1552,20 @@ sp_selection_move(gdouble dx, gdouble dy)
     sp_selection_move_relative(selection, dx, dy);
 
     if (dx == 0) {
-        sp_document_maybe_done(sp_desktop_document(desktop), "selector:move:vertical", SP_VERB_CONTEXT_SELECT, 
+        sp_document_maybe_done(sp_desktop_document(desktop), "selector:move:vertical", SP_VERB_CONTEXT_SELECT,
                                _("Move vertically"));
     } else if (dy == 0) {
-        sp_document_maybe_done(sp_desktop_document(desktop), "selector:move:horizontal", SP_VERB_CONTEXT_SELECT, 
+        sp_document_maybe_done(sp_desktop_document(desktop), "selector:move:horizontal", SP_VERB_CONTEXT_SELECT,
                                _("Move horizontally"));
     } else {
-        sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_SELECT, 
+        sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_SELECT,
                          _("Move"));
     }
 }
 
 void
-sp_selection_move_screen(gdouble dx, gdouble dy)
+sp_selection_move_screen(SPDesktop *desktop, gdouble dx, gdouble dy)
 {
-    SPDesktop *desktop = SP_ACTIVE_DESKTOP;
-
     Inkscape::Selection *selection = sp_desktop_selection(desktop);
     if (selection->isEmpty()) {
         return;
@@ -1803,13 +1578,13 @@ sp_selection_move_screen(gdouble dx, gdouble dy)
     sp_selection_move_relative(selection, zdx, zdy);
 
     if (dx == 0) {
-        sp_document_maybe_done(sp_desktop_document(desktop), "selector:move:vertical", SP_VERB_CONTEXT_SELECT, 
+        sp_document_maybe_done(sp_desktop_document(desktop), "selector:move:vertical", SP_VERB_CONTEXT_SELECT,
                                _("Move vertically by pixels"));
     } else if (dy == 0) {
-        sp_document_maybe_done(sp_desktop_document(desktop), "selector:move:horizontal", SP_VERB_CONTEXT_SELECT, 
+        sp_document_maybe_done(sp_desktop_document(desktop), "selector:move:horizontal", SP_VERB_CONTEXT_SELECT,
                                _("Move horizontally by pixels"));
     } else {
-        sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_SELECT, 
+        sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_SELECT,
                          _("Move"));
     }
 }
@@ -1829,7 +1604,7 @@ struct Forward {
 
     static Iterator children(SPObject *o) { return sp_object_first_child(o); }
     static Iterator siblings_after(SPObject *o) { return SP_OBJECT_NEXT(o); }
-    static void dispose(Iterator i) {}
+    static void dispose(Iterator /*i*/) {}
 
     static SPObject *object(Iterator i) { return i; }
     static Iterator next(Iterator i) { return SP_OBJECT_NEXT(i); }
@@ -1867,15 +1642,15 @@ private:
 }
 
 void
-sp_selection_item_next(void)
+sp_selection_item_next(SPDesktop *desktop)
 {
-    SPDesktop *desktop = SP_ACTIVE_DESKTOP;
     g_return_if_fail(desktop != NULL);
     Inkscape::Selection *selection = sp_desktop_selection(desktop);
 
-    PrefsSelectionContext inlayer = (PrefsSelectionContext)prefs_get_int_attribute ("options.kbselection", "inlayer", PREFS_SELECTION_LAYER);
-    bool onlyvisible = prefs_get_int_attribute ("options.kbselection", "onlyvisible", 1);
-    bool onlysensitive = prefs_get_int_attribute ("options.kbselection", "onlysensitive", 1);
+    Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+    PrefsSelectionContext inlayer = (PrefsSelectionContext)prefs->getInt("/options/kbselection/inlayer", PREFS_SELECTION_LAYER);
+    bool onlyvisible = prefs->getBool("/options/kbselection/onlyvisible", true);
+    bool onlysensitive = prefs->getBool("/options/kbselection/onlysensitive", true);
 
     SPObject *root;
     if (PREFS_SELECTION_ALL != inlayer) {
@@ -1895,17 +1670,17 @@ sp_selection_item_next(void)
 }
 
 void
-sp_selection_item_prev(void)
+sp_selection_item_prev(SPDesktop *desktop)
 {
-    SPDocument *document = SP_ACTIVE_DOCUMENT;
-    SPDesktop *desktop = SP_ACTIVE_DESKTOP;
+    SPDocument *document = sp_desktop_document(desktop);
     g_return_if_fail(document != NULL);
     g_return_if_fail(desktop != NULL);
     Inkscape::Selection *selection = sp_desktop_selection(desktop);
 
-    PrefsSelectionContext inlayer = (PrefsSelectionContext)prefs_get_int_attribute ("options.kbselection", "inlayer", PREFS_SELECTION_LAYER);
-    bool onlyvisible = prefs_get_int_attribute ("options.kbselection", "onlyvisible", 1);
-    bool onlysensitive = prefs_get_int_attribute ("options.kbselection", "onlysensitive", 1);
+    Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+    PrefsSelectionContext inlayer = (PrefsSelectionContext) prefs->getInt("/options/kbselection/inlayer", PREFS_SELECTION_LAYER);
+    bool onlyvisible = prefs->getBool("/options/kbselection/onlyvisible", true);
+    bool onlysensitive = prefs->getBool("/options/kbselection/onlysensitive", true);
 
     SPObject *root;
     if (PREFS_SELECTION_ALL != inlayer) {
@@ -1924,6 +1699,73 @@ sp_selection_item_prev(void)
     }
 }
 
+void sp_selection_next_patheffect_param(SPDesktop * dt)
+{
+    if (!dt) return;
+
+    Inkscape::Selection *selection = sp_desktop_selection(dt);
+    if ( selection && !selection->isEmpty() ) {
+        SPItem *item = selection->singleItem();
+        if ( item && SP_IS_SHAPE(item)) {
+            if (sp_lpe_item_has_path_effect(SP_LPE_ITEM(item))) {
+                sp_lpe_item_edit_next_param_oncanvas(SP_LPE_ITEM(item), dt);
+            } else {
+                dt->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("The selection has no applied path effect."));
+            }
+        }
+    }
+}
+
+void sp_selection_edit_clip_or_mask(SPDesktop * dt, bool clip)
+{
+    if (!dt) return;
+
+    Inkscape::Selection *selection = sp_desktop_selection(dt);
+    if ( selection && !selection->isEmpty() ) {
+        SPItem *item = selection->singleItem();
+        if ( item ) {
+            SPObject *obj = NULL;
+            if (clip)
+                obj = item->clip_ref ? SP_OBJECT(item->clip_ref->getObject()) : NULL;
+            else
+                obj = item->mask_ref ? SP_OBJECT(item->mask_ref->getObject()) : NULL;
+
+            if (obj) {
+                // obj is a group object, the children are the actual clippers
+                for ( SPObject *child = obj->children ; child ; child = child->next ) {
+                    if ( SP_IS_ITEM(child) ) {
+                        // If not already in nodecontext, goto it!
+                        if (!tools_isactive(dt, TOOLS_NODES)) {
+                            tools_switch(dt, TOOLS_NODES);
+                        }
+
+                        ShapeEditor * shape_editor = dt->event_context->shape_editor;
+                        // TODO: should we set the item for nodepath or knotholder or both? seems to work with both.
+                        shape_editor->set_item(SP_ITEM(child), SH_NODEPATH);
+                        shape_editor->set_item(SP_ITEM(child), SH_KNOTHOLDER);
+                        Inkscape::NodePath::Path *np = shape_editor->get_nodepath();
+                        if (np) {
+                            // take colors from prefs (same as used in outline mode)
+                            Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+                            np->helperpath_rgba = clip ?
+                                prefs->getInt("/options/wireframecolors/clips", 0x00ff00ff) :
+                                prefs->getInt("/options/wireframecolors/masks", 0x0000ffff);
+                            np->helperpath_width = 1.0;
+                            sp_nodepath_show_helperpath(np, true);
+                        }
+                        break; // break out of for loop after 1st encountered item
+                    }
+                }
+            } else if (clip) {
+                dt->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("The selection has no applied clip path."));
+            } else {
+                dt->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("The selection has no applied mask."));
+            }
+        }
+    }
+}
+
+
 namespace {
 
 template <typename D>
@@ -2010,15 +1852,15 @@ SPItem *next_item(SPDesktop *desktop, GSList *path, SPObject *root,
  */
 void scroll_to_show_item(SPDesktop *desktop, SPItem *item)
 {
-    NR::Rect dbox = desktop->get_display_area();
-    NR::Maybe<NR::Rect> sbox = sp_item_bbox_desktop(item);
+    Geom::Rect dbox = desktop->get_display_area();
+    Geom::OptRect sbox = sp_item_bbox_desktop(item);
 
     if ( sbox && dbox.contains(*sbox) == false ) {
-        NR::Point const s_dt = sbox->midpoint();
-        NR::Point const s_w = desktop->d2w(s_dt);
-        NR::Point const d_dt = dbox.midpoint();
-        NR::Point const d_w = desktop->d2w(d_dt);
-        NR::Point const moved_w( d_w - s_w );
+        Geom::Point const s_dt = sbox->midpoint();
+        Geom::Point const s_w = desktop->d2w(s_dt);
+        Geom::Point const d_dt = dbox.midpoint();
+        Geom::Point const d_w = desktop->d2w(d_dt);
+        Geom::Point const moved_w( d_w - s_w );
         gint const dx = (gint) moved_w[X];
         gint const dy = (gint) moved_w[Y];
         desktop->scroll_world(dx, dy);
@@ -2027,9 +1869,8 @@ void scroll_to_show_item(SPDesktop *desktop, SPItem *item)
 
 
 void
-sp_selection_clone()
+sp_selection_clone(SPDesktop *desktop)
 {
-    SPDesktop *desktop = SP_ACTIVE_DESKTOP;
     if (desktop == NULL)
         return;
 
@@ -2044,26 +1885,26 @@ sp_selection_clone()
     }
 
     GSList *reprs = g_slist_copy((GSList *) selection->reprList());
-  
+
     selection->clear();
-  
+
     // sorting items from different parents sorts each parent's subset without possibly mixing them, just what we need
     reprs = g_slist_sort(reprs, (GCompareFunc) sp_repr_compare_position);
 
     GSList *newsel = NULL;
+
     while (reprs) {
         Inkscape::XML::Node *sel_repr = (Inkscape::XML::Node *) reprs->data;
         Inkscape::XML::Node *parent = sp_repr_parent(sel_repr);
 
         Inkscape::XML::Node *clone = xml_doc->createElement("svg:use");
-        sp_repr_set_attr(clone, "x", "0");
-        sp_repr_set_attr(clone, "y", "0");
-        sp_repr_set_attr(clone, "xlink:href", g_strdup_printf("#%s", sel_repr->attribute("id")));
+        clone->setAttribute("x", "0", false);
+        clone->setAttribute("y", "0", false);
+        clone->setAttribute("xlink:href", g_strdup_printf("#%s", sel_repr->attribute("id")), false);
+
+        clone->setAttribute("inkscape:transform-center-x", sel_repr->attribute("inkscape:transform-center-x"), false);
+        clone->setAttribute("inkscape:transform-center-y", sel_repr->attribute("inkscape:transform-center-y"), false);
 
-        sp_repr_set_attr(clone, "inkscape:transform-center-x", sel_repr->attribute("inkscape:transform-center-x"));
-        sp_repr_set_attr(clone, "inkscape:transform-center-y", sel_repr->attribute("inkscape:transform-center-y"));
-        
         // add the new clone to the top of the original's parent
         parent->appendChild(clone);
 
@@ -2071,26 +1912,75 @@ sp_selection_clone()
         reprs = g_slist_remove(reprs, sel_repr);
         Inkscape::GC::release(clone);
     }
-    
-    sp_document_done(sp_desktop_document(desktop), SP_VERB_EDIT_CLONE, 
+
+    // TRANSLATORS: only translate "string" in "context|string".
+    // For more details, see http://developer.gnome.org/doc/API/2.0/glib/glib-I18N.html#Q-:CAPS
+    sp_document_done(sp_desktop_document(desktop), SP_VERB_EDIT_CLONE,
                      Q_("action|Clone"));
 
     selection->setReprList(newsel);
+
     g_slist_free(newsel);
 }
 
 void
-sp_selection_unlink()
+sp_selection_relink(SPDesktop *desktop)
 {
-    SPDesktop *desktop = SP_ACTIVE_DESKTOP;
     if (!desktop)
         return;
 
     Inkscape::Selection *selection = sp_desktop_selection(desktop);
 
     if (selection->isEmpty()) {
-        desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select a <b>clone</b> to unlink."));
+        desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>clones</b> to relink."));
+        return;
+    }
+
+    Inkscape::UI::ClipboardManager *cm = Inkscape::UI::ClipboardManager::get();
+    const gchar *newid = cm->getFirstObjectID();
+    if (!newid) {
+        desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Copy an <b>object</b> to clipboard to relink clones to."));
+        return;
+    }
+    gchar *newref = g_strdup_printf("#%s", newid);
+
+    // Get a copy of current selection.
+    bool relinked = false;
+    for (GSList *items = (GSList *) selection->itemList();
+         items != NULL;
+         items = items->next)
+    {
+        SPItem *item = (SPItem *) items->data;
+
+        if (!SP_IS_USE(item))
+            continue;
+
+        SP_OBJECT_REPR(item)->setAttribute("xlink:href", newref);
+        SP_OBJECT(item)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
+        relinked = true;
+    }
+
+    g_free(newref);
+
+    if (!relinked) {
+        desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("<b>No clones to relink</b> in the selection."));
+    } else {
+        sp_document_done(sp_desktop_document(desktop), SP_VERB_EDIT_UNLINK_CLONE,
+                         _("Relink clone"));
+    }
+}
+
+
+void
+sp_selection_unlink(SPDesktop *desktop)
+{
+    if (!desktop)
+        return;
+
+    Inkscape::Selection *selection = sp_desktop_selection(desktop);
+
+    if (selection->isEmpty()) {
+        desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>clones</b> to unlink."));
         return;
     }
 
@@ -2101,15 +1991,33 @@ sp_selection_unlink()
          items != NULL;
          items = items->next)
     {
-        SPItem *use = (SPItem *) items->data;
+        SPItem *item = (SPItem *) items->data;
 
-        if (!SP_IS_USE(use)) {
-            // keep the non-yse item in the new selection
-            new_select = g_slist_prepend(new_select, use);
+        if (SP_IS_TEXT(item)) {
+            SPObject *tspan = sp_tref_convert_to_tspan(SP_OBJECT(item));
+
+            if (tspan) {
+                SP_OBJECT(item)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
+            }
+
+            // Set unlink to true, and fall into the next if which
+            // will include this text item in the new selection
+            unlinked = true;
+        }
+
+        if (!(SP_IS_USE(item) || SP_IS_TREF(item))) {
+            // keep the non-use item in the new selection
+            new_select = g_slist_prepend(new_select, item);
             continue;
         }
 
-        SPItem *unlink = sp_use_unlink(SP_USE(use));
+        SPItem *unlink;
+        if (SP_IS_USE(item)) {
+            unlink = sp_use_unlink(SP_USE(item));
+        } else /*if (SP_IS_TREF(use))*/ {
+            unlink = SP_ITEM(sp_tref_convert_to_tspan(SP_OBJECT(item)));
+        }
+
         unlinked = true;
         // Add ungrouped items to the new selection.
         new_select = g_slist_prepend(new_select, unlink);
@@ -2129,9 +2037,8 @@ sp_selection_unlink()
 }
 
 void
-sp_select_clone_original()
+sp_select_clone_original(SPDesktop *desktop)
 {
-    SPDesktop *desktop = SP_ACTIVE_DESKTOP;
     if (desktop == NULL)
         return;
 
@@ -2139,7 +2046,7 @@ sp_select_clone_original()
 
     SPItem *item = selection->singleItem();
 
-    const gchar *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.");
+    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.");
 
     // Check if other than two objects are selected
     if (g_slist_length((GSList *) selection->itemList()) != 1 || !item) {
@@ -2149,13 +2056,13 @@ sp_select_clone_original()
 
     SPItem *original = NULL;
     if (SP_IS_USE(item)) {
-        original = sp_use_get_original (SP_USE(item));
-    } else if (SP_IS_OFFSET(item) && SP_OFFSET (item)->sourceHref) {
-        original = sp_offset_get_source (SP_OFFSET(item));
+        original = sp_use_get_original(SP_USE(item));
+    } else if (SP_IS_OFFSET(item) && SP_OFFSET(item)->sourceHref) {
+        original = sp_offset_get_source(SP_OFFSET(item));
     } else if (SP_IS_TEXT_TEXTPATH(item)) {
-        original = sp_textpath_get_path_item (SP_TEXTPATH(sp_object_first_child(SP_OBJECT(item))));
+        original = sp_textpath_get_path_item(SP_TEXTPATH(sp_object_first_child(SP_OBJECT(item))));
     } else if (SP_IS_FLOWTEXT(item)) {
-        original = SP_FLOWTEXT(item)->get_frame (NULL); // first frame only
+        original = SP_FLOWTEXT(item)->get_frame(NULL); // first frame only
     } else { // it's an object that we don't know what to do with
         desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, error);
         return;
@@ -2166,14 +2073,33 @@ sp_select_clone_original()
         return;
     }
 
-    for (SPObject *o = original; o && !SP_IS_ROOT(o); o = SP_OBJECT_PARENT (o)) {
-        if (SP_IS_DEFS (o)) {
+    for (SPObject *o = original; o && !SP_IS_ROOT(o); o = SP_OBJECT_PARENT(o)) {
+        if (SP_IS_DEFS(o)) {
             desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("The object you're trying to select is <b>not visible</b> (it is in &lt;defs&gt;)"));
             return;
         }
     }
 
     if (original) {
+        Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+        bool highlight = prefs->getBool("/options/highlightoriginal/value");
+        if (highlight) {
+            Geom::OptRect a = item->getBounds(sp_item_i2d_affine(item));
+            Geom::OptRect b = original->getBounds(sp_item_i2d_affine(original));
+            if ( a && b ) {
+                // draw a flashing line between the objects
+                SPCurve *curve = new SPCurve();
+                curve->moveto(a->midpoint());
+                curve->lineto(b->midpoint());
+
+                SPCanvasItem * canvasitem = sp_canvas_bpath_new(sp_desktop_tempgroup(desktop), curve);
+                sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(canvasitem), 0x0000ddff, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT, 5, 3);
+                sp_canvas_item_show(canvasitem);
+                curve->unref();
+                desktop->add_temporary_canvasitem(canvasitem, 1000);
+            }
+        }
+
         selection->clear();
         selection->set(original);
         if (SP_CYCLING == SP_CYCLE_FOCUS) {
@@ -2182,15 +2108,138 @@ sp_select_clone_original()
     }
 }
 
+
+void sp_selection_to_marker(SPDesktop *desktop, bool apply)
+{
+    if (desktop == NULL)
+        return;
+
+    SPDocument *doc = sp_desktop_document(desktop);
+    Inkscape::XML::Document *xml_doc = sp_document_repr_doc(doc);
+
+    Inkscape::Selection *selection = sp_desktop_selection(desktop);
+
+    // check if something is selected
+    if (selection->isEmpty()) {
+        desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to convert to marker."));
+        return;
+    }
+
+    sp_document_ensure_up_to_date(doc);
+    Geom::OptRect r = selection->bounds();
+    boost::optional<Geom::Point> c = selection->center();
+    if ( !r || !c ) {
+        return;
+    }
+
+    // calculate the transform to be applied to objects to move them to 0,0
+    Geom::Point move_p = Geom::Point(0, sp_document_height(doc)) - *c;
+    move_p[Geom::Y] = -move_p[Geom::Y];
+    Geom::Matrix move = Geom::Matrix(Geom::Translate(move_p));
+
+    GSList *items = g_slist_copy((GSList *) selection->itemList());
+
+    items = g_slist_sort(items, (GCompareFunc) sp_object_compare_position);
+
+    // bottommost object, after sorting
+    SPObject *parent = SP_OBJECT_PARENT(items->data);
+
+    Geom::Matrix parent_transform(sp_item_i2doc_affine(SP_ITEM(parent)));
+
+    // remember the position of the first item
+    gint pos = SP_OBJECT_REPR(items->data)->position();
+    (void)pos; // TODO check why this was remembered
+
+    // create a list of duplicates
+    GSList *repr_copies = NULL;
+    for (GSList *i = items; i != NULL; i = i->next) {
+        Inkscape::XML::Node *dup = (SP_OBJECT_REPR(i->data))->duplicate(xml_doc);
+        repr_copies = g_slist_prepend(repr_copies, dup);
+    }
+
+    Geom::Rect bounds(desktop->dt2doc(r->min()), desktop->dt2doc(r->max()));
+
+    if (apply) {
+        // delete objects so that their clones don't get alerted; this object will be restored shortly
+        for (GSList *i = items; i != NULL; i = i->next) {
+            SPObject *item = SP_OBJECT(i->data);
+            item->deleteObject(false);
+        }
+    }
+
+    // Hack: Temporarily set clone compensation to unmoved, so that we can move clone-originals
+    // without disturbing clones.
+    // See ActorAlign::on_button_click() in src/ui/dialog/align-and-distribute.cpp
+    Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+    int saved_compensation = prefs->getInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_UNMOVED);
+    prefs->setInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_UNMOVED);
+
+    gchar const *mark_id = generate_marker(repr_copies, bounds, doc,
+                                           ( Geom::Matrix(Geom::Translate(desktop->dt2doc(
+                                                                              Geom::Point(r->min()[Geom::X],
+                                                                                          r->max()[Geom::Y]))))
+                                             * parent_transform.inverse() ),
+                                           parent_transform * move);
+    (void)mark_id;
+
+    // restore compensation setting
+    prefs->setInt("/options/clonecompensation/value", saved_compensation);
+
+
+    g_slist_free(items);
+
+    sp_document_done(doc, SP_VERB_EDIT_SELECTION_2_MARKER,
+                     _("Objects to marker"));
+}
+
+static void sp_selection_to_guides_recursive(SPItem *item, bool deleteitem, bool wholegroups) {
+    if (SP_IS_GROUP(item) && !SP_IS_BOX3D(item) && !wholegroups) {
+        for (GSList *i = sp_item_group_item_list(SP_GROUP(item)); i != NULL; i = i->next) {
+            sp_selection_to_guides_recursive(SP_ITEM(i->data), deleteitem, wholegroups);
+        }
+    } else {
+        sp_item_convert_item_to_guides(item);
+
+        if (deleteitem) {
+            SP_OBJECT(item)->deleteObject(true);
+        }
+    }
+}
+
+void sp_selection_to_guides(SPDesktop *desktop)
+{
+    if (desktop == NULL)
+        return;
+
+    SPDocument *doc = sp_desktop_document(desktop);
+    Inkscape::Selection *selection = sp_desktop_selection(desktop);
+    // we need to copy the list because it gets reset when objects are deleted
+    GSList *items = g_slist_copy((GSList *) selection->itemList());
+
+    if (!items) {
+        desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to convert to guides."));
+        return;
+    }
+
+    Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+    bool deleteitem = !prefs->getBool("/tools/cvg_keep_objects", 0);
+    bool wholegroups = prefs->getBool("/tools/cvg_convert_whole_groups", 0);
+
+    for (GSList const *i = items; i != NULL; i = i->next) {
+        sp_selection_to_guides_recursive(SP_ITEM(i->data), deleteitem, wholegroups);
+    }
+
+    sp_document_done(doc, SP_VERB_EDIT_SELECTION_2_GUIDES, _("Objects to guides"));
+}
+
 void
-sp_selection_tile(bool apply)
+sp_selection_tile(SPDesktop *desktop, bool apply)
 {
-    SPDesktop *desktop = SP_ACTIVE_DESKTOP;
     if (desktop == NULL)
         return;
 
-    SPDocument *document = sp_desktop_document(desktop);
-    Inkscape::XML::Document *xml_doc = sp_document_repr_doc(document);
+    SPDocument *doc = sp_desktop_document(desktop);
+    Inkscape::XML::Document *xml_doc = sp_document_repr_doc(doc);
 
     Inkscape::Selection *selection = sp_desktop_selection(desktop);
 
@@ -2200,74 +2249,80 @@ sp_selection_tile(bool apply)
         return;
     }
 
-    sp_document_ensure_up_to_date(document);
-    NR::Maybe<NR::Rect> r = selection->bounds();
+    sp_document_ensure_up_to_date(doc);
+    Geom::OptRect r = selection->bounds();
     if ( !r ) {
         return;
     }
 
     // calculate the transform to be applied to objects to move them to 0,0
-    NR::Point move_p = NR::Point(0, sp_document_height(document)) - (r->min() + NR::Point (0, r->extent(NR::Y)));
-    move_p[NR::Y] = -move_p[NR::Y];
-    NR::Matrix move = NR::Matrix (NR::translate (move_p));
+    Geom::Point move_p = Geom::Point(0, sp_document_height(doc)) - (r->min() + Geom::Point(0, r->dimensions()[Geom::Y]));
+    move_p[Geom::Y] = -move_p[Geom::Y];
+    Geom::Matrix move = Geom::Matrix(Geom::Translate(move_p));
 
     GSList *items = g_slist_copy((GSList *) selection->itemList());
 
-    items = g_slist_sort (items, (GCompareFunc) sp_object_compare_position);
+    items = g_slist_sort(items, (GCompareFunc) sp_object_compare_position);
 
     // bottommost object, after sorting
-    SPObject *parent = SP_OBJECT_PARENT (items->data);
+    SPObject *parent = SP_OBJECT_PARENT(items->data);
 
-    NR::Matrix parent_transform = sp_item_i2root_affine(SP_ITEM(parent));
+    Geom::Matrix parent_transform(sp_item_i2doc_affine(SP_ITEM(parent)));
 
     // remember the position of the first item
-    gint pos = SP_OBJECT_REPR (items->data)->position();
+    gint pos = SP_OBJECT_REPR(items->data)->position();
 
     // create a list of duplicates
     GSList *repr_copies = NULL;
     for (GSList *i = items; i != NULL; i = i->next) {
-        Inkscape::XML::Node *dup = (SP_OBJECT_REPR (i->data))->duplicate();
-        repr_copies = g_slist_prepend (repr_copies, dup);
+        Inkscape::XML::Node *dup = (SP_OBJECT_REPR(i->data))->duplicate(xml_doc);
+        repr_copies = g_slist_prepend(repr_copies, dup);
     }
+    // restore the z-order after prepends
+    repr_copies = g_slist_reverse(repr_copies);
 
-    NR::Rect bounds(desktop->dt2doc(r->min()), desktop->dt2doc(r->max()));
+    Geom::Rect bounds(desktop->dt2doc(r->min()), desktop->dt2doc(r->max()));
 
     if (apply) {
         // delete objects so that their clones don't get alerted; this object will be restored shortly
         for (GSList *i = items; i != NULL; i = i->next) {
-            SPObject *item = SP_OBJECT (i->data);
-            item->deleteObject (false);
+            SPObject *item = SP_OBJECT(i->data);
+            item->deleteObject(false);
         }
     }
 
     // Hack: Temporarily set clone compensation to unmoved, so that we can move clone-originals
     // without disturbing clones.
     // See ActorAlign::on_button_click() in src/ui/dialog/align-and-distribute.cpp
-    int saved_compensation = prefs_get_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
-    prefs_set_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
+    Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+    int saved_compensation = prefs->getInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_UNMOVED);
+    prefs->setInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_UNMOVED);
 
-    const gchar *pat_id = pattern_tile (repr_copies, bounds, document,
-                                        NR::Matrix(NR::translate(desktop->dt2doc(NR::Point(r->min()[NR::X], r->max()[NR::Y])))) * parent_transform.inverse(), parent_transform * move);
+    gchar const *pat_id = pattern_tile(repr_copies, bounds, doc,
+                                       ( Geom::Matrix(Geom::Translate(desktop->dt2doc(Geom::Point(r->min()[Geom::X],
+                                                                                            r->max()[Geom::Y]))))
+                                         * to_2geom(parent_transform.inverse()) ),
+                                       parent_transform * move);
 
     // restore compensation setting
-    prefs_set_int_attribute("options.clonecompensation", "value", saved_compensation);
+    prefs->setInt("/options/clonecompensation/value", saved_compensation);
 
     if (apply) {
         Inkscape::XML::Node *rect = xml_doc->createElement("svg:rect");
         rect->setAttribute("style", g_strdup_printf("stroke:none;fill:url(#%s)", pat_id));
 
-        NR::Point min = bounds.min() * parent_transform.inverse();
-        NR::Point max = bounds.max() * parent_transform.inverse();
+        Geom::Point min = bounds.min() * to_2geom(parent_transform.inverse());
+        Geom::Point max = bounds.max() * to_2geom(parent_transform.inverse());
 
-        sp_repr_set_svg_double(rect, "width", max[NR::X] - min[NR::X]);
-        sp_repr_set_svg_double(rect, "height", max[NR::Y] - min[NR::Y]);
-        sp_repr_set_svg_double(rect, "x", min[NR::X]);
-        sp_repr_set_svg_double(rect, "y", min[NR::Y]);
+        sp_repr_set_svg_double(rect, "width", max[Geom::X] - min[Geom::X]);
+        sp_repr_set_svg_double(rect, "height", max[Geom::Y] - min[Geom::Y]);
+        sp_repr_set_svg_double(rect, "x", min[Geom::X]);
+        sp_repr_set_svg_double(rect, "y", min[Geom::Y]);
 
         // restore parent and position
-        SP_OBJECT_REPR (parent)->appendChild(rect);
+        SP_OBJECT_REPR(parent)->appendChild(rect);
         rect->setPosition(pos > 0 ? pos : 0);
-        SPItem *rectangle = (SPItem *) sp_desktop_document (desktop)->getObjectByRepr(rect);
+        SPItem *rectangle = (SPItem *) sp_desktop_document(desktop)->getObjectByRepr(rect);
 
         Inkscape::GC::release(rect);
 
@@ -2275,20 +2330,20 @@ sp_selection_tile(bool apply)
         selection->set(rectangle);
     }
 
-    g_slist_free (items);
+    g_slist_free(items);
 
-    sp_document_done (document, SP_VERB_EDIT_TILE, 
-                      _("Objects to pattern"));
+    sp_document_done(doc, SP_VERB_EDIT_TILE,
+                     _("Objects to pattern"));
 }
 
 void
-sp_selection_untile()
+sp_selection_untile(SPDesktop *desktop)
 {
-    SPDesktop *desktop = SP_ACTIVE_DESKTOP;
     if (desktop == NULL)
         return;
 
-    SPDocument *document = sp_desktop_document(desktop);
+    SPDocument *doc = sp_desktop_document(desktop);
+    Inkscape::XML::Document *xml_doc = sp_document_repr_doc(doc);
 
     Inkscape::Selection *selection = sp_desktop_selection(desktop);
 
@@ -2308,9 +2363,9 @@ sp_selection_untile()
 
         SPItem *item = (SPItem *) items->data;
 
-        SPStyle *style = SP_OBJECT_STYLE (item);
+        SPStyle *style = SP_OBJECT_STYLE(item);
 
-        if (!style || style->fill.type != SP_PAINT_TYPE_PAINTSERVER)
+        if (!style || !style->fill.isPaintserver())
             continue;
 
         SPObject *server = SP_OBJECT_STYLE_FILL_SERVER(item);
@@ -2320,59 +2375,59 @@ sp_selection_untile()
 
         did = true;
 
-        SPPattern *pattern = pattern_getroot (SP_PATTERN (server));
+        SPPattern *pattern = pattern_getroot(SP_PATTERN(server));
 
-        NR::Matrix pat_transform = pattern_patternTransform (SP_PATTERN (server));
+        Geom::Matrix pat_transform = to_2geom(pattern_patternTransform(SP_PATTERN(server)));
         pat_transform *= item->transform;
 
         for (SPObject *child = sp_object_first_child(SP_OBJECT(pattern)) ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
-            Inkscape::XML::Node *copy = SP_OBJECT_REPR(child)->duplicate();
-            SPItem *i = SP_ITEM (desktop->currentLayer()->appendChildRepr(copy));
+            Inkscape::XML::Node *copy = SP_OBJECT_REPR(child)->duplicate(xml_doc);
+            SPItem *i = SP_ITEM(desktop->currentLayer()->appendChildRepr(copy));
 
            // FIXME: relink clones to the new canvas objects
            // use SPObject::setid when mental finishes it to steal ids of
 
             // this is needed to make sure the new item has curve (simply requestDisplayUpdate does not work)
-            sp_document_ensure_up_to_date (document);
+            sp_document_ensure_up_to_date(doc);
 
-            NR::Matrix transform( i->transform * pat_transform );
+            Geom::Matrix transform( i->transform * pat_transform );
             sp_item_write_transform(i, SP_OBJECT_REPR(i), transform);
 
             new_select = g_slist_prepend(new_select, i);
         }
 
-        SPCSSAttr *css = sp_repr_css_attr_new ();
-        sp_repr_css_set_property (css, "fill", "none");
-        sp_repr_css_change (SP_OBJECT_REPR (item), css, "style");
+        SPCSSAttr *css = sp_repr_css_attr_new();
+        sp_repr_css_set_property(css, "fill", "none");
+        sp_repr_css_change(SP_OBJECT_REPR(item), css, "style");
     }
 
     if (!did) {
         desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("<b>No pattern fills</b> in the selection."));
     } else {
-        sp_document_done(sp_desktop_document(desktop), SP_VERB_EDIT_UNTILE, 
+        sp_document_done(sp_desktop_document(desktop), SP_VERB_EDIT_UNTILE,
                          _("Pattern to objects"));
         selection->setList(new_select);
     }
 }
 
 void
-sp_selection_get_export_hints (Inkscape::Selection *selection, const char **filename, float *xdpi, float *ydpi) 
+sp_selection_get_export_hints(Inkscape::Selection *selection, char const **filename, float *xdpi, float *ydpi)
 {
     if (selection->isEmpty()) {
         return;
     }
 
-    const GSList * reprlst = selection->reprList();
+    GSList const *reprlst = selection->reprList();
     bool filename_search = TRUE;
     bool xdpi_search = TRUE;
     bool ydpi_search = TRUE;
 
-    for(; reprlst != NULL &&
+    for (; reprlst != NULL &&
             filename_search &&
             xdpi_search &&
             ydpi_search;
         reprlst = reprlst->next) {
-        const gchar * dpi_string;
+        gchar const *dpi_string;
         Inkscape::XML::Node * repr = (Inkscape::XML::Node *)reprlst->data;
 
         if (filename_search) {
@@ -2402,10 +2457,10 @@ sp_selection_get_export_hints (Inkscape::Selection *selection, const char **file
 }
 
 void
-sp_document_get_export_hints (SPDocument * doc, const char **filename, float *xdpi, float *ydpi) 
+sp_document_get_export_hints(SPDocument *doc, char const **filename, float *xdpi, float *ydpi)
 {
     Inkscape::XML::Node * repr = sp_document_repr_root(doc);
-    const gchar * dpi_string;
+    gchar const *dpi_string;
 
     *filename = repr->attribute("inkscape:export-filename");
 
@@ -2423,9 +2478,8 @@ sp_document_get_export_hints (SPDocument * doc, const char **filename, float *xd
 }
 
 void
-sp_selection_create_bitmap_copy ()
+sp_selection_create_bitmap_copy(SPDesktop *desktop)
 {
-    SPDesktop *desktop = SP_ACTIVE_DESKTOP;
     if (desktop == NULL)
         return;
 
@@ -2440,16 +2494,21 @@ sp_selection_create_bitmap_copy ()
         return;
     }
 
+    desktop->messageStack()->flash(Inkscape::IMMEDIATE_MESSAGE, _("Rendering bitmap..."));
+    // set "busy" cursor
+    desktop->setWaitingCursor();
+
     // Get the bounding box of the selection
     NRRect bbox;
-    sp_document_ensure_up_to_date (document);
+    sp_document_ensure_up_to_date(document);
     selection->bounds(&bbox);
     if (NR_RECT_DFLS_TEST_EMPTY(&bbox)) {
+        desktop->clearWaitingCursor();
         return; // exceptional situation, so not bother with a translatable error message, just quit quietly
     }
 
     // List of the items to show; all others will be hidden
-    GSList *items = g_slist_copy ((GSList *) selection->itemList());
+    GSList *items = g_slist_copy((GSList *) selection->itemList());
 
     // Sort items so that the topmost comes last
     items = g_slist_sort(items, (GCompareFunc) sp_item_repr_compare_position);
@@ -2457,18 +2516,29 @@ sp_selection_create_bitmap_copy ()
     // Generate a random value from the current time (you may create bitmap from the same object(s)
     // multiple times, and this is done so that they don't clash)
     GTimeVal cu;
-    g_get_current_time (&cu);
+    g_get_current_time(&cu);
     guint current = (int) (cu.tv_sec * 1000000 + cu.tv_usec) % 1024;
 
-    // Create the filename
-    gchar *filename = g_strdup_printf ("%s-%s-%u.png", document->name, SP_OBJECT_REPR(items->data)->attribute("id"), current);
+    // Create the filename.
+    gchar *const basename = g_strdup_printf("%s-%s-%u.png",
+                                            document->name,
+                                            SP_OBJECT_REPR(items->data)->attribute("id"),
+                                            current);
     // Imagemagick is known not to handle spaces in filenames, so we replace anything but letters,
     // digits, and a few other chars, with "_"
-    filename = g_strcanon (filename, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.=+~$#@^&!?", '_');
-    // Build the complete path by adding document->base if set
-    gchar *filepath = g_build_filename (document->base?document->base:"", filename, NULL);
+    g_strcanon(basename, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.=+~$#@^&!?", '_');
+
+    // Build the complete path by adding document base dir, if set, otherwise home dir
+    gchar * directory = NULL;
+    if (SP_DOCUMENT_URI(document)) {
+        directory = g_dirname(SP_DOCUMENT_URI(document));
+    }
+    if (directory == NULL) {
+        directory = homedir_path(NULL);
+    }
+    gchar *filepath = g_build_filename(directory, basename, NULL);
 
-    //g_print ("%s\n", filepath);
+    //g_print("%s\n", filepath);
 
     // Remember parent and z-order of the topmost one
     gint pos = SP_OBJECT_REPR(g_slist_last(items)->data)->position();
@@ -2476,25 +2546,26 @@ sp_selection_create_bitmap_copy ()
     Inkscape::XML::Node *parent = SP_OBJECT_REPR(parent_object);
 
     // Calculate resolution
+    Inkscape::Preferences *prefs = Inkscape::Preferences::get();
     double res;
-    int const prefs_res = prefs_get_int_attribute ("options.createbitmap", "resolution", 0);
-    int const prefs_min = prefs_get_int_attribute ("options.createbitmap", "minsize", 0);
+    int const prefs_res = prefs->getInt("/options/createbitmap/resolution", 0);
+    int const prefs_min = prefs->getInt("/options/createbitmap/minsize", 0);
     if (0 < prefs_res) {
         // If it's given explicitly in prefs, take it
         res = prefs_res;
     } else if (0 < prefs_min) {
         // If minsize is given, look up minimum bitmap size (default 250 pixels) and calculate resolution from it
-        res = PX_PER_IN * prefs_min / MIN ((bbox.x1 - bbox.x0), (bbox.y1 - bbox.y0));
+        res = PX_PER_IN * prefs_min / MIN((bbox.x1 - bbox.x0), (bbox.y1 - bbox.y0));
     } else {
         float hint_xdpi = 0, hint_ydpi = 0;
-        const char *hint_filename;
+        char const *hint_filename;
         // take resolution hint from the selected objects
-        sp_selection_get_export_hints (selection, &hint_filename, &hint_xdpi, &hint_ydpi);
+        sp_selection_get_export_hints(selection, &hint_filename, &hint_xdpi, &hint_ydpi);
         if (hint_xdpi != 0) {
             res = hint_xdpi;
         } else {
             // take resolution hint from the document
-            sp_document_get_export_hints (document, &hint_filename, &hint_xdpi, &hint_ydpi);
+            sp_document_get_export_hints(document, &hint_filename, &hint_xdpi, &hint_ydpi);
             if (hint_xdpi != 0) {
                 res = hint_xdpi;
             } else {
@@ -2505,69 +2576,74 @@ sp_selection_create_bitmap_copy ()
     }
 
     // The width and height of the bitmap in pixels
-    unsigned width = (unsigned) floor ((bbox.x1 - bbox.x0) * res / PX_PER_IN);
-    unsigned height =(unsigned) floor ((bbox.y1 - bbox.y0) * res / PX_PER_IN);
+    unsigned width = (unsigned) floor((bbox.x1 - bbox.x0) * res / PX_PER_IN);
+    unsigned height =(unsigned) floor((bbox.y1 - bbox.y0) * res / PX_PER_IN);
 
-    // Find out if we have to run a filter
-    const gchar *run = NULL;
-    const gchar *filter = prefs_get_string_attribute ("options.createbitmap", "filter");
-    if (filter) {
+    // Find out if we have to run an external filter
+    gchar const *run = NULL;
+    Glib::ustring filter = prefs->getString("/options/createbitmap/filter");
+    if (!filter.empty()) {
         // filter command is given;
         // see if we have a parameter to pass to it
-        const gchar *param1 = prefs_get_string_attribute ("options.createbitmap", "filter_param1");
-        if (param1) {
-            if (param1[strlen(param1) - 1] == '%') {
+        Glib::ustring param1 = prefs->getString("/options/createbitmap/filter_param1");
+        if (!param1.empty()) {
+            if (param1[param1.length() - 1] == '%') {
                 // if the param string ends with %, interpret it as a percentage of the image's max dimension
                 gchar p1[256];
-                g_ascii_dtostr (p1, 256, ceil (g_ascii_strtod (param1, NULL) * MAX(width, height) / 100));
+                g_ascii_dtostr(p1, 256, ceil(g_ascii_strtod(param1.data(), NULL) * MAX(width, height) / 100));
                 // the first param is always the image filename, the second is param1
-                run = g_strdup_printf ("%s \"%s\" %s", filter, filepath, p1);
+                run = g_strdup_printf("%s \"%s\" %s", filter.data(), filepath, p1);
             } else {
                 // otherwise pass the param1 unchanged
-                run = g_strdup_printf ("%s \"%s\" %s", filter, filepath, param1);
+                run = g_strdup_printf("%s \"%s\" %s", filter.data(), filepath, param1.data());
             }
         } else {
             // run without extra parameter
-            run = g_strdup_printf ("%s \"%s\"", filter, filepath);
+            run = g_strdup_printf("%s \"%s\"", filter.data(), filepath);
         }
     }
 
     // Calculate the matrix that will be applied to the image so that it exactly overlaps the source objects
-    NR::Matrix eek = sp_item_i2d_affine (SP_ITEM(parent_object));
-    NR::Matrix t;
+    Geom::Matrix eek(sp_item_i2d_affine(SP_ITEM(parent_object)));
+    Geom::Matrix t;
 
     double shift_x = bbox.x0;
-    double shift_y = bbox.y1; 
+    double shift_y = bbox.y1;
     if (res == PX_PER_IN) { // for default 90 dpi, snap it to pixel grid
-        shift_x = round (shift_x);
-        shift_y = -round (-shift_y); // this gets correct rounding despite coordinate inversion, remove the negations when the inversion is gone
+        shift_x = round(shift_x);
+        shift_y = -round(-shift_y); // this gets correct rounding despite coordinate inversion, remove the negations when the inversion is gone
     }
-    t = NR::scale(1, -1) * NR::translate (shift_x, shift_y) * eek.inverse();
+    t = Geom::Scale(1, -1) * Geom::Translate(shift_x, shift_y) * eek.inverse();
 
     // Do the export
     sp_export_png_file(document, filepath,
-                   bbox.x0, bbox.y0, bbox.x1, bbox.y1,
-                   width, height, res, res,
-                   (guint32) 0xffffff00,
-                   NULL, NULL,
-                   true,  /*bool force_overwrite,*/
-                   items);
+                       bbox.x0, bbox.y0, bbox.x1, bbox.y1,
+                       width, height, res, res,
+                       (guint32) 0xffffff00,
+                       NULL, NULL,
+                       true,  /*bool force_overwrite,*/
+                       items);
 
-    g_slist_free (items);
+    g_slist_free(items);
 
     // Run filter, if any
     if (run) {
-        g_print ("Running external filter: %s\n", run);
-        system (run);
+        g_print("Running external filter: %s\n", run);
+        int retval;
+        retval = system(run);
     }
 
     // Import the image back
-    GdkPixbuf *pb = gdk_pixbuf_new_from_file (filepath, NULL);
+    GdkPixbuf *pb = gdk_pixbuf_new_from_file(filepath, NULL);
     if (pb) {
         // Create the repr for the image
         Inkscape::XML::Node * repr = xml_doc->createElement("svg:image");
-        repr->setAttribute("xlink:href", filename);
-        repr->setAttribute("sodipodi:absref", filepath);
+        {
+            repr->setAttribute("sodipodi:absref", filepath);
+            gchar *abs_base = Inkscape::XML::calc_abs_doc_base(document->base);
+            repr->setAttribute("xlink:href", sp_relative_path_from_path(filepath, abs_base));
+            g_free(abs_base);
+        }
         if (res == PX_PER_IN) { // for default 90 dpi, snap it to pixel grid
             sp_repr_set_svg_double(repr, "width", width);
             sp_repr_set_svg_double(repr, "height", height);
@@ -2593,37 +2669,37 @@ sp_selection_create_bitmap_copy ()
 
         // Clean up
         Inkscape::GC::release(repr);
-        gdk_pixbuf_unref (pb);
+        gdk_pixbuf_unref(pb);
 
         // Complete undoable transaction
-        sp_document_done (document, SP_VERB_SELECTION_CREATE_BITMAP,
-                          _("Create bitmap"));
+        sp_document_done(document, SP_VERB_SELECTION_CREATE_BITMAP,
+                         _("Create bitmap"));
     }
 
-    g_free (filename);
-    g_free (filepath);
+    desktop->clearWaitingCursor();
+
+    g_free(basename);
+    g_free(filepath);
 }
 
 /**
- * \brief sp_selection_set_mask
- *
- * This function creates a mask or clipPath from selection
+ * \brief Creates a mask or clipPath from selection
  * Two different modes:
  *  if applyToLayer, all selection is moved to DEFS as mask/clippath
  *       and is applied to current layer
  *  otherwise, topmost object is used as mask for other objects
  * If \a apply_clip_path parameter is true, clipPath is created, otherwise mask
- * 
+ *
  */
 void
-sp_selection_set_mask(bool apply_clip_path, bool apply_to_layer)
+sp_selection_set_mask(SPDesktop *desktop, bool apply_clip_path, bool apply_to_layer)
 {
-    SPDesktop *desktop = SP_ACTIVE_DESKTOP;
     if (desktop == NULL)
         return;
 
-    SPDocument *document = sp_desktop_document(desktop);
-    
+    SPDocument *doc = sp_desktop_document(desktop);
+    Inkscape::XML::Document *xml_doc = sp_document_repr_doc(doc);
+
     Inkscape::Selection *selection = sp_desktop_selection(desktop);
 
     // check if something is selected
@@ -2636,119 +2712,120 @@ sp_selection_set_mask(bool apply_clip_path, bool apply_to_layer)
         return;
     }
 
-    // FIXME: temporary patch to prevent crash! 
+    // FIXME: temporary patch to prevent crash!
     // Remove this when bboxes are fixed to not blow up on an item clipped/masked with its own clone
-    bool clone_with_original = selection_contains_both_clone_and_original (selection);
+    bool clone_with_original = selection_contains_both_clone_and_original(selection);
     if (clone_with_original) {
         return; // in this version, you cannot clip/mask an object with its own clone
     }
     // /END FIXME
-    
-    sp_document_ensure_up_to_date(document);
+
+    sp_document_ensure_up_to_date(doc);
 
     GSList *items = g_slist_copy((GSList *) selection->itemList());
-    
-    items = g_slist_sort (items, (GCompareFunc) sp_object_compare_position);
+
+    items = g_slist_sort(items, (GCompareFunc) sp_object_compare_position);
 
     // create a list of duplicates
     GSList *mask_items = NULL;
     GSList *apply_to_items = NULL;
     GSList *items_to_delete = NULL;
-    bool topmost = prefs_get_int_attribute ("options.maskobject", "topmost", 1);
-    bool remove_original = prefs_get_int_attribute ("options.maskobject", "remove", 1);
-    
+    Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+    bool topmost = prefs->getBool("/options/maskobject/topmost", true);
+    bool remove_original = prefs->getBool("/options/maskobject/remove", true);
+
     if (apply_to_layer) {
         // all selected items are used for mask, which is applied to a layer
-        apply_to_items = g_slist_prepend (apply_to_items, desktop->currentLayer());
+        apply_to_items = g_slist_prepend(apply_to_items, desktop->currentLayer());
 
         for (GSList *i = items; i != NULL; i = i->next) {
-            Inkscape::XML::Node *dup = (SP_OBJECT_REPR (i->data))->duplicate();
-            mask_items = g_slist_prepend (mask_items, dup);
+            Inkscape::XML::Node *dup = (SP_OBJECT_REPR(i->data))->duplicate(xml_doc);
+            mask_items = g_slist_prepend(mask_items, dup);
 
             if (remove_original) {
-                SPObject *item = SP_OBJECT (i->data);
-                items_to_delete = g_slist_prepend (items_to_delete, item);
+                SPObject *item = SP_OBJECT(i->data);
+                items_to_delete = g_slist_prepend(items_to_delete, item);
             }
         }
     } else if (!topmost) {
         // topmost item is used as a mask, which is applied to other items in a selection
         GSList *i = items;
-        Inkscape::XML::Node *dup = (SP_OBJECT_REPR (i->data))->duplicate();
-        mask_items = g_slist_prepend (mask_items, dup);
+        Inkscape::XML::Node *dup = (SP_OBJECT_REPR(i->data))->duplicate(xml_doc);
+        mask_items = g_slist_prepend(mask_items, dup);
 
         if (remove_original) {
-            SPObject *item = SP_OBJECT (i->data);
-            items_to_delete = g_slist_prepend (items_to_delete, item);
+            SPObject *item = SP_OBJECT(i->data);
+            items_to_delete = g_slist_prepend(items_to_delete, item);
         }
-        
+
         for (i = i->next; i != NULL; i = i->next) {
-            apply_to_items = g_slist_prepend (apply_to_items, i->data);
+            apply_to_items = g_slist_prepend(apply_to_items, i->data);
         }
     } else {
         GSList *i = NULL;
         for (i = items; NULL != i->next; i = i->next) {
-            apply_to_items = g_slist_prepend (apply_to_items, i->data);
+            apply_to_items = g_slist_prepend(apply_to_items, i->data);
         }
 
-        Inkscape::XML::Node *dup = (SP_OBJECT_REPR (i->data))->duplicate();
-        mask_items = g_slist_prepend (mask_items, dup);
+        Inkscape::XML::Node *dup = (SP_OBJECT_REPR(i->data))->duplicate(xml_doc);
+        mask_items = g_slist_prepend(mask_items, dup);
 
         if (remove_original) {
-            SPObject *item = SP_OBJECT (i->data);
-            items_to_delete = g_slist_prepend (items_to_delete, item);
+            SPObject *item = SP_OBJECT(i->data);
+            items_to_delete = g_slist_prepend(items_to_delete, item);
         }
     }
-    
-    g_slist_free (items);
+
+    g_slist_free(items);
     items = NULL;
-            
-    gchar constattributeName = apply_clip_path ? "clip-path" : "mask";
+
+    gchar const *attributeName = apply_clip_path ? "clip-path" : "mask";
     for (GSList *i = apply_to_items; NULL != i; i = i->next) {
         SPItem *item = reinterpret_cast<SPItem *>(i->data);
         // inverted object transform should be applied to a mask object,
         // as mask is calculated in user space (after applying transform)
-        NR::Matrix maskTransform (item->transform.inverse());
+        Geom::Matrix maskTransform(item->transform.inverse());
 
         GSList *mask_items_dup = NULL;
         for (GSList *mask_item = mask_items; NULL != mask_item; mask_item = mask_item->next) {
-            Inkscape::XML::Node *dup = reinterpret_cast<Inkscape::XML::Node *>(mask_item->data)->duplicate();
-            mask_items_dup = g_slist_prepend (mask_items_dup, dup);
+            Inkscape::XML::Node *dup = reinterpret_cast<Inkscape::XML::Node *>(mask_item->data)->duplicate(xml_doc);
+            mask_items_dup = g_slist_prepend(mask_items_dup, dup);
         }
 
-        const gchar *mask_id = NULL;
+        gchar const *mask_id = NULL;
         if (apply_clip_path) {
-            mask_id = sp_clippath_create(mask_items_dup, document, &maskTransform);
+            mask_id = sp_clippath_create(mask_items_dup, doc, &maskTransform);
         } else {
-            mask_id = sp_mask_create(mask_items_dup, document, &maskTransform);
+            mask_id = sp_mask_create(mask_items_dup, doc, &maskTransform);
         }
 
-        g_slist_free (mask_items_dup);
+        g_slist_free(mask_items_dup);
         mask_items_dup = NULL;
 
         SP_OBJECT_REPR(i->data)->setAttribute(attributeName, g_strdup_printf("url(#%s)", mask_id));
     }
 
-    g_slist_free (mask_items);
-    g_slist_free (apply_to_items);
+    g_slist_free(mask_items);
+    g_slist_free(apply_to_items);
 
     for (GSList *i = items_to_delete; NULL != i; i = i->next) {
-        SPObject *item = SP_OBJECT (i->data);
-        item->deleteObject (false);
+        SPObject *item = SP_OBJECT(i->data);
+        item->deleteObject(false);
     }
-    g_slist_free (items_to_delete);
+    g_slist_free(items_to_delete);
 
-    if (apply_clip_path) 
-        sp_document_done (document, SP_VERB_OBJECT_SET_CLIPPATH, _("Set clipping path"));
-    else 
-        sp_document_done (document, SP_VERB_OBJECT_SET_MASK, _("Set mask"));
+    if (apply_clip_path)
+        sp_document_done(doc, SP_VERB_OBJECT_SET_CLIPPATH, _("Set clipping path"));
+    else
+        sp_document_done(doc, SP_VERB_OBJECT_SET_MASK, _("Set mask"));
 }
 
-void sp_selection_unset_mask(bool apply_clip_path) {
-    SPDesktop *desktop = SP_ACTIVE_DESKTOP;
+void sp_selection_unset_mask(SPDesktop *desktop, bool apply_clip_path) {
     if (desktop == NULL)
         return;
-    
-    SPDocument *document = sp_desktop_document(desktop);    
+
+    SPDocument *doc = sp_desktop_document(desktop);
+    Inkscape::XML::Document *xml_doc = sp_document_repr_doc(doc);
     Inkscape::Selection *selection = sp_desktop_selection(desktop);
 
     // check if something is selected
@@ -2756,18 +2833,21 @@ void sp_selection_unset_mask(bool apply_clip_path) {
         desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to remove clippath or mask from."));
         return;
     }
-    
-    bool remove_original = prefs_get_int_attribute ("options.maskobject", "remove", 1);
-    sp_document_ensure_up_to_date(document);
 
-    gchar const* attributeName = apply_clip_path ? "clip-path" : "mask";
+    Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+    bool remove_original = prefs->getBool("/options/maskobject/remove", true);
+    sp_document_ensure_up_to_date(doc);
+
+    gchar const *attributeName = apply_clip_path ? "clip-path" : "mask";
     std::map<SPObject*,SPItem*> referenced_objects;
-    for (GSList const*i = selection->itemList(); NULL != i; i = i->next) {
+    // SPObject* refers to a group containing the clipped path or mask itself,
+    // whereas SPItem* refers to the item being clipped or masked
+    for (GSList const *i = selection->itemList(); NULL != i; i = i->next) {
         if (remove_original) {
             // remember referenced mask/clippath, so orphaned masks can be moved back to document
             SPItem *item = reinterpret_cast<SPItem *>(i->data);
             Inkscape::URIReference *uri_ref = NULL;
-        
+
             if (apply_clip_path) {
                 uri_ref = item->clip_ref;
             } else {
@@ -2785,11 +2865,12 @@ void sp_selection_unset_mask(bool apply_clip_path) {
 
     // restore mask objects into a document
     for ( std::map<SPObject*,SPItem*>::iterator it = referenced_objects.begin() ; it != referenced_objects.end() ; ++it) {
-        SPObject *obj = (*it).first;
+        SPObject *obj = (*it).first; // Group containing the clipped paths or masks
         GSList *items_to_move = NULL;
         for (SPObject *child = sp_object_first_child(obj) ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
-            Inkscape::XML::Node *copy = SP_OBJECT_REPR(child)->duplicate();
-            items_to_move = g_slist_prepend (items_to_move, copy);
+            // Collect all clipped paths and masks within a single group
+            Inkscape::XML::Node *copy = SP_OBJECT_REPR(child)->duplicate(xml_doc);
+            items_to_move = g_slist_prepend(items_to_move, copy);
         }
 
         if (!obj->isReferenced()) {
@@ -2801,6 +2882,7 @@ void sp_selection_unset_mask(bool apply_clip_path) {
         Inkscape::XML::Node *parent = SP_OBJECT_REPR((*it).second)->parent();
         gint pos = SP_OBJECT_REPR((*it).second)->position();
 
+        // Iterate through all clipped paths / masks
         for (GSList *i = items_to_move; NULL != i; i = i->next) {
             Inkscape::XML::Node *repr = (Inkscape::XML::Node *)i->data;
 
@@ -2808,50 +2890,85 @@ void sp_selection_unset_mask(bool apply_clip_path) {
             parent->appendChild(repr);
             repr->setPosition((pos + 1) > 0 ? (pos + 1) : 0);
 
-            SPItem *mask_item = (SPItem *) sp_desktop_document (desktop)->getObjectByRepr(repr);
+            SPItem *mask_item = (SPItem *) sp_desktop_document(desktop)->getObjectByRepr(repr);
             selection->add(repr);
 
             // transform mask, so it is moved the same spot where mask was applied
-            NR::Matrix transform (mask_item->transform);
+            Geom::Matrix transform(mask_item->transform);
             transform *= (*it).second->transform;
             sp_item_write_transform(mask_item, SP_OBJECT_REPR(mask_item), transform);
         }
 
-        g_slist_free (items_to_move);
+        g_slist_free(items_to_move);
     }
 
-    if (apply_clip_path) 
-        sp_document_done (document, SP_VERB_OBJECT_UNSET_CLIPPATH, _("Release clipping path"));
-    else 
-        sp_document_done (document, SP_VERB_OBJECT_UNSET_MASK, _("Release mask"));
+    if (apply_clip_path)
+        sp_document_done(doc, SP_VERB_OBJECT_UNSET_CLIPPATH, _("Release clipping path"));
+    else
+        sp_document_done(doc, SP_VERB_OBJECT_UNSET_MASK, _("Release mask"));
 }
 
-void fit_canvas_to_selection(SPDesktop *desktop) {
-    g_return_if_fail(desktop != NULL);
+/**
+ * Returns true if an undoable change should be recorded.
+ */
+bool
+fit_canvas_to_selection(SPDesktop *desktop)
+{
+    g_return_val_if_fail(desktop != NULL, false);
     SPDocument *doc = sp_desktop_document(desktop);
 
-    g_return_if_fail(doc != NULL);
-    g_return_if_fail(desktop->selection != NULL);
-    g_return_if_fail(!desktop->selection->isEmpty());
-    NRRect bbox(0, 0, 0, 0);
+    g_return_val_if_fail(doc != NULL, false);
+    g_return_val_if_fail(desktop->selection != NULL, false);
 
-    desktop->selection->bounds(&bbox);
-    if (!nr_rect_d_test_empty(&bbox)) {
-        doc->fitToRect(bbox);
+    if (desktop->selection->isEmpty()) {
+        desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to fit canvas to."));
+        return false;
     }
-};
+    Geom::OptRect const bbox(desktop->selection->bounds());
+    if (bbox) {
+        doc->fitToRect(*bbox);
+        return true;
+    } else {
+        return false;
+    }
+}
 
-void fit_canvas_to_drawing(SPDocument *doc) {
-    g_return_if_fail(doc != NULL);
-    NRRect bbox(0, 0, 0, 0);
+/**
+ * Fit canvas to the bounding box of the selection, as an undoable action.
+ */
+void
+verb_fit_canvas_to_selection(SPDesktop *const desktop)
+{
+    if (fit_canvas_to_selection(desktop)) {
+        sp_document_done(sp_desktop_document(desktop), SP_VERB_FIT_CANVAS_TO_SELECTION,
+                         _("Fit Page to Selection"));
+    }
+}
 
-    sp_document_ensure_up_to_date (doc);
-    sp_item_invoke_bbox(SP_ITEM(doc->root), &bbox, sp_item_i2r_affine(SP_ITEM(doc->root)), TRUE);
+bool
+fit_canvas_to_drawing(SPDocument *doc)
+{
+    g_return_val_if_fail(doc != NULL, false);
 
-    if (!nr_rect_d_test_empty(&bbox)) {
-        doc->fitToRect(bbox);
+    sp_document_ensure_up_to_date(doc);
+    SPItem const *const root = SP_ITEM(doc->root);
+    Geom::OptRect const bbox(root->getBounds(sp_item_i2d_affine(root)));
+    if (bbox) {
+        doc->fitToRect(*bbox);
+        return true;
+    } else {
+        return false;
     }
-};
+}
+
+void
+verb_fit_canvas_to_drawing(SPDesktop *desktop)
+{
+    if (fit_canvas_to_drawing(sp_desktop_document(desktop))) {
+        sp_document_done(sp_desktop_document(desktop), SP_VERB_FIT_CANVAS_TO_DRAWING,
+                         _("Fit Page to Drawing"));
+    }
+}
 
 void fit_canvas_to_selection_or_drawing(SPDesktop *desktop) {
     g_return_if_fail(desktop != NULL);
@@ -2860,14 +2977,13 @@ void fit_canvas_to_selection_or_drawing(SPDesktop *desktop) {
     g_return_if_fail(doc != NULL);
     g_return_if_fail(desktop->selection != NULL);
 
-    if (desktop->selection->isEmpty()) {
-        fit_canvas_to_drawing(doc);
-    } else {
-        fit_canvas_to_selection(desktop);
+    bool const changed = ( desktop->selection->isEmpty()
+                           ? fit_canvas_to_drawing(doc)
+                           : fit_canvas_to_selection(desktop) );
+    if (changed) {
+        sp_document_done(sp_desktop_document(desktop), SP_VERB_FIT_CANVAS_TO_SELECTION_OR_DRAWING,
+                         _("Fit Page to Selection or Drawing"));
     }
-
-    sp_document_done(doc, SP_VERB_FIT_CANVAS_TO_DRAWING, 
-                     _("Fit page to selection"));
 };
 
 static void itemtree_map(void (*f)(SPItem *, SPDesktop *), SPObject *root, SPDesktop *desktop) {
@@ -2883,7 +2999,7 @@ static void itemtree_map(void (*f)(SPItem *, SPDesktop *), SPObject *root, SPDes
     }
 }
 
-static void unlock(SPItem *item, SPDesktop *desktop) {
+static void unlock(SPItem *item, SPDesktop */*desktop*/) {
     if (item->isLocked()) {
         item->setLocked(FALSE);
     }
@@ -2897,14 +3013,14 @@ static void unhide(SPItem *item, SPDesktop *desktop) {
 
 static void process_all(void (*f)(SPItem *, SPDesktop *), SPDesktop *dt, bool layer_only) {
     if (!dt) return;
-        
+
     SPObject *root;
     if (layer_only) {
         root = dt->currentLayer();
     } else {
         root = dt->currentRoot();
     }
-    
+
     itemtree_map(f, root, dt);
 }
 
@@ -2924,6 +3040,7 @@ void unhide_all_in_all_layers(SPDesktop *dt) {
     process_all(&unhide, dt, false);
 }
 
+
 /*
   Local Variables:
   mode:c++
@@ -2933,4 +3050,4 @@ void unhide_all_in_all_layers(SPDesktop *dt) {
   fill-column:99
   End:
 */
-// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :