/* ********************************************************************************** ** ** This file was created for the LibreCAD project (librecad.org), a 2D CAD program. ** ** Copyright (C) 2025 librecad (www.librecad.org) ** Copyright (C) 2025 dxli (github.com/dxli) ** ** This program is free software; you can redistribute it and/or ** modify it under the terms of the GNU General Public License ** as published by the Free Software Foundation; either version 2 ** of the License, or (at your option) any later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software ** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** ********************************************************************************** **/ #include "lc_containertraverser.h" #include "rs.h" #include "rs_entity.h" #include "rs_entitycontainer.h" #include "rs_information.h" namespace { // ParentNode used to track containers during traversing struct ParentNode { ParentNode(const RS_EntityContainer* container, int index): container{container} , index{index} {} // Whether the index is valid within the current parent node bool isValid() const { return container != nullptr && index >= 0 && size_t(index) + 1 <= container->count(); } const RS_EntityContainer* container = nullptr; int index = 0; }; bool isText(const RS_Entity& entity) { switch (entity.rtti()) { case RS2::EntityText: case RS2::EntityMText: return true; default: return false; } } } // namespace namespace lc { // pImp struct struct LC_ContainerTraverser::Data { Data(const RS_EntityContainer& container, RS2::ResolveLevel level): container{&container} , indices{{&container, 0}} , level{level} { } // whether to traverse into bool canResolve(const RS_Entity* entity) const { if (entity == nullptr || !entity->isContainer()) return false; switch(level) { case RS2::ResolveNone: return false; case RS2::ResolveAllButInserts: return entity->rtti() != RS2::EntityInsert; case RS2::ResolveAllButTextImage: return (entity->rtti() != RS2::EntityImage) && isText(*entity); case RS2::ResolveAllButTexts: return !isText(*entity); case RS2::ResolveAll: default: return true; } } const RS_EntityContainer* container = nullptr; std::vector indices; RS2::ResolveLevel level = RS2::ResolveNone; }; LC_ContainerTraverser::LC_ContainerTraverser(const RS_EntityContainer& container, RS2::ResolveLevel level, LC_ContainerTraverser::Direction direction): m_pImp{std::make_unique(container, level)} , m_direction{direction} { } LC_ContainerTraverser::~LC_ContainerTraverser() = default; std::vector LC_ContainerTraverser::entities() { std::vector ret; // collecting entities by the DFS order collect(ret, m_pImp->container); return ret; } void LC_ContainerTraverser::collect(std::vector& items, const RS_EntityContainer* container) const { if (container == nullptr) return; for (RS_Entity* entity: std::as_const(*container)) { if (entity == nullptr) continue; if (entity->isContainer() && m_pImp->canResolve(container)) { collect(items, static_cast(entity)); } else { items.push_back(entity); } } } RS_Entity* LC_ContainerTraverser::first() { m_pImp->indices = std::vector{{m_pImp->container, 0}}; return get(); } RS_Entity* LC_ContainerTraverser::next() { return get(); } RS_Entity* LC_ContainerTraverser::prev() { // create a traverser with reverted direction // so the next traversed node is the previous of the current traverser LC_ContainerTraverser revTraverser{*m_pImp->container, m_pImp->level, LC_ContainerTraverser::Direction::Backword}; revTraverser.m_direction = (m_direction == Direction::Forward) ? Direction::Backword : Direction::Backword; // revert the indices for (ParentNode& node: revTraverser.m_pImp->indices) { if (node.isValid()) node.index = node.container->count() - 1 - node.index; } // the previous. // The index always points to the next [[maybe_unused]] RS_Entity* next = revTraverser.get(); // current [[maybe_unused]] RS_Entity* current = revTraverser.get(); // previous return revTraverser.get(); } RS_Entity* LC_ContainerTraverser::last() { LC_ContainerTraverser revTraverser{*m_pImp->container, m_pImp->level, LC_ContainerTraverser::Direction::Backword}; return revTraverser.get(); } RS_Entity* LC_ContainerTraverser::get() { if (m_pImp->indices.empty()) return nullptr; auto& [container, ii] = m_pImp->indices.back(); if (ii < 0 || size_t(ii) >= container->count()) { // exhausted the current m_pImp->indices.pop_back(); return get(); } RS_Entity* current = container->entityAt(currentIndex()); // advance the index, pointing to the next candidate ++ii; if (current->isContainer() && m_pImp->canResolve(current)) { m_pImp->indices.emplace_back(static_cast(current), 0); return get(); } return current; } size_t LC_ContainerTraverser::currentIndex() const { if (!m_pImp->indices.empty() && !m_pImp->indices.back().container->empty()) { size_t index = m_pImp->indices.back().index; if (m_direction == Direction::Backword) { return m_pImp->indices.back().container->count() - 1 - index; } return index; } return 0; } } // namespace lc