Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
object-set.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * Multiindex container for selection
4 *
5 * Authors:
6 * Adrian Boguszewski
7 *
8 * Copyright (C) 2016 Adrian Boguszewski
9 *
10 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
11 */
12
13#include "object-set.h"
14
15#include <boost/range/adaptor/filtered.hpp>
16#include <boost/range/adaptor/transformed.hpp>
17#include <glib.h>
18#include <sigc++/sigc++.h>
19
20#include "box3d.h"
21#include "persp3d.h"
22#include "preferences.h"
23
24#include "desktop.h"
25#include "document.h"
26
27namespace Inkscape {
28
30 : _desktop(desktop)
31{
32 if (desktop) {
34 }
35}
36
37bool ObjectSet::add(SPObject *object, bool nosignal)
38{
39 g_return_val_if_fail(object != nullptr, false);
40
41 // any ancestor is in the set - do nothing
42 if (_anyAncestorIsInSet(object)) {
43 return false;
44 }
45
46 // very nice function, but changes selection behavior (probably needs new selection option to deal with it)
47 // check if there is mutual ancestor for some elements, which can replace all of them in the set
48// object = _getMutualAncestor(object);
49
50 // remove all descendants from the set
52
53 _add(object);
54 if (!nosignal)
56 return true;
57}
58
60{
61 if (document() && repr) {
62 SPObject *obj = document()->getObjectByRepr(repr);
63 assert(obj == document()->getObjectById(repr->attribute("id")));
64 add(obj);
65 }
66}
67
69 g_return_val_if_fail(object != nullptr, false);
70
71 // object is the top of subtree
72 if (includes(object)) {
73 _remove(object);
75 return true;
76 }
77
78 // any ancestor of object is in the set
79 if (_anyAncestorIsInSet(object)) {
82 return true;
83 }
84
85 // no object nor any parent in the set
86 return false;
87}
88
89void ObjectSet::_emitChanged(bool persist_selection_context /*= false*/) {
90 _sibling_state.clear();
91}
92
93bool ObjectSet::includes(SPObject *object, bool anyAncestor) {
94 g_return_val_if_fail(object != nullptr, false);
95 if (anyAncestor) {
96 return _anyAncestorIsInSet(object);
97 } else {
98 return _container.get<hashed>().find(object) != _container.get<hashed>().end();
99 }
100}
101
103{
104 if (node) {
105 return includes(document()->getObjectByRepr(node), anyAncestor);
106 }
107 return false;
108}
109
110SPObject *
112 g_return_val_if_fail(object != nullptr, nullptr);
113 SPObject* o = object;
114 while (o != nullptr) {
115 if (includes(o)) {
116 return o;
117 }
118 o = o->parent;
119 }
120 return nullptr;
121}
122
124 _clear();
125 _emitChanged();
126}
127
129 return _container.size();
130}
131
133 SPObject* o = object;
134 while (o != nullptr) {
135 if (includes(o)) {
136 return true;
137 }
138 o = o->parent;
139 }
140
141 return false;
142}
143
145 for (auto& child: object->children) {
146 if (includes(&child)) {
147 _remove(&child);
148 // there is certainly no children of this child in the set
149 continue;
150 }
151
153 }
154}
155
157 _releaseConnections[object].disconnect();
158 _releaseConnections.erase(object);
160 _releaseSignals(object);
161}
162
164 _disconnect(object);
165 _container.get<hashed>().erase(object);
166}
167
169 _releaseConnections[object] = object->connectRelease(sigc::hide_return(sigc::mem_fun(*this, &ObjectSet::remove)));
170 _container.push_back(object);
172 _connectSignals(object);
173}
174
176 for (auto object: _container)
177 _disconnect(object);
178 _container.clear();
179}
182 SPObject *o = object;
183
184 bool flag = true;
185 while (o->parent != nullptr) {
186 for (auto &child: o->parent->children) {
187 if(&child != o && !includes(&child)) {
188 flag = false;
189 break;
190 }
191 }
192 if (!flag) {
193 break;
195 o = o->parent;
196 }
197 return o;
198}
199
201 SPObject* o = object;
202 while (o->parent != nullptr) {
203 for (auto &child: o->parent->children) {
204 if (&child != o) {
205 _add(&child);
206 }
207 }
208 if (includes(o->parent)) {
209 _remove(o->parent);
210 break;
211 }
212 o = o->parent;
213 }
214}
215
219
221 if (includes(obj)) {
222 remove(obj);
223 } else {
224 add(obj);
225 }
226}
227
229 return _container.size() == 0;
230}
231
233 return _container.size() == 1 ? *_container.begin() : nullptr;
234}
235
237 if (_container.size() == 1) {
238 SPObject* obj = *_container.begin();
239 if (is<SPItem>(obj)) {
240 return cast<SPItem>(obj);
241 }
242 }
243
244 return nullptr;
245}
246
248{
249 return _container.size() ? cast<SPItem>(_container.front()) : nullptr;
250}
251
253{
254 return _container.size() ? cast<SPItem>(_container.back()) : nullptr;
255}
256
258 return _sizeistItem(true, compare);
259}
260
262 return _sizeistItem(false, compare);
263}
264
266 auto items = this->items();
267 gdouble max = sml ? 1e18 : 0;
268 SPItem *ist = nullptr;
269
270 for (auto *item : items) {
272 if (!obox || obox.empty()) {
273 continue;
274 }
275
276 Geom::Rect bbox = *obox;
277
278 gdouble size = compare == AREA ? bbox.area() :
279 (compare == VERTICAL ? bbox.height() : bbox.width());
280 size = sml ? size : size * -1;
281 if (size < max) {
282 max = size;
283 ist = item;
284 }
285 }
286
287 return ist;
288}
289
293
295 SPObject *obj = single();
296 return obj ? obj->getRepr() : nullptr;
297}
298
300{
301 auto const &nodes = const_cast<ObjectSet *>(this)->xmlNodes();
302
303 if (nodes.empty()) {
304 return nullptr;
305 }
306
307#ifdef _LIBCPP_VERSION
308 // workaround for
309 // static_assert(__is_cpp17_forward_iterator<_ForwardIterator>::value
310 auto const n = std::vector<Inkscape::XML::Node *>(nodes.begin(), nodes.end());
311#else
312 auto const& n = nodes;
313#endif
314
315 return *std::max_element(n.begin(), n.end(), sp_repr_compare_position_bool);
316}
317
318void ObjectSet::set(SPObject *object, bool persist_selection_context) {
319 _clear();
320 _add(object);
321 _emitChanged(persist_selection_context);
322}
323
325{
326 if (document() && repr) {
327 SPObject *obj = document()->getObjectByRepr(repr);
328 assert(obj == document()->getObjectById(repr->attribute("id")));
329 set(obj);
330 }
331}
332
333void ObjectSet::setReprList(std::vector<XML::Node*> const &list) {
334 if(!document())
335 return;
336 clear();
337 for (auto iter = list.rbegin(); iter != list.rend(); ++iter) {
338#if 0
339 // This can fail when pasting a clone into a new document
340 SPObject *obj = document()->getObjectByRepr(*iter);
341 assert(obj == document()->getObjectById((*iter)->attribute("id")));
342#else
343 SPObject *obj = document()->getObjectById((*iter)->attribute("id"));
344#endif
345 if (obj) {
346 add(obj, true);
347 }
348 }
349 _emitChanged();
350}
351
353{
354 bool idAssigned = false;
355 auto items = this->items();
356 for (auto *item : items) {
357 if (!item->getId()) {
358 // Selected object does not have an ID, so assign it a unique ID
359 auto id = item->generate_unique_id();
360 item->setAttribute("id", id);
361 idAssigned = true;
362 }
363 }
364 if (idAssigned) {
366 if (document) {
368 }
369 }
370}
371
377
379{
380 auto items = const_cast<ObjectSet *>(this)->items();
381
382 Geom::OptRect bbox;
383 for (auto *item : items) {
385 }
386 return bbox;
387}
388
390{
391 auto items = const_cast<ObjectSet *>(this)->items();
392
393 Geom::OptRect bbox;
394 for (auto *item : items) {
396 }
397 return bbox;
398}
399
401{
402 auto items = const_cast<ObjectSet *>(this)->items();
403
404 Geom::OptRect bbox;
405 for (auto *item : items) {
406 bbox.unionWith(item->visualBounds(item->i2doc_affine(), false, true, true));
407 }
408 if (bbox) {
409 *bbox *= _desktop->getDocument()->doc2dt();
410 }
411 return bbox;
412}
413
415{
416 if (Inkscape::Preferences::get()->getInt("/tools/bounding_box") == 0) {
418 } else {
420 }
421}
422
424{
425 Geom::OptRect bbox;
426 auto items = const_cast<ObjectSet *>(this)->items();
427 if (items.empty()) return bbox;
428
429 for (auto *item : items) {
430 bbox |= item->documentBounds(type);
431 }
432
433 return bbox;
434}
435
437{
438 if (Inkscape::Preferences::get()->getInt("/tools/bounding_box") == 0) {
440 } else {
442 }
443}
444
445// If we have a selection of multiple items, then the center of the first item
446// will be returned; this is also the case in SelTrans::centerRequest()
447std::optional<Geom::Point> ObjectSet::center() const {
448 auto items = const_cast<ObjectSet *>(this)->items();
449 if (!items.empty()) {
450 SPItem *first = items.back(); // from the first item in selection
451 if (first->isCenterSet()) { // only if set explicitly
452 return first->getCenter();
453 }
454 }
456 if (bbox) {
457 return bbox->midpoint();
458 } else {
459 return std::optional<Geom::Point>();
460 }
461}
462
463std::list<Persp3D *> const ObjectSet::perspList() {
464 std::list<Persp3D *> pl;
465 for (auto & _3dboxe : _3dboxes) {
466 Persp3D *persp = _3dboxe->get_perspective();
467 if (std::find(pl.begin(), pl.end(), persp) == pl.end())
468 pl.push_back(persp);
469 }
470 return pl;
471}
472
473std::list<SPBox3D *> const ObjectSet::box3DList(Persp3D *persp) {
474 std::list<SPBox3D *> boxes;
475 if (persp) {
476 for (auto box : _3dboxes) {
477 if (persp == box->get_perspective()) {
478 boxes.push_back(box);
479 }
480 }
481 } else {
482 boxes = _3dboxes;
483 }
484 return boxes;
485}
486
488 std::list<SPBox3D *> boxes = SPBox3D::extract_boxes(obj);
489
490 for (auto box : boxes) {
491 _3dboxes.push_back(box);
492 }
493}
494
496 std::list<SPBox3D *> boxes = SPBox3D::extract_boxes(obj);
497
498 for (auto box : boxes) {
499 std::list<SPBox3D *>::iterator b = std::find(_3dboxes.begin(), _3dboxes.end(), box);
500 if (b == _3dboxes.end()) {
501 g_warning ("Warning! Trying to remove unselected box from selection.");
502 return;
503 }
504 _3dboxes.erase(b);
505 }
506}
507
508} // namespace Inkscape
509
510/*
511 Local Variables:
512 mode:c++
513 c-file-style:"stroustrup"
514 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
515 indent-tabs-mode:nil
516 fill-column:99
517 End:
518*/
519// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
Geom::IntRect bounds
Definition canvas.cpp:182
void unionWith(CRect const &b)
Enlarge the rectangle to contain the argument.
bool empty() const
Check for emptiness.
C area() const
Compute the rectangle's area.
C height() const
Get the vertical extent of the rectangle.
C width() const
Get the horizontal extent of the rectangle.
Axis-aligned rectangle that can be empty.
Definition rect.h:203
Axis aligned, non-empty rectangle.
Definition rect.h:92
void enforceIds()
Assign IDs to selected objects that don't have an ID attribute Checks if the object's id attribute is...
SPItem * smallestItem(CompareSize compare)
Returns the smallest item from this selection.
SPObject * includesAncestor(SPObject *object)
Returns ancestor if the given object has ancestor selected.
void _remove(SPObject *object)
SPItem * lastItem() const
Returns the last selected item, returns nullptr if no items selected.
SPDesktop * desktop()
Returns the desktop the selection is bound to.
Definition object-set.h:390
bool remove(SPObject *object)
Removes an item from the set of selected objects.
SPItemRange items()
Returns a range of selected SPItems.
Definition object-set.h:255
void _disconnect(SPObject *object)
Geom::OptRect preferredBounds() const
Returns either the visual or geometric bounding rectangle of the selection, based on the preferences ...
Geom::OptRect bounds(SPItem::BBoxType type) const
Returns the bounding rectangle of the selection.
virtual void _remove3DBoxesRecursively(SPObject *obj)
bool add(SPObject *object, bool nosignal=false)
Add an SPObject to the set of selected objects.
XMLNodeRange xmlNodes()
Returns a range of the xml nodes of all selected objects.
Definition object-set.h:274
void setReprList(std::vector< XML::Node * > const &list)
Selects the objects with the same IDs as those in list.
SPItem * largestItem(CompareSize compare)
Returns the largest item from this selection.
Geom::OptRect documentPreferredBounds() const
Returns either the visual or geometric bounding rectangle of selection in document coordinates based ...
std::list< SPBox3D * > _3dboxes
Definition object-set.h:532
SPDocument * _document
Definition object-set.h:531
SPItem * _sizeistItem(bool sml, CompareSize compare)
std::unordered_map< SPObject *, sigc::connection > _releaseConnections
Definition object-set.h:533
std::optional< Geom::Point > center() const
Returns the rotation/skew center of the selection.
void _add(SPObject *object)
Geom::OptRect strokedBounds() const
void clear()
Unselects all selected objects.
virtual void _emitChanged(bool persist_selection_context=false)
std::list< SPBox3D * > const box3DList(Persp3D *persp=nullptr)
Returns a list of all 3D boxes in the current selection which are associated to persp.
Geom::OptRect geometricBounds() const
virtual void _add3DBoxesRecursively(SPObject *obj)
virtual void _releaseSignals(SPObject *object)
Definition object-set.h:516
SPObject * single()
Returns a single selected object.
bool isEmpty()
Returns true if no items are selected.
std::map< SPObject *, SiblingState > _sibling_state
Definition object-set.h:540
void set(SPObject *object, bool persist_selection_context=false)
Set the selection to a single specific object.
Geom::OptRect documentBounds(SPItem::BBoxType type) const
int size()
Returns size of the selection.
SPDesktop * _desktop
Definition object-set.h:530
SPDocument * document()
Returns the document the selection is bound to.
Definition object-set.h:397
void _removeAncestorsFromSet(SPObject *object)
bool _anyAncestorIsInSet(SPObject *object)
SPItem * firstItem() const
Returns the first selected item, returns nullptr if no items selected.
void toggle(SPObject *obj)
Removes an item if selected, adds otherwise.
void _removeDescendantsFromSet(SPObject *object)
bool includes(SPObject *object, bool anyAncestor=false)
Returns true if the given object is selected.
SPItem * singleItem()
Returns a single selected item.
SPObjectRange objects()
Returns the list of selected objects.
virtual void _connectSignals(SPObject *object)
Definition object-set.h:515
XML::Node * singleRepr()
Returns a single selected object's xml node.
XML::Node * topRepr() const
The top-most item, or NULL if the selection is empty.
MultiIndexContainer _container
Definition object-set.h:529
Geom::OptRect visualBounds() const
SPObject * _getMutualAncestor(SPObject *object)
std::list< Persp3D * > const perspList()
Returns a list of all perspectives which have a 3D box in the current selection.
static Preferences * get()
Access the singleton Preferences object.
Interface for refcounted XML nodes.
Definition node.h:80
virtual char const * attribute(char const *key) const =0
Get the string representation of a node's attribute.
static std::list< SPBox3D * > extract_boxes(SPObject *obj)
Definition box3d.cpp:1214
To do: update description of desktop.
Definition desktop.h:149
SPDocument * getDocument() const
Definition desktop.h:189
Typed SVG document implementation.
Definition document.h:103
const Geom::Affine & doc2dt() const
Document to desktop coordinate transformation.
Definition document.cpp:935
SPObject * getObjectById(std::string const &id) const
void setModifiedSinceSave(bool const modified=true)
Indicate to the user if the document has been modified since the last save by displaying a "*" in fro...
SPObject * getObjectByRepr(Inkscape::XML::Node *repr) const
Base class for visual SVG elements.
Definition sp-item.h:109
Geom::OptRect documentPreferredBounds() const
Definition sp-item.cpp:1004
Geom::OptRect documentBounds(BBoxType type) const
Definition sp-item.cpp:1026
Geom::Point getCenter(bool ensure_uptodate=true) const
Definition sp-item.cpp:377
Geom::OptRect desktopVisualBounds() const
Get item's visual bbox in desktop coordinate system.
Definition sp-item.cpp:1049
Geom::OptRect desktopGeometricBounds() const
Get item's geometric bbox in desktop coordinate system.
Definition sp-item.cpp:1044
@ VISUAL_BBOX
Definition sp-item.h:118
@ GEOMETRIC_BBOX
Definition sp-item.h:116
Geom::Affine i2doc_affine() const
Returns the accumulated transformation of the item and all its ancestors, including root's viewport.
Definition sp-item.cpp:1816
bool isCenterSet() const
Definition sp-item.cpp:372
Geom::OptRect visualBounds(Geom::Affine const &transform=Geom::identity(), bool wfilter=true, bool wclip=true, bool wmask=true) const
Get item's visual bounding box in this item's coordinate system.
Definition sp-item.cpp:925
SPObject is an abstract base class of all of the document nodes at the SVG document level.
Definition sp-object.h:160
void setAttribute(Inkscape::Util::const_char_ptr key, Inkscape::Util::const_char_ptr value)
char const * getId() const
Returns the objects current ID string.
SPObject * parent
Definition sp-object.h:189
std::string generate_unique_id(char const *default_id=nullptr) const
Generate a document-wide unique id for this object.
Inkscape::XML::Node * getRepr()
Returns the XML representation of tree.
ChildrenList children
Definition sp-object.h:907
Editable view implementation.
static char const *const parent
Definition dir-util.cpp:70
SPItem * item
Inkscape::XML::Node * node
Geom::Point end
Helper class to stream background task notifications as a series of messages.
boost::any_range< SPObject *, boost::random_access_traversal_tag, SPObject *const &, std::ptrdiff_t > SPObjectRange
Definition object-set.h:117
Singleton class to access the preferences file in a convenient way.
Ocnode * child[8]
Definition quantize.cpp:33
bool sp_repr_compare_position_bool(Inkscape::XML::Node const *first, Inkscape::XML::Node const *second)
SPDesktop * desktop