Code

now that selection description includes style (filtered, clipped), we need to update...
[inkscape.git] / src / selection-chemistry.cpp
index 08449720d627800fb3d2c9abc4bbb0db7cf9ee48..badf27ecb45caa4d93541f5e806a371f43b66fd9 100644 (file)
@@ -82,6 +82,7 @@
 #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"
@@ -91,8 +92,8 @@
 
 #include "ui/clipboard.h"
 
-using NR::X;
-using NR::Y;
+using Geom::X;
+using Geom::Y;
 
 /* 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
@@ -102,7 +103,7 @@ 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, Inkscape::XML::Document* xml_doc)
+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(xml_doc);
 
@@ -147,8 +148,8 @@ GSList *sp_selection_paste_impl (SPDocument *doc, SPObject *parent, GSList **cli
         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");
             Geom::Matrix item_t (Geom::identity());
             if (t_str)
@@ -593,6 +594,36 @@ void sp_selection_ungroup(SPDesktop *desktop)
                      _("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(GSList const *items)
 {
@@ -614,12 +645,12 @@ sp_item_list_common_parent_group(GSList const *items)
 }
 
 /** Finds out the minimum common bbox of the selected items. */
-static boost::optional<Geom::Rect>
+static Geom::OptRect
 enclose_items(GSList const *items)
 {
     g_assert(items != NULL);
 
-    boost::optional<Geom::Rect> r;
+    Geom::OptRect r;
     for (GSList const *i = items; i; i = i->next) {
         r = Geom::unify(r, sp_item_bbox_desktop((SPItem *) i->data));
     }
@@ -667,7 +698,7 @@ sp_selection_raise(SPDesktop *desktop)
     rev = g_slist_sort(rev, (GCompareFunc) sp_item_repr_compare_position);
 
     // Determine the common bbox of the selected items.
-    boost::optional<Geom::Rect> selected = enclose_items(items);
+    Geom::OptRect selected = enclose_items(items);
 
     // Iterate over all objects in the selection (starting from top).
     if (selected) {
@@ -677,7 +708,7 @@ sp_selection_raise(SPDesktop *desktop)
             for (SPObject *newref = child->next; newref; newref = newref->next) {
                 // if the sibling is an item AND overlaps our selection,
                 if (SP_IS_ITEM(newref)) {
-                    boost::optional<Geom::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)) {
@@ -757,7 +788,7 @@ sp_selection_lower(SPDesktop *desktop)
     Inkscape::XML::Node *grepr = SP_OBJECT_REPR(group);
 
     // Determine the common bbox of the selected items.
-    boost::optional<Geom::Rect> selected = enclose_items(items);
+    Geom::OptRect selected = enclose_items(items);
 
     /* Construct direct-ordered list of selected children. */
     GSList *rev = g_slist_copy((GSList *) items);
@@ -772,7 +803,7 @@ sp_selection_lower(SPDesktop *desktop)
             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)) {
-                    boost::optional<Geom::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)) {
@@ -1216,9 +1247,9 @@ void sp_selection_apply_affine(Inkscape::Selection *selection, Geom::Matrix cons
             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
-            Geom::Matrix parent_transform = sp_item_i2root_affine(SP_ITEM(SP_OBJECT_PARENT (item)));
-            Geom::Matrix t = parent_transform * matrix_to_desktop (matrix_from_desktop (affine, item), item) * parent_transform.inverse();
-            Geom::Matrix t_inv =parent_transform * matrix_to_desktop (matrix_from_desktop (affine.inverse(), item), item) * parent_transform.inverse();
+            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.isTranslation()) {
@@ -1234,7 +1265,7 @@ void sp_selection_apply_affine(Inkscape::Selection *selection, Geom::Matrix cons
 
                 } else if (prefs_unmoved) {
                     //if (SP_IS_USE(sp_use_get_original(SP_USE(item))))
-                    //    clone_move = NR::identity();
+                    //    clone_move = Geom::identity();
                     Geom::Matrix move = result * clone_move;
                     sp_item_write_transform(item, SP_OBJECT_REPR(item), move, &t);
                 }
@@ -1285,8 +1316,8 @@ sp_selection_scale_absolute(Inkscape::Selection *selection,
     if (selection->isEmpty())
         return;
 
-    boost::optional<Geom::Rect> const bbox(selection->bounds());
-    if ( !bbox || bbox->isEmpty() ) {
+    Geom::OptRect const bbox(selection->bounds());
+    if ( !bbox ) {
         return;
     }
 
@@ -1296,7 +1327,7 @@ sp_selection_scale_absolute(Inkscape::Selection *selection,
                             y1 - y0);
     Geom::Scale const scale( newSize * Geom::Scale(bbox->dimensions()).inverse() );
     Geom::Translate const o2n(x0, y0);
-    NR::Matrix const final( p2o * scale * o2n );
+    Geom::Matrix const final( p2o * scale * o2n );
 
     sp_selection_apply_affine(selection, final);
 }
@@ -1307,15 +1338,15 @@ void sp_selection_scale_relative(Inkscape::Selection *selection, Geom::Point con
     if (selection->isEmpty())
         return;
 
-    boost::optional<Geom::Rect> const bbox(selection->bounds());
+    Geom::OptRect const bbox(selection->bounds());
 
-    if ( !bbox || bbox->isEmpty() ) {
+    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->dimensions()[NR::X] * scale[Geom::X] > 1e6  ||
-         bbox->dimensions()[NR::Y] * scale[Geom::Y] > 1e6 )
+    if ( bbox->dimensions()[Geom::X] * scale[Geom::X] > 1e6  ||
+         bbox->dimensions()[Geom::Y] * scale[Geom::Y] > 1e6 )
     {
         return;
     }
@@ -1350,12 +1381,12 @@ sp_selection_skew_relative(Inkscape::Selection *selection, Geom::Point const &al
 
 void sp_selection_move_relative(Inkscape::Selection *selection, Geom::Point const &move)
 {
-    sp_selection_apply_affine(selection, NR::Matrix(Geom::Translate(move)));
+    sp_selection_apply_affine(selection, Geom::Matrix(Geom::Translate(move)));
 }
 
 void sp_selection_move_relative(Inkscape::Selection *selection, double dx, double dy)
 {
-    sp_selection_apply_affine(selection, NR::Matrix(Geom::Translate(dx, dy)));
+    sp_selection_apply_affine(selection, Geom::Matrix(Geom::Translate(dx, dy)));
 }
 
 /**
@@ -1369,7 +1400,7 @@ void sp_selection_rotate_90(SPDesktop *desktop, bool ccw)
         return;
 
     GSList const *l = selection->itemList();
-    Geom::Rotate const rot_90(NR::Point(0, ccw ? 1 : -1)); // pos. or neg. rotation, depending on the value of ccw
+    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_90);
@@ -1425,7 +1456,7 @@ sp_selection_rotate_screen(Inkscape::Selection *selection, gdouble angle)
     if (selection->isEmpty())
         return;
 
-    boost::optional<Geom::Rect> const bbox(selection->bounds());
+    Geom::OptRect const bbox(selection->bounds());
     boost::optional<Geom::Point> center = selection->center();
 
     if ( !bbox || !center ) {
@@ -1454,7 +1485,7 @@ sp_selection_scale(Inkscape::Selection *selection, gdouble grow)
     if (selection->isEmpty())
         return;
 
-    boost::optional<Geom::Rect> const bbox(selection->bounds());
+    Geom::OptRect const bbox(selection->bounds());
     if (!bbox) {
         return;
     }
@@ -1491,7 +1522,7 @@ sp_selection_scale_times(Inkscape::Selection *selection, gdouble times)
     if (selection->isEmpty())
         return;
 
-    boost::optional<Geom::Rect> sel_bbox = selection->bounds();
+    Geom::OptRect sel_bbox = selection->bounds();
 
     if (!sel_bbox) {
         return;
@@ -1701,7 +1732,7 @@ void sp_selection_edit_clip_or_mask(SPDesktop * dt, bool clip)
                             tools_switch(dt, TOOLS_NODES);
                         }
 
-                        ShapeEditor * shape_editor = SP_NODE_CONTEXT( dt->event_context )->shape_editor;
+                        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);
@@ -1815,7 +1846,7 @@ SPItem *next_item(SPDesktop *desktop, GSList *path, SPObject *root,
 void scroll_to_show_item(SPDesktop *desktop, SPItem *item)
 {
     Geom::Rect dbox = desktop->get_display_area();
-    boost::optional<Geom::Rect> sbox = sp_item_bbox_desktop(item);
+    Geom::OptRect sbox = sp_item_bbox_desktop(item);
 
     if ( sbox && dbox.contains(*sbox) == false ) {
         Geom::Point const s_dt = sbox->midpoint();
@@ -2046,8 +2077,8 @@ sp_select_clone_original(SPDesktop *desktop)
         Inkscape::Preferences *prefs = Inkscape::Preferences::get();
         bool highlight = prefs->getBool("/options/highlightoriginal/value");
         if (highlight) {
-            boost::optional<Geom::Rect> a = item->getBounds(sp_item_i2d_affine(item));
-            boost::optional<Geom::Rect> b = original->getBounds(sp_item_i2d_affine(original));
+            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();
@@ -2088,9 +2119,9 @@ void sp_selection_to_marker(SPDesktop *desktop, bool apply)
     }
 
     sp_document_ensure_up_to_date(doc);
-    boost::optional<Geom::Rect> r = selection->bounds();
+    Geom::OptRect r = selection->bounds();
     boost::optional<Geom::Point> c = selection->center();
-    if ( !r || !c || r->isEmpty() ) {
+    if ( !r || !c ) {
         return;
     }
 
@@ -2106,7 +2137,7 @@ void sp_selection_to_marker(SPDesktop *desktop, bool apply)
     // bottommost object, after sorting
     SPObject *parent = SP_OBJECT_PARENT (items->data);
 
-    Geom::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();
@@ -2212,15 +2243,15 @@ sp_selection_tile(SPDesktop *desktop, bool apply)
     }
 
     sp_document_ensure_up_to_date(doc);
-    boost::optional<Geom::Rect> r = selection->bounds();
-    if ( !r || r->isEmpty() ) {
+    Geom::OptRect r = selection->bounds();
+    if ( !r ) {
         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)) - (r->min() + Geom::Point (0, r->dimensions()[NR::Y]));
+    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];
-    NR::Matrix move = NR::Matrix (Geom::Translate (move_p));
+    Geom::Matrix move = Geom::Matrix (Geom::Translate (move_p));
 
     GSList *items = g_slist_copy((GSList *) selection->itemList());
 
@@ -2229,7 +2260,7 @@ sp_selection_tile(SPDesktop *desktop, bool apply)
     // bottommost object, after sorting
     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();
@@ -2243,7 +2274,7 @@ sp_selection_tile(SPDesktop *desktop, bool apply)
     // 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
@@ -2261,9 +2292,9 @@ sp_selection_tile(SPDesktop *desktop, bool apply)
     prefs->setInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_UNMOVED);
 
     gchar const *pat_id = pattern_tile(repr_copies, bounds, doc,
-                                       ( NR::Matrix(Geom::Translate(desktop->dt2doc(NR::Point(r->min()[NR::X],
-                                                                                            r->max()[NR::Y]))))
-                                         * parent_transform.inverse() ),
+                                       ( 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
@@ -2273,13 +2304,13 @@ sp_selection_tile(SPDesktop *desktop, bool 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);
@@ -2486,8 +2517,16 @@ sp_selection_create_bitmap_copy (SPDesktop *desktop)
     // 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);
+
+    // 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, filename, NULL);
 
     //g_print ("%s\n", filepath);
 
@@ -2731,7 +2770,7 @@ sp_selection_set_mask(SPDesktop *desktop, bool apply_clip_path, bool apply_to_la
         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) {
@@ -2841,7 +2880,7 @@ void sp_selection_unset_mask(SPDesktop *desktop, bool apply_clip_path) {
             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);
         }
@@ -2871,8 +2910,8 @@ fit_canvas_to_selection(SPDesktop *desktop)
         desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to fit canvas to."));
         return false;
     }
-    boost::optional<Geom::Rect> const bbox(desktop->selection->bounds());
-    if (bbox && !bbox->isEmpty()) {
+    Geom::OptRect const bbox(desktop->selection->bounds());
+    if (bbox) {
         doc->fitToRect(*bbox);
         return true;
     } else {
@@ -2899,8 +2938,8 @@ fit_canvas_to_drawing(SPDocument *doc)
 
     sp_document_ensure_up_to_date(doc);
     SPItem const *const root = SP_ITEM(doc->root);
-    boost::optional<Geom::Rect> const bbox(root->getBounds(sp_item_i2r_affine(root)));
-    if (bbox && !bbox->isEmpty()) {
+    Geom::OptRect const bbox(root->getBounds(sp_item_i2d_affine(root)));
+    if (bbox) {
         doc->fitToRect(*bbox);
         return true;
     } else {