File indexing completed on 2024-05-19 05:42:23

0001 // ct_lvtqtw_tool_reparent_entity.cpp                                                                          -*-C++-*-
0002 
0003 /*
0004 // Copyright 2023 Codethink Ltd <codethink@codethink.co.uk>
0005 // SPDX-License-Identifier: Apache-2.0
0006 //
0007 // Licensed under the Apache License, Version 2.0 (the "License");
0008 // you may not use this file except in compliance with the License.
0009 // You may obtain a copy of the License at
0010 //
0011 //     http://www.apache.org/licenses/LICENSE-2.0
0012 //
0013 // Unless required by applicable law or agreed to in writing, software
0014 // distributed under the License is distributed on an "AS IS" BASIS,
0015 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0016 // See the License for the specific language governing permissions and
0017 // limitations under the License.
0018 */
0019 
0020 #include <ct_lvtqtc_tool_reparent_entity.h>
0021 
0022 #include <ct_lvtqtc_componententity.h>
0023 #include <ct_lvtqtc_graphicsscene.h>
0024 #include <ct_lvtqtc_graphicsview.h>
0025 #include <ct_lvtqtc_undo_reparent_entity.h>
0026 
0027 #include <ct_lvtqtc_util.h>
0028 
0029 #include <ct_lvtldr_lakosiannode.h>
0030 #include <ct_lvtldr_nodestorage.h>
0031 #include <ct_lvtqtc_iconhelpers.h>
0032 #include <ct_lvtshr_functional.h>
0033 
0034 #include <QApplication>
0035 #include <QDebug>
0036 #include <QInputDialog>
0037 #include <preferences.h>
0038 
0039 using namespace Codethink::lvtldr;
0040 using namespace Codethink::lvtshr;
0041 
0042 namespace Codethink::lvtqtc {
0043 
0044 struct ToolReparentEntity::Private {
0045     enum class ToolReparentEntityState { Browsing, MovingEntity, NoEntitySelected, Finished };
0046 
0047     NodeStorage& nodeStorage;
0048     QCursor currentCursor;
0049     LakosEntity *currentItem = nullptr;
0050     LakosEntity *targetItem = nullptr;
0051     QGraphicsItem *originalCurrentItemParent = nullptr;
0052     qreal originalCurrentItemZValue = 0;
0053     ToolReparentEntityState state = ToolReparentEntityState::Browsing;
0054     QPointF originalCurrentItemPos = {0.0, 0.0};
0055 };
0056 
0057 ToolReparentEntity::ToolReparentEntity(GraphicsView *gv, NodeStorage& nodeStorage):
0058     ITool(tr("Reparent entity"),
0059           tr("Moves an entity from one parent to another"),
0060           IconHelpers::iconFrom(":/icons/reparent_entity"),
0061           gv),
0062     d(std::make_unique<Private>(Private{nodeStorage, Qt::OpenHandCursor}))
0063 {
0064 }
0065 
0066 ToolReparentEntity::~ToolReparentEntity() = default;
0067 
0068 void ToolReparentEntity::mousePressEvent(QMouseEvent *event)
0069 {
0070     qCDebug(LogTool) << name() << "Mouse Press Event";
0071 
0072     switch (d->state) {
0073     case Private::ToolReparentEntityState::Browsing: {
0074         if (d->currentItem == nullptr) {
0075             d->state = Private::ToolReparentEntityState::NoEntitySelected;
0076             graphicsView()->setCursor(Qt::ForbiddenCursor);
0077             return;
0078         }
0079         d->state = Private::ToolReparentEntityState::MovingEntity;
0080 
0081         d->originalCurrentItemParent = d->currentItem->parentItem();
0082         d->currentItem->setParentItem(nullptr);
0083 
0084         d->originalCurrentItemPos = d->currentItem->pos();
0085         graphicsView()->setCursor(Qt::ClosedHandCursor);
0086         return;
0087     }
0088     case Private::ToolReparentEntityState::MovingEntity: {
0089         assert(false && "Unexpected state");
0090     }
0091     case Private::ToolReparentEntityState::NoEntitySelected: {
0092         assert(false && "Unexpected state");
0093     }
0094     case Private::ToolReparentEntityState::Finished: {
0095         assert(false && "Unexpected state");
0096     }
0097     }
0098 }
0099 
0100 void ToolReparentEntity::mouseMoveEvent(QMouseEvent *event)
0101 {
0102     switch (d->state) {
0103     case Private::ToolReparentEntityState::Browsing: {
0104         graphicsView()->setCursor(Qt::OpenHandCursor);
0105         auto extractComponentFromMousePosition = [&]() -> LakosEntity * {
0106             const auto qItems = graphicsView()->itemsByType<LakosEntity>(event->pos());
0107             for (auto const& item : qItems) {
0108                 if (item->instanceType() == DiagramType::ComponentType) {
0109                     return item;
0110                 }
0111             }
0112             return nullptr;
0113         };
0114 
0115         auto *item = extractComponentFromMousePosition();
0116         if (item != d->currentItem) {
0117             updateCurrentItemTo(item);
0118         }
0119         return;
0120     }
0121     case Private::ToolReparentEntityState::MovingEntity: {
0122         assert(d->currentItem && "Unexpected empty item with moving state");
0123 
0124         d->currentItem->setPos(graphicsView()->mapToScene(event->pos()));
0125 
0126         auto extractPackageFromMousePosition = [&]() -> LakosEntity * {
0127             const auto qItems = graphicsView()->itemsByType<LakosEntity>(event->pos());
0128             for (auto const& item : qItems) {
0129                 if (item->instanceType() == DiagramType::PackageType) {
0130                     return item;
0131                 }
0132             }
0133             return nullptr;
0134         };
0135 
0136         auto *item = extractPackageFromMousePosition();
0137         if (item != d->targetItem) {
0138             updateTargetItemTo(item);
0139         }
0140         return;
0141     }
0142     case Private::ToolReparentEntityState::NoEntitySelected: {
0143         // Noop
0144         return;
0145     }
0146     case Private::ToolReparentEntityState::Finished: {
0147         assert(false && "Unexpected state");
0148     }
0149     }
0150 }
0151 
0152 void ToolReparentEntity::mouseReleaseEvent(QMouseEvent *event)
0153 {
0154     qCDebug(LogTool) << name() << "Mouse Release Event";
0155 
0156     using Codethink::lvtshr::ScopeExit;
0157     ScopeExit _([&]() {
0158         deactivate();
0159     });
0160 
0161     switch (d->state) {
0162     case Private::ToolReparentEntityState::Browsing: {
0163         assert(false && "Unexpected state");
0164     }
0165     case Private::ToolReparentEntityState::MovingEntity: {
0166         assert(d->currentItem && "Unexpected empty item with moving state");
0167 
0168         if (d->targetItem == nullptr || d->targetItem == d->originalCurrentItemParent) {
0169             d->currentItem->setPos(d->originalCurrentItemPos);
0170             return;
0171         }
0172 
0173         auto *entity = d->currentItem->internalNode();
0174         auto *oldParent = entity->parent();
0175         auto *newParent = d->targetItem->internalNode();
0176 
0177         auto oldName = entity->name();
0178         auto newName = [&]() {
0179             if (Preferences::useLakosianRules()) {
0180                 auto oldPrefix = oldParent->name();
0181                 auto prefixIndex = oldName.find(oldPrefix);
0182                 if (prefixIndex != 0) {
0183                     // If the prefix isn't found at the beginning of the string, avoid changing anything
0184                     return entity->name();
0185                 }
0186 
0187                 auto result = oldName;
0188                 result.replace(0, oldPrefix.size(), newParent->name());
0189                 return result;
0190             }
0191             return entity->name();
0192         }();
0193         entity->setName(newName);
0194 
0195         auto result = d->nodeStorage.reparentEntity(entity, newParent);
0196         if (result.has_error()) {
0197             switch (result.error().kind) {
0198             case lvtldr::ErrorReparentEntity::Kind::InvalidEntity: {
0199                 assert(false && "Unexpected invalid kind of node");
0200                 break;
0201             }
0202             case lvtldr::ErrorReparentEntity::Kind::InvalidParent: {
0203                 assert(false && "Unexpected invalid parent");
0204                 break;
0205             }
0206             }
0207 
0208             d->state = Private::ToolReparentEntityState::Finished;
0209             return;
0210         }
0211 
0212         Q_EMIT undoCommandCreated(
0213             new lvtqtc::UndoReparentEntity(d->nodeStorage, entity, oldParent, newParent, oldName, newName));
0214 
0215         d->state = Private::ToolReparentEntityState::Finished;
0216     }
0217     case Private::ToolReparentEntityState::NoEntitySelected: {
0218         // Noop
0219         return;
0220     }
0221     case Private::ToolReparentEntityState::Finished: {
0222         assert(false && "Unexpected state");
0223     }
0224     }
0225 }
0226 
0227 void ToolReparentEntity::deactivate()
0228 {
0229     graphicsView()->unsetCursor();
0230     if (d->state == Private::ToolReparentEntityState::MovingEntity) {
0231         d->currentItem->setParentItem(d->originalCurrentItemParent);
0232     }
0233     updateCurrentItemTo(nullptr);
0234     updateTargetItemTo(nullptr);
0235     d->originalCurrentItemPos = {0.0, 0.0};
0236     d->state = Private::ToolReparentEntityState::Browsing;
0237     ITool::deactivate();
0238 }
0239 
0240 void ToolReparentEntity::updateCurrentItemTo(LakosEntity *newItem)
0241 {
0242     auto setFocusedStyle = [&](LakosEntity *item, bool enabled) {
0243         if (enabled) {
0244             item->setOpacity(0.5);
0245             item->setPen(Qt::PenStyle::DashLine);
0246             d->originalCurrentItemZValue = item->zValue();
0247             item->setZValue(std::numeric_limits<qreal>::max());
0248         } else {
0249             item->setOpacity(1.0);
0250             item->setPen(Qt::PenStyle::SolidLine);
0251             item->setZValue(d->originalCurrentItemZValue);
0252         }
0253     };
0254 
0255     if (d->currentItem) {
0256         setFocusedStyle(d->currentItem, false);
0257     }
0258     d->currentItem = newItem;
0259     if (d->currentItem) {
0260         setFocusedStyle(d->currentItem, true);
0261     }
0262 }
0263 
0264 void ToolReparentEntity::updateTargetItemTo(LakosEntity *newItem)
0265 {
0266     static auto setFocusedStyle = [](LakosEntity *item, bool enabled) {
0267         if (enabled) {
0268             item->setOpacity(0.5);
0269             item->setPen(Qt::PenStyle::DashLine);
0270         } else {
0271             item->setOpacity(1.0);
0272             item->setPen(Qt::PenStyle::SolidLine);
0273         }
0274     };
0275 
0276     if (d->targetItem) {
0277         setFocusedStyle(d->targetItem, false);
0278     }
0279     d->targetItem = newItem;
0280     if (d->targetItem) {
0281         setFocusedStyle(d->targetItem, true);
0282     }
0283 }
0284 
0285 } // namespace Codethink::lvtqtc