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