Code

Refactoring of 3D box tool, mainly to avoid unnecessary creation of perspectives.
[inkscape.git] / src / selection.cpp
index 87e0788d888e042b7cb452f604d57c0b1df0f1d8..ed8a9d57b86034dfcaf4c36915bace17a5ff3671 100644 (file)
@@ -5,7 +5,9 @@
  *   Lauris Kaplinski <lauris@kaplinski.com>
  *   MenTaLguY <mental@rydia.net>
  *   bulia byak <buliabyak@users.sf.net>
+ *   Andrius R. <knutux@gmail.com>
  *
+ * Copyright (C)      2006 Andrius R.
  * Copyright (C) 2004-2005 MenTaLguY
  * Copyright (C) 1999-2002 Lauris Kaplinski
  * Copyright (C) 2001-2002 Ximian, Inc.
 #include "desktop-handles.h"
 #include "document.h"
 #include "selection.h"
+#include "helper/recthull.h"
 #include "xml/repr.h"
 
 #include "sp-shape.h"
+#include "sp-path.h"
+#include "sp-item-group.h"
+#include "box3d.h"
+#include "box3d.h"
+#include "persp3d.h"
 
+#include <sigc++/functors/mem_fun.h>
 
 #define SP_SELECTION_UPDATE_PRIORITY (G_PRIORITY_HIGH_IDLE + 1)
 
@@ -36,6 +45,7 @@ Selection::Selection(SPDesktop *desktop) :
     _reprs(NULL),
     _items(NULL),
     _desktop(desktop),
+    _selection_context(NULL),
     _flags(0),
     _idle(0)
 {
@@ -50,24 +60,16 @@ Selection::~Selection() {
     }
 }
 
-void
-Selection::_release(SPObject *obj, Selection *selection)
-{
-    selection->remove(obj);
-}
-
 /* Handler for selected objects "modified" signal */
 
