diff options
Diffstat (limited to 'src/object/object-set.cpp')
-rw-r--r-- | src/object/object-set.cpp | 521 |
1 files changed, 521 insertions, 0 deletions
diff --git a/src/object/object-set.cpp b/src/object/object-set.cpp new file mode 100644 index 0000000..6952852 --- /dev/null +++ b/src/object/object-set.cpp @@ -0,0 +1,521 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Multiindex container for selection + * + * Authors: + * Adrian Boguszewski + * + * Copyright (C) 2016 Adrian Boguszewski + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "object-set.h" + +#include <boost/range/adaptor/filtered.hpp> +#include <boost/range/adaptor/transformed.hpp> +#include <glib.h> +#include <sigc++/sigc++.h> + +#include "box3d.h" +#include "persp3d.h" +#include "preferences.h" + +namespace Inkscape { + +bool ObjectSet::add(SPObject* object, bool nosignal) { + g_return_val_if_fail(object != nullptr, false); + g_return_val_if_fail(SP_IS_OBJECT(object), false); + + // any ancestor is in the set - do nothing + if (_anyAncestorIsInSet(object)) { + return false; + } + + // very nice function, but changes selection behavior (probably needs new selection option to deal with it) + // check if there is mutual ancestor for some elements, which can replace all of them in the set +// object = _getMutualAncestor(object); + + // remove all descendants from the set + _removeDescendantsFromSet(object); + + _add(object); + if (!nosignal) + _emitChanged(); + return true; +} + +void ObjectSet::add(XML::Node *repr) +{ + if (document() && repr) { + SPObject *obj = document()->getObjectByRepr(repr); + assert(obj == document()->getObjectById(repr->attribute("id"))); + add(obj); + } +} + +bool ObjectSet::remove(SPObject* object) { + g_return_val_if_fail(object != nullptr, false); + g_return_val_if_fail(SP_IS_OBJECT(object), false); + + // object is the top of subtree + if (includes(object)) { + _remove(object); + _emitChanged(); + return true; + } + + // any ancestor of object is in the set + if (_anyAncestorIsInSet(object)) { + _removeAncestorsFromSet(object); + _emitChanged(); + return true; + } + + // no object nor any parent in the set + return false; +} + +void ObjectSet::_emitChanged(bool persist_selection_context /*= false*/) { + _sibling_state.clear(); +} + +bool ObjectSet::includes(SPObject *object, bool anyAncestor) { + g_return_val_if_fail(object != nullptr, false); + g_return_val_if_fail(SP_IS_OBJECT(object), false); + if (anyAncestor) { + return _anyAncestorIsInSet(object); + } else { + return _container.get<hashed>().find(object) != _container.get<hashed>().end(); + } +} + +SPObject * +ObjectSet::includesAncestor(SPObject *object) { + g_return_val_if_fail(object != nullptr, nullptr); + g_return_val_if_fail(SP_IS_OBJECT(object), nullptr); + SPObject* o = object; + while (o != nullptr) { + if (includes(o)) { + return o; + } + o = o->parent; + } + return nullptr; +} + +void ObjectSet::clear() { + _clear(); + _emitChanged(); +} + +int ObjectSet::size() { + return _container.size(); +} + +bool ObjectSet::_anyAncestorIsInSet(SPObject *object) { + SPObject* o = object; + while (o != nullptr) { + if (includes(o)) { + return true; + } + o = o->parent; + } + + return false; +} + +void ObjectSet::_removeDescendantsFromSet(SPObject *object) { + for (auto& child: object->children) { + if (includes(&child)) { + _remove(&child); + // there is certainly no children of this child in the set + continue; + } + + _removeDescendantsFromSet(&child); + } +} + +void ObjectSet::_disconnect(SPObject *object) { + _releaseConnections[object].disconnect(); + _releaseConnections.erase(object); + _remove3DBoxesRecursively(object); + _releaseSignals(object); +} + +void ObjectSet::_remove(SPObject *object) { + _disconnect(object); + _container.get<hashed>().erase(object); +} + +void ObjectSet::_add(SPObject *object) { + _releaseConnections[object] = object->connectRelease(sigc::hide_return(sigc::mem_fun(*this, &ObjectSet::remove))); + _container.push_back(object); + _add3DBoxesRecursively(object); + _connectSignals(object); +} + +void ObjectSet::_clear() { + for (auto object: _container) + _disconnect(object); + _container.clear(); +} + +SPObject *ObjectSet::_getMutualAncestor(SPObject *object) { + SPObject *o = object; + + bool flag = true; + while (o->parent != nullptr) { + for (auto &child: o->parent->children) { + if(&child != o && !includes(&child)) { + flag = false; + break; + } + } + if (!flag) { + break; + } + o = o->parent; + } + return o; +} + +void ObjectSet::_removeAncestorsFromSet(SPObject *object) { + SPObject* o = object; + while (o->parent != nullptr) { + for (auto &child: o->parent->children) { + if (&child != o) { + _add(&child); + } + } + if (includes(o->parent)) { + _remove(o->parent); + break; + } + o = o->parent; + } +} + +ObjectSet::~ObjectSet() { + _clear(); +} + +void ObjectSet::toggle(SPObject *obj) { + if (includes(obj)) { + remove(obj); + } else { + add(obj); + } +} + +bool ObjectSet::isEmpty() { + return _container.size() == 0; +} + +SPObject *ObjectSet::single() { + return _container.size() == 1 ? *_container.begin() : nullptr; +} + +SPItem *ObjectSet::singleItem() { + if (_container.size() == 1) { + SPObject* obj = *_container.begin(); + if (SP_IS_ITEM(obj)) { + return SP_ITEM(obj); + } + } + + return nullptr; +} + +SPItem *ObjectSet::firstItem() const +{ + return _container.size() ? SP_ITEM(_container.front()) : nullptr; +} + +SPItem *ObjectSet::lastItem() const +{ + return _container.size() ? SP_ITEM(_container.back()) : nullptr; +} + +SPItem *ObjectSet::smallestItem(CompareSize compare) { + return _sizeistItem(true, compare); +} + +SPItem *ObjectSet::largestItem(CompareSize compare) { + return _sizeistItem(false, compare); +} + +SPItem *ObjectSet::_sizeistItem(bool sml, CompareSize compare) { + auto items = this->items(); + gdouble max = sml ? 1e18 : 0; + SPItem *ist = nullptr; + + for (auto *item : items) { + Geom::OptRect obox = item->documentPreferredBounds(); + if (!obox || obox.empty()) { + continue; + } + + Geom::Rect bbox = *obox; + + gdouble size = compare == AREA ? bbox.area() : + (compare == VERTICAL ? bbox.height() : bbox.width()); + size = sml ? size : size * -1; + if (size < max) { + max = size; + ist = item; + } + } + + return ist; +} + +SPObjectRange ObjectSet::objects() { + return SPObjectRange(_container.get<random_access>().begin(), _container.get<random_access>().end()); +} + +Inkscape::XML::Node *ObjectSet::singleRepr() { + SPObject *obj = single(); + return obj ? obj->getRepr() : nullptr; +} + +Inkscape::XML::Node *ObjectSet::topRepr() const +{ + auto const &nodes = const_cast<ObjectSet *>(this)->xmlNodes(); + + if (nodes.empty()) { + return nullptr; + } + +#ifdef _LIBCPP_VERSION + // workaround for + // static_assert(__is_cpp17_forward_iterator<_ForwardIterator>::value + auto const n = std::vector<Inkscape::XML::Node *>(nodes.begin(), nodes.end()); +#else + auto const& n = nodes; +#endif + + return *std::max_element(n.begin(), n.end(), sp_repr_compare_position_bool); +} + +void ObjectSet::set(SPObject *object, bool persist_selection_context) { + _clear(); + _add(object); + _emitChanged(persist_selection_context); +} + +void ObjectSet::set(XML::Node *repr) +{ + if (document() && repr) { + SPObject *obj = document()->getObjectByRepr(repr); + assert(obj == document()->getObjectById(repr->attribute("id"))); + set(obj); + } +} + +int ObjectSet::setBetween(SPObject *obj_a, SPObject *obj_b) +{ + auto parent = obj_a->parent; + if (!obj_b) + obj_b = lastItem(); + + if (!obj_a || !obj_b || parent != obj_b->parent) { + return 0; + } else if (obj_a == obj_b) { + set(obj_a); + return 1; + } + clear(); + + int count = 0; + int min = std::min(obj_a->getPosition(), obj_b->getPosition()); + int max = std::max(obj_a->getPosition(), obj_b->getPosition()); + for (int i = min ; i <= max ; i++) { + if (auto child = parent->nthChild(i)) { + count += add(child); + } + } + return count; +} + + +void ObjectSet::setReprList(std::vector<XML::Node*> const &list) { + if(!document()) + return; + clear(); + for (auto iter = list.rbegin(); iter != list.rend(); ++iter) { +#if 0 + // This can fail when pasting a clone into a new document + SPObject *obj = document()->getObjectByRepr(*iter); + assert(obj == document()->getObjectById((*iter)->attribute("id"))); +#else + SPObject *obj = document()->getObjectById((*iter)->attribute("id")); +#endif + if (obj) { + add(obj, true); + } + } + _emitChanged(); +} + +void ObjectSet::enforceIds() +{ + bool idAssigned = false; + auto items = this->items(); + for (auto *item : items) { + if (!item->getId()) { + // Selected object does not have an ID, so assign it a unique ID + gchar *id = sp_object_get_unique_id(item, nullptr); + item->setAttribute("id", id); + g_free(id); + idAssigned = true; + } + } + if (idAssigned) { + SPDocument *document = _desktop->getDocument(); + if (document) { + document->setModifiedSinceSave(true); + } + } +} + +Geom::OptRect ObjectSet::bounds(SPItem::BBoxType type) const +{ + return (type == SPItem::GEOMETRIC_BBOX) ? + geometricBounds() : visualBounds(); +} + +Geom::OptRect ObjectSet::geometricBounds() const +{ + auto items = const_cast<ObjectSet *>(this)->items(); + + Geom::OptRect bbox; + for (auto *item : items) { + bbox.unionWith(item->desktopGeometricBounds()); + } + return bbox; +} + +Geom::OptRect ObjectSet::visualBounds() const +{ + auto items = const_cast<ObjectSet *>(this)->items(); + + Geom::OptRect bbox; + for (auto *item : items) { + bbox.unionWith(item->desktopVisualBounds()); + } + return bbox; +} + +Geom::OptRect ObjectSet::strokedBounds() const +{ + auto items = const_cast<ObjectSet *>(this)->items(); + + Geom::OptRect bbox; + for (auto *item : items) { + bbox.unionWith(item->visualBounds(item->i2doc_affine(), false, true, true)); + } + if (bbox) { + *bbox *= _desktop->getDocument()->doc2dt(); + } + return bbox; +} + +Geom::OptRect ObjectSet::preferredBounds() const +{ + if (Inkscape::Preferences::get()->getInt("/tools/bounding_box") == 0) { + return bounds(SPItem::VISUAL_BBOX); + } else { + return bounds(SPItem::GEOMETRIC_BBOX); + } +} + +Geom::OptRect ObjectSet::documentBounds(SPItem::BBoxType type) const +{ + Geom::OptRect bbox; + auto items = const_cast<ObjectSet *>(this)->items(); + if (items.empty()) return bbox; + + for (auto *item : items) { + bbox |= item->documentBounds(type); + } + + return bbox; +} + +// If we have a selection of multiple items, then the center of the first item +// will be returned; this is also the case in SelTrans::centerRequest() +std::optional<Geom::Point> ObjectSet::center() const { + auto items = const_cast<ObjectSet *>(this)->items(); + if (!items.empty()) { + SPItem *first = items.back(); // from the first item in selection + if (first->isCenterSet()) { // only if set explicitly + return first->getCenter(); + } + } + Geom::OptRect bbox = preferredBounds(); + if (bbox) { + return bbox->midpoint(); + } else { + return std::optional<Geom::Point>(); + } +} + +std::list<Persp3D *> const ObjectSet::perspList() { + std::list<Persp3D *> pl; + for (auto & _3dboxe : _3dboxes) { + Persp3D *persp = _3dboxe->get_perspective(); + if (std::find(pl.begin(), pl.end(), persp) == pl.end()) + pl.push_back(persp); + } + return pl; +} + +std::list<SPBox3D *> const ObjectSet::box3DList(Persp3D *persp) { + std::list<SPBox3D *> boxes; + if (persp) { + for (auto box : _3dboxes) { + if (persp == box->get_perspective()) { + boxes.push_back(box); + } + } + } else { + boxes = _3dboxes; + } + return boxes; +} + +void ObjectSet::_add3DBoxesRecursively(SPObject *obj) { + std::list<SPBox3D *> boxes = SPBox3D::extract_boxes(obj); + + for (auto box : boxes) { + _3dboxes.push_back(box); + } +} + +void ObjectSet::_remove3DBoxesRecursively(SPObject *obj) { + std::list<SPBox3D *> boxes = SPBox3D::extract_boxes(obj); + + for (auto box : boxes) { + 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); + } +} + +} // namespace Inkscape + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : |