Bug Summary

File:librecad/src/lib/engine/document/container/rs_entitycontainer.cpp
Warning:line 1998, column 21
Value stored to 'countOfEntities' during its initialization is never read

Annotated Source Code

Press '?' to see keyboard shortcuts

clang -cc1 -cc1 -triple x86_64-pc-linux-gnu -analyze -disable-free -clear-ast-before-backend -disable-llvm-verifier -discard-value-names -main-file-name rs_entitycontainer.cpp -analyzer-checker=core -analyzer-checker=apiModeling -analyzer-checker=unix -analyzer-checker=deadcode -analyzer-checker=cplusplus -analyzer-checker=security.insecureAPI.UncheckedReturn -analyzer-checker=security.insecureAPI.getpw -analyzer-checker=security.insecureAPI.gets -analyzer-checker=security.insecureAPI.mktemp -analyzer-checker=security.insecureAPI.mkstemp -analyzer-checker=security.insecureAPI.vfork -analyzer-checker=nullability.NullPassedToNonnull -analyzer-checker=nullability.NullReturnedFromNonnull -analyzer-output plist -w -setup-static-analyzer -mrelocation-model pic -pic-level 2 -fhalf-no-semantic-interposition -mframe-pointer=none -fmath-errno -ffp-contract=on -fno-rounding-math -mconstructor-aliases -funwind-tables=2 -target-cpu x86-64 -tune-cpu generic -debugger-tuning=gdb -fdebug-compilation-dir=/home/runner/work/LibreCAD/LibreCAD/librecad/src -fcoverage-compilation-dir=/home/runner/work/LibreCAD/LibreCAD/librecad/src -resource-dir /usr/lib/llvm-18/lib/clang/18 -D _REENTRANT -D DWGSUPPORT -D MUPARSER_STATIC -D QC_APPDIR="librecad" -D LC_VERSION=2.2.2_alpha1-607-g330d41cec -D LC_PRERELEASE=true; -D QT_NO_DEBUG -D QT_SVG_LIB -D QT_PRINTSUPPORT_LIB -D QT_WIDGETS_LIB -D QT_GUI_LIB -D QT_NETWORK_LIB -D QT_CORE_LIB -I . -I /usr/include -I ../../libraries/lciconengine -I ../../libraries/libdxfrw/src -I ../../libraries/jwwlib/src -I ../../libraries/shapelib/src -I cmd -I lib/actions -I lib/actions/visual_snap -I lib/actions/options -I lib/creation -I lib/debug -I lib/engine -I lib/engine/document -I lib/engine/document/blocks -I lib/engine/document/container -I lib/engine/document/dimstyles -I lib/engine/document/entities -I lib/engine/document/entities/support -I lib/engine/document/fonts -I lib/engine/document/io -I lib/engine/document/layers -I lib/engine/document/patterns -I lib/engine/document/selection -I lib/engine/document/textstyles -I lib/engine/document/ucs -I lib/engine/document/variables -I lib/engine/document/views -I lib/engine/clipboard -I lib/engine/overlays -I lib/engine/overlays/angles_base -I lib/engine/overlays/highlight -I lib/engine/overlays/preview -I lib/engine/overlays/references -I lib/engine/overlays/crosshair -I lib/engine/overlays/info_cursor -I lib/engine/overlays/overlay_box -I lib/engine/overlays/ucs_mark -I lib/engine/undo -I lib/engine/utils -I lib/engine/settings -I lib/fileio -I lib/filters -I lib/generators -I lib/generators/makercamsvg -I lib/generators/layers -I lib/generators/image -I lib/gui -I lib/gui/grid -I lib/gui/render -I lib/gui/render/headless -I lib/gui/render/widget -I lib/information -I lib/math -I lib/modification -I lib/selection -I lib/selection/metaentity -I lib/selection/metaentity/entities -I lib/printing -I lib/properties -I actions -I actions/dock_widgets -I actions/dock_widgets/block -I actions/dock_widgets/entity_info -I actions/dock_widgets/layer -I actions/dock_widgets/library -I actions/dock_widgets/ucs_list -I actions/drawing -I actions/drawing/draw -I actions/drawing/draw/arc -I actions/drawing/draw/circle -I actions/drawing/draw/curve -I actions/drawing/draw/spline -I actions/drawing/draw/dimensions -I actions/drawing/draw/ellipse -I actions/drawing/draw/hatch -I actions/drawing/draw/image -I actions/drawing/draw/line -I actions/drawing/draw/point -I actions/drawing/draw/rect -I actions/drawing/draw/polygon -I actions/drawing/draw/misc -I actions/drawing/draw/line/shapes -I actions/drawing/draw/line/misc -I actions/drawing/draw/line/shapes/rect -I actions/drawing/draw/line/shapes/polygon -I actions/drawing/draw/polyline -I actions/drawing/draw/text -I actions/drawing/edit -I actions/drawing/info -I actions/drawing/pick -I actions/drawing/modify -I actions/drawing/pen -I actions/drawing/rel_zero -I actions/drawing/selection -I actions/drawing/snap -I actions/drawing/zoom -I actions/file -I actions/options -I actions/print_preview -I ui -I ui/action_options -I ui/action_options/circle -I ui/action_options/curve -I ui/action_options/spline -I ui/action_options/dimensions -I ui/action_options/edit -I ui/action_options/image -I ui/action_options/info -I ui/action_options/insert -I ui/action_options/line -I ui/action_options/rect -I ui/action_options/polygon -I ui/action_options/misc -I ui/action_options/modify -I ui/action_options/ellipse -I ui/action_options/other -I ui/action_options/polyline -I ui/action_options/point -I ui/action_options/print_preview -I ui/action_options/selection -I ui/action_options/snap -I ui/action_options/text -I ui/actions -I ui/components -I ui/components/relative_position_assistant -I ui/components/comboboxes -I ui/components/containers -I ui/components/creators -I ui/components/layouts -I ui/components/pen -I ui/components/status_bar -I ui/components/toolbars -I ui/components/utils -I ui/dialogs -I ui/dialogs/actions -I ui/dialogs/actions/modify -I ui/dialogs/actions/quick_selection -I ui/dialogs/modify -I ui/dialogs/entity -I ui/dialogs/creators -I ui/dialogs/file -I ui/dialogs/file/export -I ui/dialogs/file/export/layers -I ui/dialogs/file/export/image -I ui/dialogs/file/export/makercam -I ui/dialogs/main -I ui/dialogs/settings -I ui/dialogs/settings/dimstyles -I ui/dialogs/settings/dimstyles/dimstyle_manager -I ui/dialogs/settings/dimstyles/dimstyle_manager/support -I ui/dialogs/settings/options_device -I ui/dialogs/settings/options_drawing -I ui/dialogs/settings/options_general -I ui/dialogs/settings/options_widget -I ui/dialogs/settings/shortcuts -I ui/dock_widgets -I ui/dock_widgets/block_widget -I ui/dock_widgets/command_line -I ui/dock_widgets/entity_info -I ui/dock_widgets/layer_widget -I ui/dock_widgets/layers_tree -I ui/dock_widgets/library_widget -I ui/dock_widgets/pen_palette -I ui/dock_widgets/pen_wizard -I ui/dock_widgets/property_sheet -I ui/dock_widgets/property_sheet/lib -I ui/dock_widgets/property_sheet/lib/properties -I ui/dock_widgets/property_sheet/lib/view -I ui/dock_widgets/property_sheet/lib/view/edit -I ui/dock_widgets/property_sheet/lib/widgets -I ui/dock_widgets/property_sheet/lib/widgets/sheet -I ui/dock_widgets/property_sheet/metaentity -I ui/dock_widgets/property_sheet/metaentity/entities -I ui/dock_widgets/property_sheet/metaentity/entities/document -I ui/dock_widgets/property_sheet/properties -I ui/dock_widgets/property_sheet/properties/action -I ui/dock_widgets/property_sheet/properties/bool -I ui/dock_widgets/property_sheet/properties/color -I ui/dock_widgets/property_sheet/properties/double -I ui/dock_widgets/property_sheet/properties/enum -I ui/dock_widgets/property_sheet/properties/int -I ui/dock_widgets/property_sheet/properties/layer -I ui/dock_widgets/property_sheet/properties/linetype -I ui/dock_widgets/property_sheet/properties/linewidth -I ui/dock_widgets/property_sheet/properties/rect -I ui/dock_widgets/property_sheet/properties/rsvector -I ui/dock_widgets/property_sheet/properties/string -I ui/dock_widgets/views_list -I ui/dock_widgets/ucs_list -I ui/dock_widgets/workspaces -I ui/dock_widgets/cad -I ui/main -I ui/main/init -I ui/main/persistence -I ui/main/release_check -I ui/main/support -I ui/main/fontviewer -I ui/main/workspaces -I ui/view -I main -I main/console_dxf2pdf -I test -I plugins -I ../res -I ../res/arrows -I ../res/controls -I ../res/dxf -I ../res/gdt -I ../res/icons -I ../res/images -I ../../../Qt/6.9.0/gcc_64/include -I ../../../Qt/6.9.0/gcc_64/include/QtSvg -I ../../../Qt/6.9.0/gcc_64/include/QtPrintSupport -I ../../../Qt/6.9.0/gcc_64/include/QtWidgets -I ../../../Qt/6.9.0/gcc_64/include/QtGui -I ../../../Qt/6.9.0/gcc_64/include/QtNetwork -I ../../../Qt/6.9.0/gcc_64/include/QtCore -I ../../generated/librecad/moc -I ../../generated/librecad/ui -I ../../../Qt/6.9.0/gcc_64/mkspecs/linux-g++ -internal-isystem /usr/bin/../lib/gcc/x86_64-linux-gnu/14/../../../../include/c++/14 -internal-isystem /usr/bin/../lib/gcc/x86_64-linux-gnu/14/../../../../include/x86_64-linux-gnu/c++/14 -internal-isystem /usr/bin/../lib/gcc/x86_64-linux-gnu/14/../../../../include/c++/14/backward -internal-isystem /usr/lib/llvm-18/lib/clang/18/include -internal-isystem /usr/local/include -internal-isystem /usr/bin/../lib/gcc/x86_64-linux-gnu/14/../../../../x86_64-linux-gnu/include -internal-externc-isystem /usr/include/x86_64-linux-gnu -internal-externc-isystem /include -internal-externc-isystem /usr/include -O2 -std=gnu++1z -fdeprecated-macro -ferror-limit 19 -fgnuc-version=4.2.1 -fskip-odr-check-in-gmf -fcxx-exceptions -fexceptions -vectorize-loops -vectorize-slp -analyzer-output=html -faddrsig -D__GCC_HAVE_DWARF2_CFI_ASM=1 -o /home/runner/work/LibreCAD/LibreCAD/out/2026-08-04-154929-5069-1 -x c++ lib/engine/document/container/rs_entitycontainer.cpp
1/****************************************************************************
2**
3** This file is part of the LibreCAD project, a 2D CAD program
4**
5** Copyright (C) 2025 Dongxu Li (github.com/dxli)
6** Copyright (C) 2010 R. van Twisk (librecad@rvt.dds.nl)
7** Copyright (C) 2001-2003 RibbonSoft. All rights reserved.
8**
9**
10** This file may be distributed and/or modified under the terms of the
11** GNU General Public License version 2 as published by the Free Software
12** Foundation and appearing in the file gpl-2.0.txt included in the
13** packaging of this file.
14**
15** This program is distributed in the hope that it will be useful,
16** but WITHOUT ANY WARRANTY; without even the implied warranty of
17** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18** GNU General Public License for more details.
19**
20** You should have received a copy of the GNU General Public License
21** along with this program; if not, write to the Free Software
22** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
23**
24** This copyright notice MUST APPEAR in all copies of the script!
25**
26**********************************************************************/
27
28#include "rs_entitycontainer.h"
29
30#include <iostream>
31
32#include "lc_containertraverser.h"
33#include "lc_looputils.h"
34#include "qg_dialogfactory.h"
35#include "rs_constructionline.h"
36#include "rs_debug.h"
37#include "rs_dialogfactory.h"
38#include "rs_dimension.h"
39#include "rs_document.h"
40#include "rs_ellipse.h"
41#include "rs_information.h"
42#include "rs_insert.h"
43#include "rs_layer.h"
44#include "rs_line.h"
45#include "rs_painter.h"
46#include "rs_solid.h"
47#include "rs_vector.h"
48
49class RS_Dimension;
50
51namespace {
52 // the tolerance used to check topology of contours in hatching
53 constexpr double CONTOUR_TOLERANCE = 1e-8; // fixme - sand - to options?
54
55 // For validate hatch contours, whether an entity in the contour is a closed
56 // loop itself
57 bool isClosedLoop(RS_Entity& entity) {
58 switch (entity.rtti()) {
59 case RS2::EntityCircle:
60 // Sub containers are always closed
61 case RS2::EntityContainer:
62 return true;
63 case RS2::EntityEllipse:
64 return !static_cast<RS_Ellipse*>(&entity)->isArc();
65 default:
66 return false;
67 }
68 }
69
70 // Find the nearest distance between the endpoints of an entity to a given point
71 double endPointDistance(const RS_Vector& point, const RS_Entity& entity) {
72 double distance = RS_MAXDOUBLE1.0E+10;
73 entity.getNearestEndpoint(point, nullptr, &distance);
74 return distance;
75 }
76
77 void clearSelectionBeforeDeletion(RS_Entity* entity) {
78 if (entity == nullptr || !entity->getFlag(RS2::FlagSelected))
79 return;
80 if (RS_Document* document = entity->getDocument()) {
81 document->unselect(entity);
82 } else {
83 entity->setSelectionFlag(false);
84 }
85 }
86}
87
88/**
89 * Default constructor.
90 *
91 * @param parent
92 * @param owner True if we own and also delete the entities.
93 */
94RS_EntityContainer::RS_EntityContainer(RS_EntityContainer* parent, const bool owner) : RS_Entity(parent) {
95 m_autoDelete = owner;
96 // RS_DEBUG->print("RS_EntityContainer::RS_EntityContainer: "
97 // "owner: %d", (int)owner);
98 m_subContainer = nullptr;
99 //autoUpdateBorders = true;
100 m_entIdx = -1;
101}
102
103/**
104 * Copy constructor. Makes a deep copy of all entities.
105 */
106
107RS_EntityContainer::RS_EntityContainer(const RS_EntityContainer& other) : RS_Entity{other}, m_subContainer{other.m_subContainer},
108 m_entities{other.m_entities},
109 m_autoUpdateBorders{other.m_autoUpdateBorders},
110 m_entIdx{other.m_entIdx}, m_autoDelete{other.m_autoDelete} {
111 if (m_autoDelete) {
112 // fixme - sand - check this logic, looks suspicious!
113 for (auto& it : *this) {
114 if (it == nullptr) {
115 continue;
116 }
117 if (it->isContainer()) {
118 it = it->clone();
119 }
120 }
121 }
122}
123
124RS_EntityContainer::RS_EntityContainer(const RS_EntityContainer& other, const bool copyChildren) : RS_Entity{other} {
125 m_subContainer = nullptr;
126 m_autoUpdateBorders = other.m_autoUpdateBorders;
127 m_entIdx = other.m_entIdx;
128 m_autoDelete = other.m_autoDelete;
129 if (copyChildren) {
130 m_entities = other.m_entities;
131 if (m_autoDelete) {
132 // fixme - sand - check this logic, looks suspicious!
133 for (auto& it : *this) {
134 if (it == nullptr) {
135 continue;
136 }
137 if (it->isContainer()) {
138 it = it->clone();
139 }
140 }
141 }
142 }
143}
144
145RS_EntityContainer& RS_EntityContainer::operator =(const RS_EntityContainer& other) {
146 this->RS_Entity::operator =(other);
147 m_subContainer = other.m_subContainer;
148 m_entities = other.m_entities;
149 m_autoUpdateBorders = other.m_autoUpdateBorders;
150 m_entIdx = other.m_entIdx;
151 m_autoDelete = other.m_autoDelete;
152 if (m_autoDelete) {
153 for (auto& it : *this) {
154 if (it == nullptr) {
155 continue;
156 }
157 if (it->isContainer()) {
158 it = it->clone();
159 }
160 }
161 }
162 return *this;
163}
164
165RS_EntityContainer::RS_EntityContainer(RS_EntityContainer&& other) noexcept : RS_Entity{other}, m_subContainer{other.m_subContainer},
166 m_entities{std::move(other.m_entities)},
167 m_autoUpdateBorders{other.m_autoUpdateBorders},
168 m_entIdx{other.m_entIdx}, m_autoDelete{other.m_autoDelete} {
169}
170
171RS_EntityContainer& RS_EntityContainer::operator =(RS_EntityContainer&& other) noexcept {
172 this->RS_Entity::operator =(other);
173 m_subContainer = other.m_subContainer;
174 m_entities = std::move(other.m_entities);
175 m_autoUpdateBorders = other.m_autoUpdateBorders;
176 m_entIdx = other.m_entIdx;
177 m_autoDelete = other.m_autoDelete;
178 return *this;
179}
180
181/**
182 * Destructor.
183 */
184RS_EntityContainer::~RS_EntityContainer() {
185 if (m_autoDelete) {
186 while (!m_entities.isEmpty()) {
187 delete m_entities.takeFirst();
188 }
189 }
190 else {
191 m_entities.clear();
192 }
193}
194
195RS_Entity* RS_EntityContainer::clone() const {
196 RS_DEBUGRS_Debug::instance()->print("RS_EntityContainer::clone: ori autoDel: %d", m_autoDelete);
197
198 auto* ec = new RS_EntityContainer(getParent(), isOwner());
199 if (isOwner()) {
200 for (const RS_Entity* entity : std::as_const(m_entities)) {
201 if (entity != nullptr) {
202 ec->m_entities.push_back(entity->clone());
203 }
204 }
205 }
206 else {
207 ec->m_entities = m_entities;
208 }
209
210 RS_DEBUGRS_Debug::instance()->print("RS_EntityContainer::clone: clone autoDel: %d", ec->isOwner());
211
212 ec->detach();
213 return ec;
214}
215
216RS_Entity* RS_EntityContainer::cloneProxy() const {
217 RS_DEBUGRS_Debug::instance()->print("RS_EntityContainer::cloneproxy: ori autoDel: %d", m_autoDelete);
218
219 auto* ec = new RS_EntityContainer(getParent(), isOwner());
220 if (isOwner()) {
221 for (const RS_Entity* entity : std::as_const(m_entities)) {
222 if (entity != nullptr) {
223 ec->m_entities.push_back(entity->cloneProxy());
224 }
225 }
226 }
227 else {
228 ec->m_entities = m_entities;
229 }
230
231 RS_DEBUGRS_Debug::instance()->print("RS_EntityContainer::cloneproxy: clone autoDel: %d", ec->isOwner());
232
233 ec->detach(); // fixme - review whether detach is always need... looks like a double clone() ??
234 return ec;
235}
236
237/**
238 * Detaches shallow copies and creates deep copies of all subentities.
239 * This is called after cloning entity containers.
240 */
241void RS_EntityContainer::detach() {
242 QList<RS_Entity*> clonesList;
243 const bool autoDel = isOwner();
244 RS_DEBUGRS_Debug::instance()->print("RS_EntityContainer::detach: autoDel: %d", autoDel);
245 setOwner(false);
246
247 // make deep copies of all entities:
248 for (const RS_Entity* e : *this) {
249 if (e == nullptr) {
250 continue;
251 }
252 if (!e->getFlag(RS2::FlagTemp)) {
253 clonesList.append(e->clone());
254 }
255 }
256
257 // clear shared pointers:
258 clear();
259 setOwner(autoDel);
260
261 // point to new deep copies:
262 for (RS_Entity* e : clonesList) {
263 push_back(e);
264 e->reparent(this);
265 }
266}
267
268void RS_EntityContainer::reparent(RS_EntityContainer* newParent) {
269 RS_Entity::reparent(newParent);
270
271 // All sub-entities:
272 for (RS_Entity* e : *this) {
273 if (e == nullptr) {
274 continue;
275 }
276 e->reparent(newParent);
277 }
278}
279
280void RS_EntityContainer::setVisible(const bool v) {
281 RS_Entity::setVisible(v);
282
283 // All sub-entities:
284 for (const auto e : std::as_const(m_entities)) {
285 if (e == nullptr) {
286 continue;
287 }
288 e->setVisible(v);
289 }
290}
291
292/**
293 * @return Total length of all m_entities in this container.
294 */
295double RS_EntityContainer::getLength() const {
296 double ret = 0.0;
297
298 for (const RS_Entity* e : *this) {
299 if (e == nullptr) {
300 continue;
301 }
302 if (e->isVisible()) {
303 const double length = e->getLength();
304 if (std::signbit(length)) {
305 ret = -1.0;
306 break;
307 }
308 ret += length;
309 }
310 }
311
312 return ret;
313}
314
315/**
316 * Selects this entity.
317 */
318bool RS_EntityContainer::setSelected(const bool select) {
319 // layer is locked:
320 if (select && isLocked()) {
321 return false;
322 }
323 RS_Entity::setSelectionFlag(select);
324 const auto doc = getDocument();
325 addToSelectionSet(select, doc);
326
327 // All sub-entity's select:
328 for (RS_Entity* e : *this) {
329 if (e == nullptr) {
330 continue;
331 }
332 if (e->isVisible()) {
333 e->setSelectionFlag(select);
334 }
335 }
336 return true;
337}
338
339bool RS_EntityContainer::doSelectInDocument(const bool select, RS_Document* doc) {
340 if (select && isLocked()) {
341 return false;
342 }
343 RS_Entity::setSelectionFlag(select);
344 addToSelectionSet(select, doc);
345
346 // All sub-entity's select:
347 for (RS_Entity* e : *this) {
348 if (e == nullptr) {
349 continue;
350 }
351 if (e->isVisible()) {
352 e->setSelectionFlag(select);
353 }
354 }
355 return true;
356}
357
358void RS_EntityContainer::setSelectionFlag(const bool select) {
359 // layer is locked:
360 if (select && isLocked()) {
361 return;
362 }
363 RS_Entity::setSelectionFlag(select);
364 // All sub-entity's select:
365 for (RS_Entity* e : *this) {
366 if (e == nullptr) {
367 continue;
368 }
369 if (e->isVisible()) {
370 e->setSelectionFlag(select);
371 }
372 }
373}
374
375void RS_EntityContainer::setHighlighted(const bool on) {
376 for (RS_Entity* e : *this) {
377 if (e == nullptr) {
378 continue;
379 }
380 e->setHighlighted(on);
381 }
382 RS_Entity::setHighlighted(on);
383}
384
385/**
386 * Adds a entity to this container and updates the borders of this
387 * entity-container if autoUpdateBorders is true.
388 */
389void RS_EntityContainer::addEntity(const RS_Entity* entity) {
390 if (entity == nullptr) {
391 return;
392 }
393 debugEntityAlreadyPresentExists(entity);
394 const auto ent = const_cast<RS_Entity*>(entity);
395 if (entity->rtti() == RS2::EntityImage || entity->rtti() == RS2::EntityHatch) {
396 m_entities.prepend(ent);
397 }
398 else {
399 m_entities.append(ent);
400 }
401 adjustBordersIfNeeded(entity);
402}
403
404/**
405 * Insert a entity at the end of entities list and updates the
406 * borders of this entity-container if autoUpdateBorders is true.
407 */
408void RS_EntityContainer::appendEntity(RS_Entity* entity) {
409 if (entity == nullptr) {
410 return;
411 }
412 debugEntityAlreadyPresentExists(entity);
413 m_entities.append(entity);
414 adjustBordersIfNeeded(entity);
415}
416
417/**
418 * Insert a entity at the start of entities list and updates the
419 * borders of this entity-container if autoUpdateBorders is true.
420 */
421void RS_EntityContainer::prependEntity(RS_Entity* entity) {
422 if (entity == nullptr) {
423 return;
424 }
425 debugEntityAlreadyPresentExists(entity);
426 m_entities.prepend(entity);
427 adjustBordersIfNeeded(entity);
428}
429
430/**
431 * Move a entity list in this container at the given position,
432 * the borders of this entity-container if autoUpdateBorders is true.
433 */
434void RS_EntityContainer::moveEntity(const int index, QList<RS_Entity*>& entList) {
435 if (entList.isEmpty()) {
436 return;
437 }
438 int ci = 0; //current index for insert without invert order
439 bool into = false;
440 RS_Entity* mid = nullptr;
441 if (index < 1) {
442 ci = 0;
443 }
444 else if (index >= m_entities.size()) {
445 ci = m_entities.size() - entList.size();
446 }
447 else {
448 into = true;
449 mid = m_entities.at(index);
450 }
451
452 for (int i = 0; i < entList.size(); ++i) {
453 RS_Entity* e = entList.at(i);
454 const bool ret = m_entities.removeOne(e);
455 //if e not exist in entities list remove from entList
456 if (!ret) {
457 entList.removeAt(i);
458 }
459 }
460 if (into) {
461 ci = m_entities.indexOf(mid);
462 }
463
464 for (const auto e : entList) {
465 m_entities.insert(ci++, e);
466 }
467}
468
469void RS_EntityContainer::adjustBordersIfNeeded(const RS_Entity* entity) {
470 if (m_autoUpdateBorders) {
471 adjustBorders(entity);
472 }
473}
474
475/**
476 * Inserts a entity to this container at the given position and updates
477 * the borders of this entity-container if autoUpdateBorders is true.
478 */
479void RS_EntityContainer::insertEntity(const int index, RS_Entity* entity) {
480 if (entity == nullptr) {
481 return;
482 }
483 debugEntityAlreadyPresentExists(entity);
484 m_entities.insert(index, entity);
485 adjustBordersIfNeeded(entity);
486}
487
488/**
489 * Removes an entity from this container and updates the borders of
490 * this entity-container if autoUpdateBorders is true.
491 */
492bool RS_EntityContainer::removeEntity(RS_Entity* entity) {
493 if (entity != nullptr) {
494 const bool ret = m_entities.removeOne(entity);
495 if (ret) {
496 // actually was contained in container
497 const bool mayAffectBorders = entity->isVisible();
498 // A selected child may be owned by a transient container such as
499 // an expanded INSERT. Remove it from the document selection before
500 // its storage is released, so selection never retains a dangling
501 // entity pointer.
502 clearSelectionBeforeDeletion(entity);
503 if (m_autoDelete) {
504 delete entity;
505 }
506 if (mayAffectBorders) {
507 calculateBordersIfNeeded();
508 }
509 }
510 return ret;
511 }
512 return false;
513}
514
515/**
516 * Erases all entities in this container and resets the borders..
517 */
518void RS_EntityContainer::clear() {
519 if (m_autoDelete) {
520 while (!m_entities.isEmpty()) {
521 RS_Entity* en = m_entities.takeFirst();
522 clearSelectionBeforeDeletion(en);
523 delete en;
524 }
525 }
526 else {
527 m_entities.clear();
528 }
529 resetBorders();
530}
531
532unsigned int RS_EntityContainer::count() const {
533 return m_entities.size();
534}
535
536/**
537 * Counts all entities (leaves of the tree).
538 */
539unsigned int RS_EntityContainer::countDeep() const {
540 unsigned int c = 0;
541 for (const auto t : *this) {
542 if (t == nullptr) {
543 continue;
544 }
545 c += t->countDeep();
546 }
547 return c;
548}
549
550/**
551 * Adjusts the borders of this graphic (max/min values)
552 */
553void RS_EntityContainer::adjustBorders(const RS_Entity* entity) {
554 //RS_DEBUG->print("RS_EntityContainer::adjustBorders");
555 //resetBorders();
556
557 if (entity != nullptr) {
558 // make sure a container is not empty (otherwise the border
559 // would get extended to 0/0):
560 if (!entity->isContainer() || entity->count() > 0) {
561 const RS_Vector emin = entity->getMin();
562 const RS_Vector emax = entity->getMax();
563 if (!emin.valid || !emax.valid)
564 return;
565 m_minV = RS_Vector::minimum(emin, m_minV);
566 m_maxV = RS_Vector::maximum(emax, m_maxV);
567 }
568
569 // Notify parents. The border for the parent might
570 // also change TODO: Check for efficiency
571 //if(parent) {
572 //parent->adjustBorders(this);
573 //}
574 }
575}
576
577/**
578 * Recalculates the borders of this entity container.
579 */
580void RS_EntityContainer::calculateBorders() {
581 // fixme - sand verify that there are no not needed borders calculations!!!
582 RS_DEBUGRS_Debug::instance()->print("RS_EntityContainer::calculateBorders");
583
584 resetBorders();
585 for (RS_Entity* e : *this) {
586 if (e != nullptr && e->isVisible()) {
587 e->calculateBorders();
588 adjustBorders(e);
589 }
590 }
591
592 RS_DEBUGRS_Debug::instance()->print("RS_EntityContainer::calculateBorders: size 1: %f,%f", getSize().x, getSize().y);
593
594 if (!m_minV.valid || !m_maxV.valid || m_minV.x > m_maxV.x
595 || m_minV.y > m_maxV.y || !std::isfinite(m_minV.x)
596 || !std::isfinite(m_minV.y) || !std::isfinite(m_maxV.x)
597 || !std::isfinite(m_maxV.y)) {
598 m_minV = RS_Vector(false);
599 m_maxV = RS_Vector(false);
600 }
601
602 RS_DEBUGRS_Debug::instance()->print("RS_EntityContainer::calculateBorders: size: %f,%f", getSize().x, getSize().y);
603}
604
605/**
606 * Recalculates the borders of this entity container including
607 * invisible entities.
608 */
609void RS_EntityContainer::forcedCalculateBorders() {
610 resetBorders();
611 for (RS_Entity* e : *this) {
612 if (e == nullptr)
613 continue;
614 // INSERT overrides calculateBorders (origin-pin / empty expand). Prefer
615 // that path over a blind recursive force that collapses empty children
616 // back to (0,0) and inflates MDI resize scroll ranges.
617 if (e->rtti() == RS2::EntityInsert) {
618 e->calculateBorders();
619 } else if (e->isContainer()) {
620 const auto container = static_cast<RS_EntityContainer*>(e);
621 container->forcedCalculateBorders();
622 } else {
623 e->calculateBorders();
624 }
625 adjustBorders(e);
626 }
627
628 if (!m_minV.valid || !m_maxV.valid || m_minV.x > m_maxV.x
629 || m_minV.y > m_maxV.y || !std::isfinite(m_minV.x)
630 || !std::isfinite(m_minV.y) || !std::isfinite(m_maxV.x)
631 || !std::isfinite(m_maxV.y)) {
632 m_minV = RS_Vector(false);
633 m_maxV = RS_Vector(false);
634 }
635}
636
637/**
638 * Updates all Dimension entities in this container and / or
639 * reposition their labels.
640 *
641 * @param autoText Automatically reposition the text label bool autoText=true
642 */
643int RS_EntityContainer::updateDimensions(const bool autoText) {
644 RS_DEBUGRS_Debug::instance()->print("RS_EntityContainer::updateDimensions()");
645 int updatedDimsCount = 0;
646
647 for (RS_Entity* e : *this) {
648 if (e == nullptr) {
649 continue;
650 }
651 if (e->isDeleted()) {
652 continue;
653 }
654 if (e->rtti() == RS2::EntityDimLeader) {
655 updatedDimsCount++;
656 e->update();
657 }
658 if (RS_Information::isDimension(e->rtti())) {
659 const auto dimension = static_cast<RS_Dimension*>(e);
660 // update and reposition label:
661 dimension->update();
662 updatedDimsCount++;
663 }
664 else if (e->isContainer()) {
665 const auto container = static_cast<RS_EntityContainer*>(e);
666 updatedDimsCount += container->updateDimensions(autoText);
667 }
668 }
669
670 RS_DEBUGRS_Debug::instance()->print("RS_EntityContainer::updateDimensions() OK");
671 return updatedDimsCount;
672}
673
674int RS_EntityContainer::updateVisibleDimensions(const bool autoText) {
675 RS_DEBUGRS_Debug::instance()->print("RS_EntityContainer::updateVisibleDimensions()");
676 int updatedDimsCount = 0;
677 for (RS_Entity* e : *this) {
678 if (e == nullptr) {
679 continue;
680 }
681 if (e->isVisible()) {
682 if (e->rtti() == RS2::EntityDimLeader) {
683 e->update();
684 updatedDimsCount++;
685 }
686 else if (RS_Information::isDimension(e->rtti())) {
687 const auto dimension = static_cast<RS_Dimension*>(e);
688 // update and reposition label:
689 dimension->updateDim(autoText);
690 updatedDimsCount++;
691 }
692 else if (e->isContainer()) {
693 const auto container = static_cast<RS_EntityContainer*>(e);
694 updatedDimsCount += container->updateVisibleDimensions(autoText);
695 }
696 }
697 }
698
699 RS_DEBUGRS_Debug::instance()->print("RS_EntityContainer::updateVisibleDimensions() OK");
700 return updatedDimsCount;
701}
702
703/**
704 * Updates all Insert entities in this container.
705 */
706void RS_EntityContainer::updateInserts() {
707 const std::string idTypeId = std::to_string(getId()) + "/" + std::to_string(rtti());
708 RS_DEBUGRS_Debug::instance()->print("RS_EntityContainer::updateInserts() ID/type: %s", idTypeId.c_str());
709
710 for (RS_Entity* e : std::as_const(*this)) {
711 //// Only update our own inserts and not inserts of inserts
712 if (e != nullptr) {
713 if (e->getId() != 0 && e->rtti() == RS2::EntityInsert /*&& e->getParent()==this*/) {
714 static_cast<RS_Insert*>(e)->update();
715
716 RS_DEBUGRS_Debug::instance()->print("RS_EntityContainer::updateInserts: updated ID/type: %s", idTypeId.c_str());
717 }
718 else if (e->isContainer()) {
719 if (e->rtti() == RS2::EntityHatch) {
720 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_DEBUGGING, "RS_EntityContainer::updateInserts: skip hatch ID/type: %s", idTypeId.c_str());
721 }
722 else {
723 RS_DEBUGRS_Debug::instance()->print("RS_EntityContainer::updateInserts: update container ID/type: %s", idTypeId.c_str());
724
725 static_cast<RS_EntityContainer*>(e)->updateInserts();
726 }
727 }
728 else {
729 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_DEBUGGING, "RS_EntityContainer::updateInserts: skip entity ID/type: %s", idTypeId.c_str());
730 }
731 }
732 RS_DEBUGRS_Debug::instance()->print("RS_EntityContainer::updateInserts() ID/type: %s", idTypeId.c_str());
733 }
734}
735
736/**
737 * Renames all inserts with name 'oldName' to 'newName'. This is
738 * called after a block was rename to update the inserts.
739 */
740void RS_EntityContainer::renameInserts(const QString& oldName, const QString& newName) {
741 RS_DEBUGRS_Debug::instance()->print("RS_EntityContainer::renameInserts()");
742 for (RS_Entity* e : std::as_const(m_entities)) {
743 if (e == nullptr) {
744 continue;
745 }
746 if (e->rtti() == RS2::EntityInsert) {
747 auto* i = static_cast<RS_Insert*>(e);
748 if (i->getName() == oldName) {
749 i->setName(newName);
750 }
751 }
752 if (e->isContainer()) {
753 const auto container = static_cast<RS_EntityContainer*>(e);
754 container->renameInserts(oldName, newName);
755 }
756 }
757 RS_DEBUGRS_Debug::instance()->print("RS_EntityContainer::renameInserts() OK");
758}
759
760/**
761 * Updates all Spline entities in this container.
762 */
763void RS_EntityContainer::updateSplines() {
764 RS_DEBUGRS_Debug::instance()->print("RS_EntityContainer::updateSplines()");
765 for (RS_Entity* e : *this) {
766 //// Only update our own inserts and not inserts of inserts
767 if (e->rtti() == RS2::EntitySpline /*&& e->getParent()==this*/) {
768 e->update();
769 }
770 else if (e->isContainer() && e->rtti() != RS2::EntityHatch) {
771 static_cast<RS_EntityContainer*>(e)->updateSplines();
772 }
773 }
774 RS_DEBUGRS_Debug::instance()->print("RS_EntityContainer::updateSplines() OK");
775}
776
777/**
778 * Updates the sub entities of this container.
779 */
780void RS_EntityContainer::update() {
781 for (RS_Entity* e : *this) {
782 e->update();
783 }
784}
785
786void RS_EntityContainer::addRectangle(const RS_Vector& v0, const RS_Vector& v1) {
787 addEntity(new RS_Line{this, v0, {v1.x, v0.y}});
788 addEntity(new RS_Line{this, {v1.x, v0.y}, v1});
789 addEntity(new RS_Line{this, v1, {v0.x, v1.y}});
790 addEntity(new RS_Line{this, {v0.x, v1.y}, v0});
791}
792
793void RS_EntityContainer::addRectangle(const RS_Vector& v0, const RS_Vector& v1, const RS_Vector& v2, const RS_Vector& v3) {
794 addEntity(new RS_Line(this, v0, v1));
795 addEntity(new RS_Line(this, v1, v2));
796 addEntity(new RS_Line(this, v2, v3));
797 addEntity(new RS_Line(this, v3, v0));
798}
799
800/**
801 * Returns the first entity or nullptr if this graphic is empty.
802 * @param level
803 */
804RS_Entity* RS_EntityContainer::firstEntity(const RS2::ResolveLevel level) const {
805 RS_Entity* e = nullptr;
806 m_entIdx = -1;
807 switch (level) {
808 case RS2::ResolveNone: {
809 if (!m_entities.isEmpty()) {
810 m_entIdx = 0;
811 return m_entities.first();
812 }
813 break;
814 }
815 case RS2::ResolveAllButInserts: {
816 m_subContainer = nullptr;
817 if (!m_entities.isEmpty()) {
818 m_entIdx = 0;
819 e = m_entities.first();
820 }
821 if (e != nullptr && e->isContainer() && e->rtti() != RS2::EntityInsert) {
822 m_subContainer = static_cast<RS_EntityContainer*>(e);
823 e = m_subContainer->firstEntity(level);
824 // empty container:
825 if (e == nullptr) {
826 m_subContainer = nullptr;
827 e = nextEntity(level);
828 }
829 }
830 return e;
831 }
832 case RS2::ResolveAllButTextImage:
833 case RS2::ResolveAllButTexts: {
834 m_subContainer = nullptr;
835 if (!m_entities.isEmpty()) {
836 m_entIdx = 0;
837 e = m_entities.first();
838 }
839 if (e != nullptr && e->isContainer() && e->rtti() != RS2::EntityText && e->rtti() != RS2::EntityMText) {
840 m_subContainer = static_cast<RS_EntityContainer*>(e);
841 e = m_subContainer->firstEntity(level);
842 // empty container:
843 if (e == nullptr) {
844 m_subContainer = nullptr;
845 e = nextEntity(level);
846 }
847 }
848 return e;
849 }
850 case RS2::ResolveAll: {
851 m_subContainer = nullptr;
852 if (!m_entities.isEmpty()) {
853 m_entIdx = 0;
854 e = m_entities.first();
855 }
856 if (e != nullptr && e->isContainer()) {
857 m_subContainer = static_cast<RS_EntityContainer*>(e);
858 e = m_subContainer->firstEntity(level);
859 // empty container:
860 if (e == nullptr) {
861 m_subContainer = nullptr;
862 e = nextEntity(level);
863 }
864 }
865 return e;
866 }
867 }
868 return nullptr;
869}
870
871/**
872 * Returns the last entity or \p nullptr if this graphic is empty.
873 *
874 * @param level \li \p 0 Groups are not resolved
875 * \li \p 1 (default) only Groups are resolved
876 * \li \p 2 all Entity Containers are resolved
877 */
878RS_Entity* RS_EntityContainer::lastEntity(const RS2::ResolveLevel level) const {
879 RS_Entity* e = nullptr;
880 if (m_entities.empty()) {
881 return nullptr;
882 }
883 m_entIdx = m_entities.size() - 1;
884 switch (level) {
885 case RS2::ResolveNone: {
886 if (!m_entities.isEmpty()) {
887 return m_entities.last();
888 }
889 break;
890 }
891 case RS2::ResolveAllButInserts: {
892 if (!m_entities.isEmpty()) {
893 e = m_entities.last();
894 }
895 m_subContainer = nullptr;
896 if (e != nullptr && e->isContainer() && e->rtti() != RS2::EntityInsert) {
897 m_subContainer = static_cast<RS_EntityContainer*>(e);
898 e = m_subContainer->lastEntity(level);
899 }
900 return e;
901 }
902 case RS2::ResolveAllButTextImage:
903 case RS2::ResolveAllButTexts: {
904 if (!m_entities.isEmpty()) {
905 e = m_entities.last();
906 }
907 m_subContainer = nullptr;
908 if (e != nullptr && e->isContainer() && e->rtti() != RS2::EntityText && e->rtti() != RS2::EntityMText) {
909 m_subContainer = static_cast<RS_EntityContainer*>(e);
910 e = m_subContainer->lastEntity(level);
911 }
912 return e;
913 }
914 case RS2::ResolveAll: {
915 if (!m_entities.isEmpty()) {
916 e = m_entities.last();
917 }
918 m_subContainer = nullptr;
919 if (e != nullptr && e->isContainer()) {
920 m_subContainer = static_cast<RS_EntityContainer*>(e);
921 e = m_subContainer->lastEntity(level);
922 }
923 return e;
924 }
925 }
926 return nullptr;
927}
928
929/**
930 * Returns the next entity or container or \p nullptr if the last entity
931 * returned by \p next() was the last entity in the container.
932 */
933RS_Entity* RS_EntityContainer::nextEntity(const RS2::ResolveLevel level) const {
934 //set entIdx pointing in next entity and check if is out of range
935 ++m_entIdx;
936 switch (level) {
937 case RS2::ResolveNone: {
938 if (m_entIdx < m_entities.size()) {
939 return m_entities.at(m_entIdx);
940 }
941 break;
942 }
943 case RS2::ResolveAllButInserts: {
944 RS_Entity* e = nullptr;
945 if (m_subContainer != nullptr) {
946 e = m_subContainer->nextEntity(level);
947 if (e != nullptr) {
948 --m_entIdx; //return a sub-entity, index not advanced
949 return e;
950 }
951 if (m_entIdx < m_entities.size()) {
952 e = m_entities.at(m_entIdx);
953 }
954 }
955 else {
956 if (m_entIdx < m_entities.size()) {
957 e = m_entities.at(m_entIdx);
958 }
959 }
960 if (e != nullptr && e->isContainer() && e->rtti() != RS2::EntityInsert) {
961 m_subContainer = static_cast<RS_EntityContainer*>(e);
962 e = m_subContainer->firstEntity(level);
963 // empty container:
964 if (e == nullptr) {
965 m_subContainer = nullptr;
966 e = nextEntity(level);
967 }
968 }
969 return e;
970 }
971 case RS2::ResolveAllButTextImage:
972 case RS2::ResolveAllButTexts: {
973 RS_Entity* e = nullptr;
974 if (m_subContainer != nullptr) {
975 e = m_subContainer->nextEntity(level);
976 if (e != nullptr) {
977 --m_entIdx; //return a sub-entity, index not advanced
978 return e;
979 }
980 if (m_entIdx < m_entities.size()) {
981 e = m_entities.at(m_entIdx);
982 }
983 }
984 else {
985 if (m_entIdx < m_entities.size()) {
986 e = m_entities.at(m_entIdx);
987 }
988 }
989 if (e != nullptr && e->isContainer() && e->rtti() != RS2::EntityText && e->rtti() != RS2::EntityMText) {
990 m_subContainer = static_cast<RS_EntityContainer*>(e);
991 e = m_subContainer->firstEntity(level);
992 // empty container:
993 if (e == nullptr) {
994 m_subContainer = nullptr;
995 e = nextEntity(level);
996 }
997 }
998 return e;
999 }
1000 case RS2::ResolveAll: {
1001 RS_Entity* e = nullptr;
1002 if (m_subContainer != nullptr) {
1003 e = m_subContainer->nextEntity(level);
1004 if (e != nullptr) {
1005 --m_entIdx; //return a sub-entity, index not advanced
1006 return e;
1007 }
1008 if (m_entIdx < m_entities.size()) {
1009 e = m_entities.at(m_entIdx);
1010 }
1011 }
1012 else {
1013 if (m_entIdx < m_entities.size()) {
1014 e = m_entities.at(m_entIdx);
1015 }
1016 }
1017 if (e != nullptr && e->isContainer()) {
1018 m_subContainer = static_cast<RS_EntityContainer*>(e);
1019 e = m_subContainer->firstEntity(level);
1020 // empty container:
1021 if (e == nullptr) {
1022 m_subContainer = nullptr;
1023 e = nextEntity(level);
1024 }
1025 }
1026 return e;
1027 }
1028 }
1029 return nullptr;
1030}
1031
1032/**
1033 * Returns the prev entity or container or \p nullptr if the last entity
1034 * returned by \p prev() was the first entity in the container.
1035 */
1036RS_Entity* RS_EntityContainer::prevEntity(const RS2::ResolveLevel level) const {
1037 //set entIdx pointing in prev entity and check if is out of range
1038 --m_entIdx;
1039 switch (level) {
1040 case RS2::ResolveNone: {
1041 if (m_entIdx >= 0) {
1042 return m_entities.at(m_entIdx);
1043 }
1044 break;
1045 }
1046 case RS2::ResolveAllButInserts: {
1047 RS_Entity* e = nullptr;
1048 if (m_subContainer != nullptr) {
1049 e = m_subContainer->prevEntity(level);
1050 if (e != nullptr) {
1051 return e;
1052 }
1053 if (m_entIdx >= 0) {
1054 e = m_entities.at(m_entIdx);
1055 }
1056 }
1057 else {
1058 if (m_entIdx >= 0) {
1059 e = m_entities.at(m_entIdx);
1060 }
1061 }
1062 if (e != nullptr && e->isContainer() && e->rtti() != RS2::EntityInsert) {
1063 m_subContainer = static_cast<RS_EntityContainer*>(e);
1064 e = m_subContainer->lastEntity(level);
1065 // empty container:
1066 if (e == nullptr) {
1067 m_subContainer = nullptr;
1068 e = prevEntity(level);
1069 }
1070 }
1071 return e;
1072 }
1073 case RS2::ResolveAllButTextImage:
1074 case RS2::ResolveAllButTexts: {
1075 RS_Entity* e = nullptr;
1076 if (m_subContainer != nullptr) {
1077 e = m_subContainer->prevEntity(level);
1078 if (e != nullptr) {
1079 return e;
1080 }
1081 if (m_entIdx >= 0) {
1082 e = m_entities.at(m_entIdx);
1083 }
1084 }
1085 else {
1086 if (m_entIdx >= 0) {
1087 e = m_entities.at(m_entIdx);
1088 }
1089 }
1090 if (e != nullptr && e->isContainer() && e->rtti() != RS2::EntityText && e->rtti() != RS2::EntityMText) {
1091 m_subContainer = static_cast<RS_EntityContainer*>(e);
1092 e = m_subContainer->lastEntity(level);
1093 // empty container:
1094 if (e == nullptr) {
1095 m_subContainer = nullptr;
1096 e = prevEntity(level);
1097 }
1098 }
1099 return e;
1100 }
1101 case RS2::ResolveAll: {
1102 RS_Entity* e = nullptr;
1103 if (m_subContainer != nullptr) {
1104 e = m_subContainer->prevEntity(level);
1105 if (e != nullptr) {
1106 ++m_entIdx; //return a sub-entity, index not advanced
1107 return e;
1108 }
1109 if (m_entIdx >= 0) {
1110 e = m_entities.at(m_entIdx);
1111 }
1112 }
1113 else {
1114 if (m_entIdx >= 0) {
1115 e = m_entities.at(m_entIdx);
1116 }
1117 }
1118 if (e != nullptr && e->isContainer()) {
1119 m_subContainer = static_cast<RS_EntityContainer*>(e);
1120 e = m_subContainer->lastEntity(level);
1121 // empty container:
1122 if (e == nullptr) {
1123 m_subContainer = nullptr;
1124 e = prevEntity(level);
1125 }
1126 }
1127 return e;
1128 }
1129 }
1130 return nullptr;
1131}
1132
1133/**
1134 * @return Entity at the given index or nullptr if the index is out of range.
1135 */
1136RS_Entity* RS_EntityContainer::entityAt(const int index) const {
1137 if (m_entities.size() > index && index >= 0) {
1138 return m_entities.at(index);
1139 }
1140 return nullptr;
1141}
1142
1143void RS_EntityContainer::setEntityAt(const int index, RS_Entity* en) {
1144 if (m_autoDelete && (m_entities.at(index) != nullptr)) {
1145 clearSelectionBeforeDeletion(m_entities.at(index));
1146 delete m_entities.at(index);
1147 }
1148 debugEntityAlreadyPresentExists(en);
1149 m_entities[index] = en;
1150}
1151
1152/**
1153 * Finds the given entity and makes it the current entity if found.
1154 */
1155int RS_EntityContainer::findEntity(const RS_Entity* const entity) {
1156 m_entIdx = m_entities.indexOf(const_cast<RS_Entity*>(entity));
1157 return m_entIdx;
1158}
1159
1160int RS_EntityContainer::findEntityIndex(const RS_Entity* const entity) const {
1161 return m_entities.indexOf(const_cast<RS_Entity*>(entity));
1162}
1163
1164bool RS_EntityContainer::areNeighborsEntities(RS_Entity const *const e1, RS_Entity const *const e2) const {
1165 return abs(m_entities.indexOf(const_cast<RS_Entity *>(e1)) - m_entities.indexOf(const_cast<RS_Entity *>(e2))) <= 1;
1166}
1167
1168/**
1169 * @return The point which is closest to 'coord'
1170 * (one of the vertices)
1171 */
1172RS_Vector RS_EntityContainer::doGetNearestEndpoint(const RS_Vector& coord, double* dist, RS_Entity** entity) const {
1173 double minDist = RS_MAXDOUBLE1.0E+10; // minimum measured distance
1174 double curDist = 0.; // currently measured distance
1175 RS_Vector closestPoint(false); // closest found endpoint
1176 RS_Entity* closestEntity {nullptr};
1177
1178 // fixme - actually, there could be improvement for snapping - by takind into consideration only entities within visual area
1179 for (const RS_Entity* en : *this) {
1180 if (en != nullptr && en->getId() != 0 && en->isVisible()) {
1181 const auto parent = en->getParent();
1182 bool checkForEndpoint = true;
1183 if (parent != nullptr) {
1184 checkForEndpoint = !parent->ignoredOnModification();
1185 }
1186 if (checkForEndpoint) {
1187 //no end point for Insert, text, Dim
1188 RS_Entity* closestCandidate = nullptr;
1189 const RS_Vector point = en->getNearestEndpoint(coord, &closestCandidate, &curDist);
1190 if (point.valid && curDist < minDist) {
1191 closestPoint = point;
1192 closestEntity = closestCandidate;
1193 minDist = curDist;
1194 if (dist != nullptr) {
1195 *dist = minDist;
1196 }
1197 }
1198 }
1199 }
1200 }
1201 if (entity != nullptr) {
1202 *entity = closestEntity;
1203 }
1204 return closestPoint;
1205}
1206
1207/**
1208 * @return The point which is closest to 'coord'
1209 * (one of the vertices)
1210 */
1211RS_Vector RS_EntityContainer::obtainNearestEndpoint(const RS_Vector& coord, double* dist, RS_Entity** pEntity) const {
1212 double minDist = RS_MAXDOUBLE1.0E+10; // minimum measured distance
1213 double curDist; // currently measured distance
1214 RS_Vector closestPoint(false); // closest found endpoint
1215
1216 for (const auto en : m_entities) {
1217 if (en == nullptr) {
1218 continue;
1219 }
1220 if (en->getParent() == nullptr || !en->getParent()->ignoredOnModification()) {
1221 //no end point for Insert, text, Dim
1222 // std::cout<<"find nearest for entity "<<i0<<std::endl;
1223 const RS_Vector point = en->getNearestEndpoint(coord, nullptr, &curDist);
1224 if (point.valid && curDist < minDist) {
1225 closestPoint = point;
1226 minDist = curDist;
1227 if (dist != nullptr) {
1228 *dist = minDist;
1229 }
1230 if (pEntity != nullptr) {
1231 *pEntity = en;
1232 }
1233 }
1234 }
1235 }
1236 return closestPoint;
1237}
1238
1239RS_Vector RS_EntityContainer::doGetNearestPointOnEntity(const RS_Vector& coord, const bool onEntity, double* dist, RS_Entity** entity) const {
1240 RS_Vector point(false);
1241 const RS_Entity *en = getNearestEntity(coord, dist, RS2::ResolveNone);
1242 // Issue #2670: A container that overrides getDistanceToPoint() for hit-testing (e.g.
1243 // RS_Hatch reporting a solid-fill hit) can return itself as the nearest
1244 // entity. Recursing into such a self-reference loops forever and overflows
1245 // the stack (crash observed when snapping inside a filled hatch), so only
1246 // descend into a genuine child entity.
1247 // Null parent (orphan / mid-import / dummy container): treat as snap-eligible,
1248 // matching obtainNearestEndpoint's ignoredOnModification guard. Never
1249 // dereference getParent() without a null check (SIGSEGV on hover/snap).
1250 if (en && en != this && en->isVisible()
1251 && (en->getParent() == nullptr || !en->getParent()->ignoredSnap())) {
1252 point = en->getNearestPointOnEntity(coord, onEntity, dist, entity);
1253 }
1254 return point;
1255}
1256
1257RS_Vector RS_EntityContainer::doGetNearestCenter(const RS_Vector& coord, double* dist, RS_Entity** centerEntity) const {
1258 double minDist = RS_MAXDOUBLE1.0E+10; // minimum measured distance
1259 double curDist = RS_MAXDOUBLE1.0E+10; // currently measured distance
1260 RS_Vector closestPoint(false); // closest found endpoint
1261 RS_Entity* closestCenterEntity{nullptr};
1262
1263 for (const auto en : m_entities) {
1264 if (en != nullptr && en->getId() != 0 && en->isVisible()
1265 && (en->getParent() == nullptr || !en->getParent()->ignoredSnap())) {
1266 //no center point for spline, text, Dim
1267 RS_Entity* centerEnt;
1268 const RS_Vector point = en->getNearestCenter(coord, &curDist, &centerEnt);
1269 if (point.valid && curDist < minDist) {
1270 closestPoint = point;
1271 closestCenterEntity = centerEnt;
1272 minDist = curDist;
1273 }
1274 }
1275 }
1276 if (dist != nullptr) {
1277 *dist = minDist;
1278 }
1279 if (centerEntity != nullptr) {
1280 *centerEntity = closestCenterEntity;
1281 }
1282 return closestPoint;
1283}
1284
1285/** @return the nearest of equidistant middle points of the line. */
1286
1287RS_Vector RS_EntityContainer::doGetNearestMiddle(const RS_Vector& coord, double* dist, const int middlePoints) const {
1288 double minDist = RS_MAXDOUBLE1.0E+10; // minimum measured distance
1289 double curDist = RS_MAXDOUBLE1.0E+10; // currently measured distance
1290 RS_Vector closestPoint(false); // closest found endpoint
1291
1292 for (const auto en : m_entities) {
1293 if (en != nullptr && en->isVisible()
1294 && (en->getParent() == nullptr || !en->getParent()->ignoredSnap())) {
1295 //no midle point for spline, text, Dim
1296 const RS_Vector point = en->getNearestMiddle(coord, &curDist, middlePoints);
1297 if (point.valid && curDist < minDist) {
1298 closestPoint = point;
1299 minDist = curDist;
1300 }
1301 }
1302 }
1303 if (dist != nullptr) {
1304 *dist = minDist;
1305 }
1306 return closestPoint;
1307}
1308
1309RS_Vector RS_EntityContainer::doGetNearestDist(const double distance, const RS_Vector& coord, double* dist) const {
1310 const RS_Entity* closestEntity = getNearestEntity(coord, nullptr, RS2::ResolveNone);
1311 return (closestEntity != nullptr) ? closestEntity->getNearestDist(distance, coord, dist) : RS_Vector{false};
1312}
1313
1314/**
1315 * @return The intersection which is closest to 'coord'
1316 */
1317RS_Vector RS_EntityContainer::getNearestIntersection(const RS_Vector& coord, double* dist, RS_Entity** entity, RS_Entity** otherEntity) const {
1318 double minDist = RS_MAXDOUBLE1.0E+10; // minimum measured distance
1319 RS_Vector closestPoint(false); // closest found endpoint
1320 const RS_Entity* closestEntity = getNearestEntity(coord, nullptr, RS2::ResolveAllButTextImage);
1321
1322 if (closestEntity != nullptr) {
1323 // fixme - sand - why not via traverser?
1324 for (const RS_Entity* en = firstEntity(RS2::ResolveAllButTextImage); en != nullptr; en = nextEntity(RS2::ResolveAllButTextImage)) {
1325 const auto parent = en->getParent();
1326 bool ignoredSnap = false;
1327 if (parent != nullptr) {
1328 // may be null in block editing?
1329 ignoredSnap = parent->ignoredSnap();
1330 }
1331 if (!en->isVisible() || ignoredSnap) {
1332 continue;
1333 }
1334
1335 RS_VectorSolutions sol = RS_Information::getIntersection(closestEntity, en, true);
1336 double curDist = RS_MAXDOUBLE1.0E+10; // currently measured distance
1337 const RS_Vector point = sol.getClosest(coord, &curDist, nullptr);
1338 if (sol.getNumber() > 0 && curDist < minDist) {
1339 closestPoint = point;
1340 minDist = curDist;
1341 if (entity != nullptr) {
1342 *entity = const_cast<RS_Entity*>(closestEntity);
1343 }
1344 if (otherEntity != nullptr) {
1345 *otherEntity = const_cast<RS_Entity*>(en);
1346 }
1347 }
1348 }
1349 }
1350 if ((dist != nullptr) && closestPoint.valid) {
1351 *dist = minDist;
1352 }
1353 return closestPoint;
1354}
1355
1356RS_Vector RS_EntityContainer::getNearestVirtualIntersection(const RS_Vector& coord, const double angle, double* dist) const {
1357 const RS_Entity* closestEntity = getNearestEntity(coord, nullptr, RS2::ResolveAllButTextImage);
1358 if (closestEntity != nullptr) {
1359 const RS_Vector second_coord{angle};
1360 const RS_ConstructionLineData data(coord, coord + second_coord);
1361 const auto line = RS_ConstructionLine(nullptr, data);
1362
1363 const RS_VectorSolutions sol = RS_Information::getIntersection(closestEntity, &line, true);
1364 if (sol.getVector().empty()) {
1365 return coord;
1366 }
1367 return sol.getClosest(coord, dist, nullptr);
1368 }
1369 return coord;
1370}
1371
1372RS_Vector RS_EntityContainer::doGetNearestRef(const RS_Vector& coord, double* dist) const {
1373 double minDist = RS_MAXDOUBLE1.0E+10; // minimum measured distance
1374 double curDist; // currently measured distance
1375 RS_Vector closestPoint(false); // closest found endpoint
1376
1377 for (const RS_Entity* en : *this) {
1378 if (en == nullptr) {
1379 continue;
1380 }
1381 if (en->isVisible()) {
1382 const RS_Vector point = en->getNearestRef(coord, &curDist);
1383 if (point.valid && curDist < minDist) {
1384 closestPoint = point;
1385 minDist = curDist;
1386 if (dist != nullptr) {
1387 *dist = minDist;
1388 }
1389 }
1390 }
1391 }
1392 return closestPoint;
1393}
1394
1395RS_Vector RS_EntityContainer::doGetNearestSelectedRef(const RS_Vector& coord, double* dist) const {
1396 const RefInfo info = getNearestSelectedRefInfo(coord, dist);
1397 return info.ref;
1398}
1399
1400RS_EntityContainer::RefInfo RS_EntityContainer::getNearestSelectedRefInfo(const RS_Vector& coord, double* dist) const {
1401 double minDist = RS_MAXDOUBLE1.0E+10; // minimum measured distance
1402 RS_Vector closestPoint(false); // closest found endpoint
1403 RS_Entity* closestPointEntity = nullptr;
1404
1405 for (RS_Entity* en : *this) {
1406 if (en == nullptr) {
1407 continue;
1408 }
1409 // fixme - sand - iteration of ver all entities
1410 if (en->isVisible() && en->isSelected() && !en->isParentSelected()) {
1411 // fixme - SELECTION - selection collection!
1412 double curDist = 0.; // currently measured distance
1413 const RS_Vector point = en->getNearestSelectedRef(coord, &curDist);
1414 if (point.valid && curDist < minDist) {
1415 closestPoint = point;
1416 closestPointEntity = en;
1417 minDist = curDist;
1418 if (dist != nullptr) {
1419 *dist = minDist;
1420 }
1421 }
1422 }
1423 }
1424 const RefInfo result{closestPoint, closestPointEntity};
1425 return result;
1426}
1427
1428double RS_EntityContainer::doGetDistanceToPoint(const RS_Vector& coord, RS_Entity** entity, const RS2::ResolveLevel level,
1429 const double solidDist) const {
1430 RS_DEBUGRS_Debug::instance()->print("RS_EntityContainer::getDistanceToPoint");
1431 double minDist = RS_MAXDOUBLE1.0E+10; // minimum measured distance
1432 double curDist = 0.; // currently measured distance
1433 RS_Entity* closestEntity = nullptr; // closest entity found
1434 RS_Entity* subEntity = nullptr;
1435
1436 for (RS_Entity* e : *this) {
1437 if (e == nullptr) {
1438 continue;
1439 }
1440 const auto entityLayer = e->getLayer();
1441 if (e->isVisible() && (entityLayer == nullptr || !entityLayer->isLocked())) {
1442 RS_DEBUGRS_Debug::instance()->print("entity: getDistanceToPoint");
1443 RS_DEBUGRS_Debug::instance()->print("entity: %d", e->rtti());
1444 // bug#426, need to ignore Images to find nearest intersections
1445 if (level == RS2::ResolveAllButTextImage && e->rtti() == RS2::EntityImage) {
1446 continue;
1447 }
1448 curDist = e->getDistanceToPoint(coord, &subEntity, level, solidDist);
1449
1450 RS_DEBUGRS_Debug::instance()->print("entity: getDistanceToPoint: OK");
1451
1452 /*
1453 * By using '<=', we will prefer the *last* item in the container if there are multiple
1454 * entities that are *exactly* the same distance away, which should tend to be the one
1455 * drawn most recently, and the one most likely to be visible (as it is also the order
1456 * that the software draws the entities). This makes a difference when one entity is
1457 * drawn directly over top of another, and it's reasonable to assume that humans will
1458 * tend to want to reference entities that they see or have recently drawn as opposed
1459 * to deeper more forgotten and invisible ones...
1460 */
1461 if (curDist <= minDist) {
1462 switch (level) {
1463 case RS2::ResolveAll:
1464 case RS2::ResolveAllButTextImage:
1465 closestEntity = subEntity;
1466 break;
1467 default:
1468 closestEntity = e;
1469 break;
1470 }
1471 minDist = curDist;
1472 }
1473 }
1474 }
1475
1476 if (entity != nullptr) {
1477 *entity = closestEntity;
1478 }
1479 RS_DEBUGRS_Debug::instance()->print("RS_EntityContainer::getDistanceToPoint: OK");
1480
1481 return minDist;
1482}
1483
1484RS_Entity* RS_EntityContainer::getNearestEntity(const RS_Vector& coord, double* dist, const RS2::ResolveLevel level) const {
1485 RS_DEBUGRS_Debug::instance()->print("RS_EntityContainer::getNearestEntity");
1486
1487 RS_Entity* e = nullptr;
1488
1489 // distance for points inside solids:
1490 double solidDist = RS_MAXDOUBLE1.0E+10;
1491 if (dist != nullptr) {
1492 solidDist = *dist;
1493 }
1494
1495 const double d = getDistanceToPoint(coord, &e, level, solidDist);
1496 if (e != nullptr && e->isVisible() == false) {
1497 e = nullptr;
1498 }
1499
1500 // if d is negative, use the default distance (used for points inside solids)
1501 if (dist != nullptr) {
1502 *dist = d;
1503 }
1504 RS_DEBUGRS_Debug::instance()->print("RS_EntityContainer::getNearestEntity: OK");
1505 return e;
1506}
1507
1508/**
1509 * Rearranges the atomic entities in this container in a way that connected
1510 * entities are stored in the right order and direction.
1511 * Non-recoursive. Only affects atomic entities in this container.
1512 *
1513 * @retval true all contours were closed
1514 * @retval false at least one contour is not closed
1515
1516 * to do: find closed contour by flood-fill
1517 */
1518bool RS_EntityContainer::optimizeContours() {
1519 // std::cout<<"RS_EntityContainer::optimizeContours: begin"<<std::endl;
1520 // DEBUG_HEADER
1521 // std::cout<<"loop with count()="<<count()<<std::endl;
1522 RS_DEBUGRS_Debug::instance()->print("RS_EntityContainer::optimizeContours");
1523
1524 RS_EntityContainer tmp;
1525 tmp.setAutoUpdateBorders(false);
1526 bool closed = true;
1527
1528 /** accept all full circles **/
1529 QList<RS_Entity*> enList;
1530 for (RS_Entity* e1 : *this) {
1531 if (e1 == nullptr) {
1532 continue;
1533 }
1534 if (!e1->isEdge() || e1->isContainer()) {
1535 enList << e1;
1536 continue;
1537 }
1538
1539 //detect circles and whole ellipses
1540 switch (e1->rtti()) {
1541 case RS2::EntityEllipse: {
1542 if (static_cast<RS_Ellipse*>(e1)->isEllipticArc()) {
1543 continue;
1544 }
1545 // fall-through
1546 [[fallthrough]];
1547 }
1548 case RS2::EntityCircle: {
1549 //directly detect circles, bug#3443277
1550 tmp.addEntity(e1->clone());
1551 enList << e1;
1552 // fall-through
1553 [[fallthrough]];
1554 }
1555 default:
1556 break;
1557 }
1558 }
1559
1560 /** remove unsupported entities */
1561 for (RS_Entity* it : std::as_const(enList)) {
1562 removeEntity(it);
1563 }
1564
1565 /** check and form a closed contour **/
1566 // std::cout<<"RS_EntityContainer::optimizeContours: 2"<<std::endl;
1567 /** the first entity **/
1568 const RS_Entity* current(nullptr);
1569 if (count() > 0) {
1570 current = entityAt(0)->clone();
1571 tmp.addEntity(current);
1572 removeEntity(entityAt(0));
1573 }
1574 else {
1575 if (tmp.count() == 0) {
1576 return false;
1577 }
1578 }
1579 // std::cout<<"RS_EntityContainer::optimizeContours: 3"<<std::endl;
1580 RS_Vector vpStart;
1581 RS_Vector vpEnd;
1582 if (current != nullptr) {
1583 vpStart = current->getStartpoint();
1584 vpEnd = current->getEndpoint();
1585 }
1586 RS_Entity* next = nullptr;
1587 // std::cout<<"RS_EntityContainer::optimizeContours: 4"<<std::endl;
1588 /** connect entities **/
1589 const auto errMsg = QObject::tr("Hatch failed due to a gap=%1 between (%2, %3) and (%4, %5)");
1590
1591 while (count() > 0) {
1592 double dist = 0.;
1593 const RS_Vector vpTmp = obtainNearestEndpoint(vpEnd, &dist, &next);
1594 if (dist > CONTOUR_TOLERANCE) {
1595 if (vpEnd.squaredTo(vpStart) < CONTOUR_TOLERANCE) {
1596 RS_Entity* e2 = entityAt(0);
1597 tmp.addEntity(e2->clone());
1598 vpStart = e2->getStartpoint();
1599 vpEnd = e2->getEndpoint();
1600 removeEntity(e2);
1601 continue;
1602 }
1603 // fixme - sand - isn't it a really bad dependency? check later and remove
1604 QG_DIALOGFACTORY(RS_DialogFactory::instance()->getFactoryObject())->commandMessage(errMsg.arg(dist).arg(vpTmp.x).arg(vpTmp.y).arg(vpEnd.x).arg(vpEnd.y));
1605 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_ERROR, "RS_EntityContainer::optimizeContours: hatch failed due to a gap");
1606 closed = false;
1607 break;
1608 }
1609 if (next == nullptr) {
1610 //workaround if next is nullptr
1611 // std::cout<<"RS_EntityContainer::optimizeContours: next is nullptr" <<std::endl;
1612 RS_DEBUGRS_Debug::instance()->print("RS_EntityContainer::optimizeContours: next is nullptr");
1613 // closed=false; //workaround if next is nullptr
1614 break; //workaround if next is nullptr
1615 } // workaround if next is nullptr
1616 if (closed) {
1617 next->setProcessed(true);
1618 RS_Entity* eTmp = next->clone();
1619 if (vpEnd.squaredTo(eTmp->getStartpoint()) > vpEnd.squaredTo(eTmp->getEndpoint())) {
1620 eTmp->revertDirection();
1621 }
1622 vpEnd = eTmp->getEndpoint();
1623 tmp.addEntity(eTmp);
1624 removeEntity(next);
1625 }
1626 }
1627 // DEBUG_HEADER
1628 // if(vpEnd.valid && vpEnd.squaredTo(vpStart) > 1e-8) {
1629 // QG_DIALOGFACTORY->commandMessage(errMsg.arg(vpEnd.distanceTo(vpStart))
1630 // .arg(vpStart.x).arg(vpStart.y).arg(vpEnd.x).arg(vpEnd.y));
1631 // RS_DEBUG->print("RS_EntityContainer::optimizeContours: hatch failed due to a gap");
1632 // closed=false;
1633 // }
1634 // std::cout<<"RS_EntityContainer::optimizeContours: 5"<<std::endl;
1635
1636 // add new sorted entities:
1637 for (RS_Entity* en : tmp) {
1638 en->setProcessed(false);
1639 addEntity(en->clone());
1640 en->reparent(this);
1641 }
1642 // std::cout<<"RS_EntityContainer::optimizeContours: 6"<<std::endl;
1643
1644 if (closed) {
1645 RS_DEBUGRS_Debug::instance()->print("RS_EntityContainer::optimizeContours: OK");
1646 }
1647 else {
1648 RS_DEBUGRS_Debug::instance()->print("RS_EntityContainer::optimizeContours: bad");
1649 }
1650 // std::cout<<"RS_EntityContainer::optimizeContours: end: count()="<<count()<<std::endl;
1651 // std::cout<<"RS_EntityContainer::optimizeContours: closed="<<closed<<std::endl;
1652 return closed;
1653}
1654
1655bool RS_EntityContainer::hasEndpointsWithinWindow(const RS_Vector& v1, const RS_Vector& v2) const {
1656 return std::any_of(cbegin(), cend(), [&v1, &v2](const RS_Entity* entity) {
1657 return entity != nullptr && entity->hasEndpointsWithinWindow(v1, v2);
1658 });
1659}
1660
1661void RS_EntityContainer::move(const RS_Vector& offset) {
1662 moveBorders(offset);
1663 for (RS_Entity* e : *this) {
1664 if (e == nullptr) {
1665 continue;
1666 }
1667 e->move(offset);
1668 adjustBorders(e);
1669 }
1670 calculateBordersIfNeeded();
1671}
1672
1673void RS_EntityContainer::rotate(const RS_Vector& center, const double angle) {
1674 RS_EntityContainer::rotate(center, RS_Vector{angle});
1675}
1676
1677void RS_EntityContainer::rotate(const RS_Vector& center, const RS_Vector& angleVector) {
1678 resetBorders();
1679 for (RS_Entity* e : *this) {
1680 if (e == nullptr) {
1681 continue;
1682 }
1683 e->rotate(center, angleVector);
1684 adjustBorders(e);
1685 }
1686 calculateBordersIfNeeded();
1687}
1688
1689void RS_EntityContainer::scale(const RS_Vector& center, const RS_Vector& factor) {
1690 if (std::abs(factor.x) > RS_TOLERANCE1.0e-10 && std::abs(factor.y) > RS_TOLERANCE1.0e-10) {
1691 scaleBorders(center, factor);
1692 for (RS_Entity* e : *this) {
1693 if (e == nullptr) {
1694 continue;
1695 }
1696 e->scale(center, factor);
1697 adjustBorders(e);
1698 }
1699 calculateBordersIfNeeded();
1700 }
1701}
1702
1703void RS_EntityContainer::mirror(const RS_Vector& axisPoint1, const RS_Vector& axisPoint2) {
1704 if (axisPoint1.distanceTo(axisPoint2) > RS_TOLERANCE1.0e-10) {
1705 resetBorders();
1706 for (RS_Entity* e : *this) {
1707 if (e == nullptr) {
1708 continue;
1709 }
1710 e->mirror(axisPoint1, axisPoint2);
1711 adjustBorders(e);
1712 }
1713 }
1714}
1715
1716RS_Entity& RS_EntityContainer::shear(const double k) {
1717 for (RS_Entity* e : *this) {
1718 if (e == nullptr) {
1719 continue;
1720 }
1721 e->shear(k);
1722 }
1723 calculateBorders();
1724 return *this;
1725}
1726
1727void RS_EntityContainer::stretch(const RS_Vector& firstCorner, const RS_Vector& secondCorner, const RS_Vector& offset) {
1728 if (getMin().isInWindow(firstCorner, secondCorner) && getMax().isInWindow(firstCorner, secondCorner)) {
1729 move(offset);
1730 }
1731 else {
1732 for (RS_Entity* e : *this) {
1733 if (e == nullptr) {
1734 continue;
1735 }
1736 e->stretch(firstCorner, secondCorner, offset);
1737 }
1738 }
1739 // some entitiycontainers might need an update (e.g. RS_Leader):
1740 update();
1741}
1742
1743void RS_EntityContainer::calculateBordersIfNeeded() {
1744 if (m_autoUpdateBorders) {
1745 calculateBorders();
1746 }
1747}
1748
1749void RS_EntityContainer::moveRef(const RS_Vector& ref, const RS_Vector& offset) {
1750 resetBorders();
1751 for (RS_Entity* e : *this) {
1752 if (e == nullptr) {
1753 continue;
1754 }
1755 e->moveRef(ref, offset);
1756 adjustBorders(e);
1757 }
1758 calculateBordersIfNeeded();
1759}
1760
1761void RS_EntityContainer::moveSelectedRef(const RS_Vector& ref, const RS_Vector& offset) {
1762 resetBorders();
1763 for (RS_Entity* e : *this) {
1764 if (e == nullptr) {
1765 continue;
1766 }
1767 e->moveSelectedRef(ref, offset);
1768 adjustBorders(e);
1769 }
1770 calculateBordersIfNeeded();
1771}
1772
1773void RS_EntityContainer::revertDirection() {
1774 // revert entity order in the container
1775 for (int k = 0; k < m_entities.size() / 2; ++k) {
1776#if (QT_VERSION((6<<16)|(9<<8)|(0)) >= QT_VERSION_CHECK(5, 13, 0)((5<<16)|(13<<8)|(0)))
1777 m_entities.swapItemsAt(k, m_entities.size() - 1 - k);
1778#else
1779 entities.swap(k, entities.size() - 1 - k);
1780#endif
1781 }
1782
1783 // revert each entity itself
1784 for (RS_Entity* entity : std::as_const(m_entities)) {
1785 if (entity == nullptr) {
1786 continue;
1787 }
1788 entity->revertDirection();
1789 }
1790}
1791
1792/**
1793 * @brief draw m_entities in order
1794 * @param painter
1795 */
1796void RS_EntityContainer::draw(RS_Painter* painter) {
1797 for (RS_Entity* e : *this) {
1798 if (e != nullptr && e->getId() != 0) {
1799 painter->drawEntity(e);
1800 }
1801 }
1802}
1803
1804void RS_EntityContainer::drawAsChild(RS_Painter* painter) {
1805 for (RS_Entity* e : *this) {
1806 if (e != nullptr && e->getId() != 0) {
1807 painter->drawAsChild(e);
1808 }
1809 }
1810}
1811
1812/**
1813 * @brief areaLineIntegral, line integral for contour area calculation by Green's Theorem
1814 * Contour Area =\oint x dy
1815 * @return line integral \oint x dy along the entity
1816 */
1817double RS_EntityContainer::areaLineIntegral() const {
1818 //TODO make sure all contour integral is by counter-clockwise
1819 double contourArea = 0.;
1820 //closed area is always positive
1821 double closedArea = 0.;
1822 double subArea = 0.;
1823
1824 // edges:
1825 RS_Vector previousPoint(false);
1826 for (unsigned i = 0; i < count(); ++i) {
1827 RS_Entity* e = m_entities.at(i);
1828 if (isClosedLoop(*e)) {
1829 if (e->isContainer()) {
1830 subArea += e->areaLineIntegral();
1831 }
1832 else {
1833 closedArea += e->areaLineIntegral();
1834 }
1835 continue;
1836 }
1837 e->setLayer(getLayer());
1838 const double lineIntegral = e->areaLineIntegral();
1839 RS_Vector startPoint = e->getStartpoint();
1840 RS_Vector endPoint = e->getEndpoint();
1841 // LC_ERR << e->getId() << ": int = " << lineIntegral << ": " << startPoint.x << " - " << endPoint.x;
1842
1843 // the line integral is always by the direction: from the start point to the end point
1844 if (previousPoint.valid) {
1845 const double distance = endPointDistance(previousPoint, *e);
1846 if (distance > CONTOUR_TOLERANCE) {
1847 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_ERROR, "%s(): contour area calculation maybe incorrect: gap of %lg found at (%lg, %lg)",
1848 __func__, distance, previousPoint.x, previousPoint.y);
1849 }
1850 }
1851 // assume the contour is a simple connected loop
1852 if (previousPoint.valid && endPoint.squaredTo(previousPoint) <= RS_TOLERANCE151.5e-15) {
1853 contourArea -= lineIntegral;
1854 previousPoint = startPoint;
1855 }
1856 else {
1857 bool useEndPoint = true;
1858 if (!previousPoint.valid && i + 1 < count()) {
1859 const auto currEntity = m_entities.at(i + 1);
1860 useEndPoint = endPointDistance(endPoint, *currEntity) < endPointDistance(startPoint, *currEntity);
1861 }
1862 contourArea += useEndPoint ? lineIntegral : -lineIntegral;
1863 previousPoint = useEndPoint ? endPoint : startPoint;
1864 }
1865 }
1866 return std::abs(contourArea) + closedArea - subArea;
1867}
1868
1869LC_FirstMoment RS_EntityContainer::firstMomentLineIntegral() const {
1870 // Mirrors secondMomentLineIntegral() traversal; accumulates (∬ x dA, ∬ y dA).
1871 LC_FirstMoment contourMoment;
1872 double contourArea = 0.0;
1873 LC_FirstMoment closedMoment;
1874 LC_FirstMoment subMoment;
1875
1876 RS_Vector previousPoint(false);
1877 for (unsigned i = 0; i < count(); ++i) {
1878 RS_Entity* e = m_entities.at(i);
1879 if (isClosedLoop(*e)) {
1880 if (e->isContainer()) {
1881 subMoment += e->firstMomentLineIntegral();
1882 }
1883 else {
1884 closedMoment += e->firstMomentLineIntegral();
1885 }
1886 continue;
1887 }
1888 e->setLayer(getLayer());
1889 LC_FirstMoment m = e->firstMomentLineIntegral();
1890 double integral = e->areaLineIntegral();
1891 RS_Vector startPt = e->getStartpoint();
1892 RS_Vector endPt = e->getEndpoint();
1893
1894 if (previousPoint.valid && endPt.squaredTo(previousPoint) <= RS_TOLERANCE151.5e-15) {
1895 contourArea -= integral;
1896 contourMoment -= m;
1897 previousPoint = startPt;
1898 } else {
1899 bool useEndPoint = true;
1900 if (!previousPoint.valid && i + 1 < count()) {
1901 auto currEntity = m_entities.at(i + 1);
1902 useEndPoint = endPointDistance(endPt, *currEntity) < endPointDistance(startPt, *currEntity);
1903 }
1904 if (useEndPoint) {
1905 contourArea += integral;
1906 contourMoment += m;
1907 previousPoint = endPt;
1908 } else {
1909 contourArea -= integral;
1910 contourMoment -= m;
1911 previousPoint = startPt;
1912 }
1913 }
1914 }
1915 if (contourArea < 0.0) {
1916 contourMoment = -contourMoment;
1917 }
1918
1919 return contourMoment + closedMoment - subMoment;
1920}
1921
1922LC_SecondMoment RS_EntityContainer::secondMomentLineIntegral() const {
1923 // Follows the same traversal logic as areaLineIntegral(), applying identical
1924 // sign flips so the moments are consistent with the signed area convention.
1925 LC_SecondMoment contourMoment;
1926 double contourArea = 0.0;
1927 LC_SecondMoment closedMoment;
1928 LC_SecondMoment subMoment;
1929
1930 RS_Vector previousPoint(false);
1931 for (unsigned i = 0; i < count(); ++i) {
1932 RS_Entity* e = m_entities.at(i);
1933 if (isClosedLoop(*e)) {
1934 if (e->isContainer()) {
1935 subMoment += e->secondMomentLineIntegral();
1936 }
1937 else {
1938 closedMoment += e->secondMomentLineIntegral();
1939 }
1940 continue;
1941 }
1942 e->setLayer(getLayer());
1943 LC_SecondMoment m = e->secondMomentLineIntegral();
1944 double integral = e->areaLineIntegral();
1945 RS_Vector startPt = e->getStartpoint();
1946 RS_Vector endPt = e->getEndpoint();
1947
1948 if (previousPoint.valid && endPt.squaredTo(previousPoint) <= RS_TOLERANCE151.5e-15) {
1949 contourArea -= integral;
1950 contourMoment -= m;
1951 previousPoint = startPt;
1952 } else {
1953 bool useEndPoint = true;
1954 if (!previousPoint.valid && i + 1 < count()) {
1955 auto currEntity = m_entities.at(i + 1);
1956 useEndPoint = endPointDistance(endPt, *currEntity) < endPointDistance(startPt, *currEntity);
1957 }
1958 if (useEndPoint) {
1959 contourArea += integral;
1960 contourMoment += m;
1961 previousPoint = endPt;
1962 } else {
1963 contourArea -= integral;
1964 contourMoment -= m;
1965 previousPoint = startPt;
1966 }
1967 }
1968 }
1969 // Match the abs-value convention of areaLineIntegral()
1970 if (contourArea < 0.0) {
1971 contourMoment = -contourMoment;
1972 }
1973
1974 return contourMoment + closedMoment - subMoment;
1975}
1976
1977bool RS_EntityContainer::ignoredOnModification() const {
1978 const RS2::EntityType ownType = rtti();
1979 if (RS2::isDimensionalEntity(ownType) || RS2::isTextEntity(ownType) || ownType == RS2::EntityHatch) {
1980 return true;
1981 }
1982 return isParentIgnoredOnModifications();
1983}
1984
1985bool RS_EntityContainer::ignoredSnap() const {
1986 // issue #652 , disable snap for hatch
1987 // TODO, should snapping on hatch be a feature enabled by settings?
1988 if ((getParent() != nullptr) && getParent()->rtti() == RS2::EntityHatch) {
1989 return true;
1990 }
1991 return ignoredOnModification();
1992}
1993
1994#define DEBUG_CONTAINER_DUPLICATE // fixme - sand - disable before push!
1995
1996void RS_EntityContainer::debugEntityAlreadyPresentExists(const RS_Entity* entity) const {
1997#ifdef DEBUG_CONTAINER_DUPLICATE
1998 const qsizetype countOfEntities = m_entities.count(entity);
Value stored to 'countOfEntities' during its initialization is never read
1999 Q_ASSERT(countOfEntities == 0)static_cast<void>(false && (countOfEntities == 0
))
;
2000#endif
2001}
2002
2003QList<RS_Entity*>::const_iterator RS_EntityContainer::begin() const {
2004 return m_entities.begin();
2005}
2006
2007QList<RS_Entity*>::const_iterator RS_EntityContainer::end() const {
2008 return m_entities.end();
2009}
2010
2011QList<RS_Entity*>::const_iterator RS_EntityContainer::cbegin() const {
2012 return m_entities.cbegin();
2013}
2014
2015QList<RS_Entity*>::const_iterator RS_EntityContainer::cend() const {
2016 return m_entities.cend();
2017}
2018
2019QList<RS_Entity*>::iterator RS_EntityContainer::begin() {
2020 return m_entities.begin();
2021}
2022
2023QList<RS_Entity*>::iterator RS_EntityContainer::end() {
2024 return m_entities.end();
2025}
2026
2027/**
2028 * Dumps the entities to stdout.
2029 */
2030std::ostream& operator<<(std::ostream& os, RS_EntityContainer& ec) {
2031 static int indent = 0;
2032 const std::string tab(indent * 2, ' ');
2033 ++indent;
2034 const unsigned long int id = ec.getId();
2035 os << tab << "EntityContainer[" << id << "]: \n";
2036 os << tab << "Borders[" << id << "]: " << ec.m_minV << " - " << ec.m_maxV << "\n";
2037 if (ec.getLayer() != nullptr) {
2038 os << tab << "Layer[" << id << "]: " << ec.getLayer()->getName().toLatin1().data() << "\n";
2039 }
2040 else {
2041 os << tab << "Layer[" << id << "]: <nullptr>\n";
2042 }
2043 //os << ec.layerList << "\n";
2044
2045 os << tab << " Flags[" << id << "]: " << (ec.getFlag(RS2::FlagVisible) ? "RS2::FlagVisible" : "");
2046 os << (ec.getFlag(RS2::FlagDeleted) ? " RS2::FlagUndone" : "");
2047 os << (ec.getFlag(RS2::FlagSelected) ? " RS2::FlagSelected" : "");
2048 os << "\n";
2049
2050 os << tab << "Entities[" << id << "]: \n";
2051 for (const auto t : ec) {
2052 switch (t->rtti()) {
2053 case RS2::EntityInsert:
2054 os << tab << *static_cast<RS_Insert*>(t);
2055 os << tab << *t;
2056 os << tab << *static_cast<RS_EntityContainer*>(t);
2057 break;
2058 default:
2059 if (t->isContainer()) {
2060 os << tab << *static_cast<RS_EntityContainer*>(t);
2061 }
2062 else {
2063 os << tab << *t;
2064 }
2065 break;
2066 }
2067 }
2068 os << tab << "\n\n";
2069 --indent;
2070 return os;
2071}
2072
2073RS_Entity* RS_EntityContainer::first() const {
2074 return m_entities.first();
2075}
2076
2077RS_Entity* RS_EntityContainer::last() const {
2078 return m_entities.last();
2079}
2080
2081const QList<RS_Entity*>& RS_EntityContainer::getEntityList() const {
2082 return m_entities;
2083}
2084
2085std::vector<std::unique_ptr<RS_EntityContainer>> RS_EntityContainer::getLoops() const {
2086 if (m_entities.empty()) {
2087 return {};
2088 }
2089 std::vector<std::unique_ptr<RS_EntityContainer>> loops;
2090 RS_EntityContainer edges(nullptr, false);
2091 for(RS_Entity* en: *this){
2092 if (en != nullptr && en->isContainer()){
2093 if (en->isContainer()){
2094 auto subLoops = static_cast<RS_EntityContainer*>(en)->getLoops();
2095 for (auto& subLoop: subLoops) {
2096 loops.push_back(std::move(subLoop));
2097 }
2098 }
2099 continue;
2100 }
2101
2102 if (en == nullptr || !en->isEdge()) {
2103 continue;
2104 }
2105
2106 //detect circles and whole ellipses
2107 switch (en->rtti()) {
2108 case RS2::EntityEllipse:
2109 if (static_cast<RS_Ellipse*>(en)->isEllipticArc()) {
2110 edges.addEntity(en);
2111 break;
2112 }
2113 [[fallthrough]];
2114 case RS2::EntityCircle: {
2115 auto ec = std::make_unique<RS_EntityContainer>(nullptr, false);
2116 ec->addEntity(en);
2117 loops.push_back(std::move(ec));
2118 break;
2119 }
2120 default:
2121 edges.addEntity(en);
2122 }
2123 }
2124 //find loops
2125 while (!edges.isEmpty()) {
2126 LC_LoopUtils::LoopExtractor extractor{edges};
2127 auto subLoops = extractor.extract();
2128 for (auto& loop : subLoops) {
2129 loops.push_back(std::move(loop));
2130 }
2131 }
2132 return loops;
2133}