-void
-Selection::_schedule_modified(SPObject *obj, guint flags, Selection *selection)
-{
-    if (!selection->_idle) {
+void Selection::_schedule_modified(SPObject */*obj*/, guint flags) {
+    if (!this->_idle) {
         /* Request handling to be run in _idle loop */
-        selection->_idle = g_idle_add_full(SP_SELECTION_UPDATE_PRIORITY, GSourceFunc(&Selection::_emit_modified), selection, NULL);
+        this->_idle = g_idle_add_full(SP_SELECTION_UPDATE_PRIORITY, GSourceFunc(&Selection::_emit_modified), this, NULL);
     }
 
     /* Collect all flags */
-    selection->_flags |= flags;
+    this->_flags |= flags;
 }
 
 gboolean
@@ -89,11 +91,33 @@ void Selection::_emitModified(guint flags) {
     _modified_signal.emit(this, flags);
 }
 
-void Selection::_emitChanged() {
+void Selection::_emitChanged(bool persist_selection_context/* = false */) {
+    if (persist_selection_context) {
+        if (NULL == _selection_context) {
+            _selection_context = desktop()->currentLayer();
+            sp_object_ref(_selection_context, NULL);
+            _context_release_connection = _selection_context->connectRelease(sigc::mem_fun(*this, &Selection::_releaseContext));
+        }
+    } else {
+        _releaseContext(_selection_context);
+    }
+
     inkscape_selection_changed(this);
     _changed_signal.emit(this);
 }
 
+void
+Selection::_releaseContext(SPObject *obj)
+{
+    if (NULL == _selection_context || _selection_context != obj)
+        return;
+
+    _context_release_connection.disconnect();
+
+    sp_object_unref(_selection_context, NULL);
+    _selection_context = NULL;
+}
+
 void Selection::_invalidateCachedLists() {
     g_slist_free(_items);
     _items = NULL;
@@ -106,11 +130,16 @@ void Selection::_clear() {
     _invalidateCachedLists();
     while (_objs) {
         SPObject *obj=reinterpret_cast<SPObject *>(_objs->data);
-        sp_signal_disconnect_by_data(obj, this);
-        _objs = g_slist_remove(_objs, obj);
+        _remove(obj);
     }
 }
 
+SPObject *Selection::activeContext() {
+    if (NULL != _selection_context)
+        return _selection_context;
+    return desktop()->currentLayer();
+    }
+
 bool Selection::includes(SPObject *obj) const {
     if (obj == NULL)
         return FALSE;
@@ -120,7 +149,7 @@ bool Selection::includes(SPObject *obj) const {
     return ( g_slist_find(_objs, obj) != NULL );
 }
 
-void Selection::add(SPObject *obj) {
+void Selection::add(SPObject *obj, bool persist_selection_context/* = false */) {
     g_return_if_fail(obj != NULL);
     g_return_if_fail(SP_IS_OBJECT(obj));
 
@@ -130,7 +159,28 @@ void Selection::add(SPObject *obj) {
 
     _invalidateCachedLists();
     _add(obj);
-    _emitChanged();
+    _emitChanged(persist_selection_context);
+}
+
+void Selection::add_box_perspective(SPBox3D *box) {
+    Persp3D *persp = box3d_get_perspective(box);
+    std::map<Persp3D *, unsigned int>::iterator p = _persps.find(persp);
+    if (p != _persps.end()) {
+        (*p).second++;
+    } else {
+        _persps[persp] = 1;
+    }
+}
+
+void Selection::add_3D_boxes_recursively(SPObject *obj) {
+    std::list<SPBox3D *> boxes = box3d_extract_boxes(obj);
+
+    for (std::list<SPBox3D *>::iterator i = boxes.begin(); i != boxes.end(); ++i) {
+        SPBox3D *box = *i;
+        box3d_add_to_selection(box);
+        _3dboxes.push_back(box);
+        add_box_perspective(box);
+    }
 }
 
 void Selection::_add(SPObject *obj) {
@@ -140,21 +190,16 @@ void Selection::_add(SPObject *obj) {
     _removeObjectAncestors(obj);
 
     _objs = g_slist_prepend(_objs, obj);
-    g_signal_connect(G_OBJECT(obj), "release",
-                     G_CALLBACK(&Selection::_release), this);
-    g_signal_connect(G_OBJECT(obj), "modified",
-                     G_CALLBACK(&Selection::_schedule_modified), this);
 
-    /*
-    if (!SP_IS_SHAPE(obj)) {
-        printf("This is not a shape\n");
-    }
-    */
+    add_3D_boxes_recursively(obj);
+
+    _release_connections[obj] = obj->connectRelease(sigc::mem_fun(*this, (void (Selection::*)(SPObject *))&Selection::remove));
+    _modified_connections[obj] = obj->connectModified(sigc::mem_fun(*this, &Selection::_schedule_modified));
 }
 
-void Selection::set(SPObject *object) {
+void Selection::set(SPObject *object, bool persist_selection_context) {
     _clear();
-    add(object);
+    add(object, persist_selection_context);
 }
 
 void Selection::toggle(SPObject *obj) {
@@ -175,8 +220,45 @@ void Selection::remove(SPObject *obj) {
     _emitChanged();
 }
 
+void Selection::remove_box_perspective(SPBox3D *box) {
+    Persp3D *persp = box3d_get_perspective(box);
+    std::map<Persp3D *, unsigned int>::iterator p = _persps.find(persp);
+    if (p == _persps.end()) {
+        //g_print ("Warning! Trying to remove unselected perspective from selection!\n");
+        return;
+    }
+    if ((*p).second > 1) {
+        _persps[persp]--;
+    } else {
+        _persps.erase(p);
+    }
+}
+
+void Selection::remove_3D_boxes_recursively(SPObject *obj) {
+    std::list<SPBox3D *> boxes = box3d_extract_boxes(obj);
+
+    for (std::list<SPBox3D *>::iterator i = boxes.begin(); i != boxes.end(); ++i) {
+        SPBox3D *box = *i;
+        box3d_remove_from_selection(box);
+        std::list<SPBox3D *>::iterator b = std::find(_3dboxes.begin(), _3dboxes.end(), box);
+        if (b == _3dboxes.end()) {
+            g_print ("Warning! Trying to remove unselected box from selection.\n");
+            return;
+        }
+        _3dboxes.erase(b);
+        remove_box_perspective(box);
+    }
+}
+
 void Selection::_remove(SPObject *obj) {
-    sp_signal_disconnect_by_data(obj, this);
+    _modified_connections[obj].disconnect();
+    _modified_connections.erase(obj);
+
+    _release_connections[obj].disconnect();
+    _release_connections.erase(obj);
+
+    remove_3D_boxes_recursively(obj);
+
     _objs = g_slist_remove(_objs, obj);
 }
 
@@ -260,6 +342,18 @@ GSList const *Selection::reprList() {
     return _reprs;
 }
 
+std::list<Persp3D *> const Selection::perspList() {
+    std::list<Persp3D *> pl;
+    for (std::map<Persp3D *, unsigned int>::iterator p = _persps.begin(); p != _persps.end(); ++p) {
+        pl.push_back((*p).first);
+    }
+    return pl;
+}
+
+std::list<SPBox3D *> const Selection::box3DList() {
+    return _3dboxes;
+}
+
 SPObject *Selection::single() {
     if ( _objs != NULL && _objs->next == NULL ) {
         return reinterpret_cast<SPObject *>(_objs->data);
@@ -282,36 +376,25 @@ Inkscape::XML::Node *Selection::singleRepr() {
     return obj ? SP_OBJECT_REPR(obj) : NULL;
 }
 
-NRRect *Selection::bounds(NRRect *bbox) const
+NRRect *Selection::bounds(NRRect *bbox, SPItem::BBoxType type) const
 {
     g_return_val_if_fail (bbox != NULL, NULL);
-    NR::Rect const b = bounds();
-    bbox->x0 = b.min()[NR::X];
-    bbox->y0 = b.min()[NR::Y];
-    bbox->x1 = b.max()[NR::X];
-    bbox->y1 = b.max()[NR::Y];
+    *bbox = NRRect(bounds(type));
     return bbox;
 }
 
-NR::Rect Selection::bounds() const
+Geom::OptRect Selection::bounds(SPItem::BBoxType type) const
 {
     GSList const *items = const_cast<Selection *>(this)->itemList();
-    if (!items) {
-        return NR::Rect(NR::Point(0, 0), NR::Point(0, 0));
-    }
 
-    GSList const *i = items;
-    NR::Rect bbox = sp_item_bbox_desktop(SP_ITEM(i->data));
-
-    while (i != NULL) {
-        bbox = NR::Rect::union_bounds(bbox, sp_item_bbox_desktop(SP_ITEM(i->data)));
-        i = i->next;
+    Geom::OptRect bbox;
+    for ( GSList const *i = items ; i != NULL ; i = i->next ) {
+        bbox = unify(bbox, sp_item_bbox_desktop(SP_ITEM(i->data), type));
     }
-
     return bbox;
 }
 
-NRRect *Selection::boundsInDocument(NRRect *bbox) const {
+NRRect *Selection::boundsInDocument(NRRect *bbox, SPItem::BBoxType type) const {
     g_return_val_if_fail (bbox != NULL, NULL);
 
     GSList const *items=const_cast<Selection *>(this)->itemList();
@@ -325,65 +408,86 @@ NRRect *Selection::boundsInDocument(NRRect *bbox) const {
 
     for ( GSList const *iter=items ; iter != NULL ; iter = iter->next ) {
         SPItem *item=SP_ITEM(iter->data);
-        NR::Matrix const i2doc(sp_item_i2doc_affine(item));
-        sp_item_invoke_bbox(item, bbox, i2doc, FALSE);
+        Geom::Matrix i2doc(sp_item_i2doc_affine(item));
+        sp_item_invoke_bbox(item, bbox, i2doc, FALSE, type);
     }
 
     return bbox;
 }
 
-NR::Rect Selection::boundsInDocument() const {
+Geom::OptRect Selection::boundsInDocument(SPItem::BBoxType type) const {
     NRRect r;
-    return NR::Rect(*boundsInDocument(&r));
+    return to_2geom(boundsInDocument(&r, type)->upgrade());
+}
+
+/** Extract the position of the center from the first selected object */
+boost::optional<Geom::Point> Selection::center() const {
+    GSList *items = (GSList *) const_cast<Selection *>(this)->itemList();
+    Geom::Point center;
+    if (items) {
+        SPItem *first = reinterpret_cast<SPItem*>(g_slist_last(items)->data); // from the first item in selection
+        if (first->isCenterSet()) { // only if set explicitly
+            return first->getCenter();
+        }
+    }
+    Geom::OptRect bbox = bounds();
+    if (bbox) {
+        return bounds()->midpoint();
+    } else {
+        return boost::optional<Geom::Point>();
+    }
 }
 
 /**
  * Compute the list of points in the selection that are to be considered for snapping.
  */
-std::vector<NR::Point> Selection::getSnapPoints() const {
+std::vector<std::pair<Geom::Point, int> > Selection::getSnapPoints(SnapPreferences const *snapprefs) const {
     GSList const *items = const_cast<Selection *>(this)->itemList();
-    std::vector<NR::Point> p;
+
+    SnapPreferences snapprefs_dummy = *snapprefs; // create a local copy of the snapping prefs
+    snapprefs_dummy.setIncludeItemCenter(false); // locally disable snapping to the item center
+
+    std::vector<std::pair<Geom::Point, int> > p;
     for (GSList const *iter = items; iter != NULL; iter = iter->next) {
-        sp_item_snappoints(SP_ITEM(iter->data), SnapPointsIter(p));
+        SPItem *this_item = SP_ITEM(iter->data);
+        sp_item_snappoints(this_item, false, p, &snapprefs_dummy);
+
+        //Include the transformation origin for snapping
+        //For a selection or group only the overall origin is considered
+        if (snapprefs != NULL && snapprefs->getIncludeItemCenter()) {
+               p.push_back(std::make_pair(this_item->getCenter(), SNAPSOURCE_ROTATION_CENTER));
+        }
     }
 
     return p;
 }
 
-std::vector<NR::Point> Selection::getSnapPointsConvexHull() const {
+std::vector<std::pair<Geom::Point, int> > Selection::getSnapPointsConvexHull(SnapPreferences const *snapprefs) const {
     GSList const *items = const_cast<Selection *>(this)->itemList();
-    std::vector<NR::Point> p;
-    for (GSList const *iter = items; iter != NULL; iter = iter->next) {
-       sp_item_snappoints(SP_ITEM(iter->data), SnapPointsIter(p));
-    }
 
-    std::vector<NR::Point>::iterator i;
-    NR::ConvexHull cvh(*(p.begin()));
-    for (i = p.begin(); i != p.end(); i++) {
-       // these are the points we get back
-       cvh.add(*i);
+    std::vector<std::pair<Geom::Point, int> > p;
+    for (GSList const *iter = items; iter != NULL; iter = iter->next) {
+               sp_item_snappoints(SP_ITEM(iter->data), false, p, snapprefs);
     }
 
-    NR::Rect rHull = cvh.bounds();
-    std::vector<NR::Point> pHull(4);
-    pHull[0] = rHull.corner(0);
-    pHull[1] = rHull.corner(1);
-    pHull[2] = rHull.corner(2);
-    pHull[3] = rHull.corner(3);
-
-    return pHull;
-}
+    std::vector<std::pair<Geom::Point, int> > pHull;
+    if (!p.empty()) {
+       std::vector<std::pair<Geom::Point, int> >::iterator i;
+        Geom::RectHull cvh((p.front()).first);
+        for (i = p.begin(); i != p.end(); i++) {
+            // these are the points we get back
+            cvh.add((*i).first);
+        }
 
-std::vector<NR::Point> Selection::getBBoxPoints() const {
-    GSList const *items = const_cast<Selection *>(this)->itemList();
-    std::vector<NR::Point> p;
-    for (GSList const *iter = items; iter != NULL; iter = iter->next) {
-        NR::Rect b = sp_item_bbox_desktop(SP_ITEM(iter->data));
-        p.push_back(b.min());
-        p.push_back(b.max());
+        Geom::OptRect rHull = cvh.bounds();
+        if (rHull) {
+            for ( unsigned i = 0 ; i < 4 ; ++i ) {
+                pHull.push_back(std::make_pair(rHull->corner(i), SNAPSOURCE_CONVEX_HULL_CORNER));
+            }
+        }
     }
 
-    return p;
+    return pHull;
 }
 
 void Selection::_removeObjectDescendants(SPObject *obj) {
@@ -416,37 +520,37 @@ SPObject *Selection::_objectForXMLNode(Inkscape::XML::Node *repr) const {
     g_return_val_if_fail(repr != NULL, NULL);
     gchar const *id = repr->attribute("id");
     g_return_val_if_fail(id != NULL, NULL);
-    SPObject *object=SP_DT_DOCUMENT(_desktop)->getObjectById(id);
+    SPObject *object=sp_desktop_document(_desktop)->getObjectById(id);
     g_return_val_if_fail(object != NULL, NULL);
     return object;
 }
 
 guint Selection::numberOfLayers() {
-      GSList const *items = const_cast<Selection *>(this)->itemList();
-       GSList *layers = NULL;
-       for (GSList const *iter = items; iter != NULL; iter = iter->next) {
-               SPObject *layer = desktop()->layerForObject(SP_OBJECT(iter->data));
-               if (g_slist_find (layers, layer) == NULL) {
-                       layers = g_slist_prepend (layers, layer);
-               }
-       }
-       guint ret = g_slist_length (layers);
-       g_slist_free (layers);
-       return ret;
+    GSList const *items = const_cast<Selection *>(this)->itemList();
+    GSList *layers = NULL;
+    for (GSList const *iter = items; iter != NULL; iter = iter->next) {
+        SPObject *layer = desktop()->layerForObject(SP_OBJECT(iter->data));
+        if (g_slist_find (layers, layer) == NULL) {
+            layers = g_slist_prepend (layers, layer);
+        }
+    }
+    guint ret = g_slist_length (layers);
+    g_slist_free (layers);
+    return ret;
 }
 
 guint Selection::numberOfParents() {
-      GSList const *items = const_cast<Selection *>(this)->itemList();
-       GSList *parents = NULL;
-       for (GSList const *iter = items; iter != NULL; iter = iter->next) {
-               SPObject *parent = SP_OBJECT_PARENT(iter->data);
-               if (g_slist_find (parents, parent) == NULL) {
-                       parents = g_slist_prepend (parents, parent);
-               }
-       }
-       guint ret = g_slist_length (parents);
-       g_slist_free (parents);
-       return ret;
+    GSList const *items = const_cast<Selection *>(this)->itemList();
+    GSList *parents = NULL;
+    for (GSList const *iter = items; iter != NULL; iter = iter->next) {
+        SPObject *parent = SP_OBJECT_PARENT(iter->data);
+        if (g_slist_find (parents, parent) == NULL) {
+            parents = g_slist_prepend (parents, parent);
+        }
+    }
+    guint ret = g_slist_length (parents);
+    g_slist_free (parents);
+    return ret;
 }
 
 }