File indexing completed on 2024-05-19 05:42:17
0001 // ct_lvtqtc_graphicsscene.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 <any> 0021 #include <ct_lvtqtc_graphicsscene.h> 0022 0023 #include <ct_lvtqtc_alg_level_layout.h> 0024 #include <ct_lvtqtc_alg_transitive_reduction.h> 0025 0026 #include <ct_lvtclr_colormanagement.h> 0027 #include <ct_lvtqtc_componententity.h> 0028 #include <ct_lvtqtc_edgecollection.h> 0029 #include <ct_lvtqtc_graphicsview.h> 0030 #include <ct_lvtqtc_isa.h> 0031 #include <ct_lvtqtc_lakosentitypluginutils.h> 0032 #include <ct_lvtqtc_logicalentity.h> 0033 #include <ct_lvtqtc_packagedependency.h> 0034 #include <ct_lvtqtc_packageentity.h> 0035 #include <ct_lvtqtc_repositoryentity.h> 0036 #include <ct_lvtqtc_undo_add_component.h> 0037 #include <ct_lvtqtc_undo_add_edge.h> 0038 #include <ct_lvtqtc_undo_add_logicalentity.h> 0039 #include <ct_lvtqtc_undo_add_package.h> 0040 #include <ct_lvtqtc_undo_load_entity.h> 0041 #include <ct_lvtqtc_undo_rename_entity.h> 0042 #include <ct_lvtqtc_usesintheimplementation.h> 0043 #include <ct_lvtqtc_usesintheinterface.h> 0044 0045 #include <ct_lvtldr_componentnode.h> 0046 #include <ct_lvtldr_lakosiannode.h> 0047 #include <ct_lvtldr_nodestorage.h> 0048 #include <ct_lvtldr_packagenode.h> 0049 #include <ct_lvtldr_physicalloader.h> 0050 #include <ct_lvtldr_typenode.h> 0051 #include <ct_lvtmdb_soci_helper.h> 0052 0053 #include <ct_lvtshr_functional.h> 0054 #include <ct_lvtshr_stringhelpers.h> 0055 0056 #include <ct_lvtplg_pluginmanager.h> 0057 #include <ct_lvtqtc_pluginmanagerutils.h> 0058 0059 #include <preferences.h> 0060 0061 #include <QAction> 0062 #include <QApplication> 0063 #include <QCursor> 0064 #include <QGraphicsRectItem> 0065 #include <QGraphicsSceneContextMenuEvent> 0066 #include <QGraphicsView> 0067 #include <QLoggingCategory> 0068 #include <QMenu> 0069 #include <QScreen> 0070 #include <QTextBrowser> 0071 #include <QTimer> 0072 0073 #include <QJsonArray> 0074 #include <QJsonDocument> 0075 #include <QJsonObject> 0076 0077 #include <fstream> 0078 #include <random> 0079 0080 using namespace Codethink::lvtldr; 0081 0082 namespace Codethink::lvtqtc { 0083 0084 struct GraphicsScene::Private { 0085 std::unordered_map<std::string, LakosEntity *> vertices; 0086 // A map of Entity unique id strings against vertex descriptors. 0087 // 0088 // The values for a particular row in a 0089 // database table, such as the 'class_declaration' table, 0090 // should only appear once in the map. 0091 0092 std::unordered_map<lvtldr::LakosianNode *, lvtldr::NodeLoadFlags> entityLoadFlags; 0093 // How each loaded element handles loading data. 0094 0095 std::vector<LakosEntity *> verticesVec; 0096 // Stores the same vertices as above, but with a stable order. 0097 // The order is important because positions of childrens are 0098 // processed by Qt relative to the positions of parents: therefore 0099 // parents have to be positioned before children. 0100 0101 std::vector<LakosRelation *> relationVec; 0102 // stores all relations. 0103 0104 std::shared_ptr<lvtclr::ColorManagement> colorManagement; 0105 // Manages the Color for the nodes 0106 0107 lvtldr::PhysicalLoader physicalLoader; 0108 0109 bool blockNodeResizeOnHover = false; 0110 // blocks mouseHoverEvent resizing the nodes with this flag on. 0111 0112 std::vector<LakosEntity *> selectedEntities; 0113 // The selected entities is chosen by the user by selecting it on the view. 0114 0115 QGraphicsSimpleTextItem *bgMessage = nullptr; 0116 0117 lvtldr::NodeStorage& nodeStorage; 0118 0119 bool showTransitive = false; 0120 // Show all transitive edges on the top level elements. 0121 // up to the children. when this flag changes, all children will 0122 // also set their showTransitive status. but modifying a child 0123 // won't change this. 0124 0125 AlgorithmTransitiveReduction *transitiveReductionAlg = nullptr; 0126 0127 lvtprj::ProjectFile const& projectFile; 0128 0129 std::optional<std::reference_wrapper<lvtplg::PluginManager>> pluginManager = std::nullopt; 0130 0131 explicit Private(NodeStorage& nodeStorage, lvtprj::ProjectFile const& projectFile): 0132 physicalLoader(nodeStorage), nodeStorage(nodeStorage), projectFile(projectFile) 0133 { 0134 showTransitive = Preferences::showRedundantEdgesDefault(); 0135 } 0136 }; 0137 0138 // Spacing between the relation arrows 0139 0140 // -------------------------------------------- 0141 // class GraphicsScene 0142 // -------------------------------------------- 0143 0144 GraphicsScene::GraphicsScene(NodeStorage& nodeStorage, lvtprj::ProjectFile const& projectFile, QObject *parent): 0145 QGraphicsScene(parent), d(std::make_unique<GraphicsScene::Private>(nodeStorage, projectFile)) 0146 { 0147 static int last_id = 0; 0148 this->setObjectName(QString::fromStdString("gs_" + std::to_string(last_id))); 0149 last_id++; 0150 0151 d->transitiveReductionAlg = new AlgorithmTransitiveReduction(); 0152 0153 d->physicalLoader.setGraph(this); 0154 d->physicalLoader.setExtDeps(true); 0155 0156 QObject::connect(&d->nodeStorage, &NodeStorage::storageCleared, this, &GraphicsScene::clearGraph); 0157 0158 QObject::connect(&d->nodeStorage, &NodeStorage::nodeNameChanged, this, [this](LakosianNode *node) { 0159 auto *entity = findLakosEntityFromUid(node->uid()); 0160 if (!entity) { 0161 // This Graphics Scene doesn't have such entity to update 0162 return; 0163 } 0164 0165 // Update the node data 0166 entity->setQualifiedName(node->qualifiedName()); 0167 entity->setName(node->name()); 0168 entity->updateTooltip(); 0169 0170 // Update all relations to that node 0171 for (auto const& ec : entity->edgesCollection()) { 0172 for (auto *relation : ec->relations()) { 0173 relation->updateTooltip(); 0174 } 0175 } 0176 for (auto const& ec : entity->targetCollection()) { 0177 for (auto *relation : ec->relations()) { 0178 relation->updateTooltip(); 0179 } 0180 } 0181 }); 0182 0183 QObject::connect(&d->nodeStorage, &NodeStorage::nodeAdded, this, [this](LakosianNode *node, std::any userdata) { 0184 auto *parentPackage = node->parent(); 0185 auto *parent = parentPackage ? findLakosEntityFromUid(parentPackage->uid()) : nullptr; 0186 0187 try { 0188 auto *anyScene = std::any_cast<GraphicsScene *>(userdata); 0189 if (!parent && anyScene != this) { 0190 return; 0191 } 0192 } catch (const std::bad_any_cast&) { 0193 // noop. 0194 } 0195 0196 lvtshr::LoaderInfo info; 0197 info.setHasParent(parent != nullptr); 0198 0199 // The parameter after newPackageId has the name `selected`, but that actually serves to tell if this 0200 // entity will be a "graph" or a "leaf". 0201 auto *entity = ([&]() -> LakosEntity * { 0202 switch (node->type()) { 0203 case lvtshr::DiagramType::ClassType: 0204 return addUdtVertex(node, true, parent, info); 0205 case lvtshr::DiagramType::ComponentType: 0206 return addCompVertex(node, true, parent, info); 0207 case lvtshr::DiagramType::PackageType: 0208 return addPkgVertex(node, true, parent, info); 0209 case lvtshr::DiagramType::FreeFunctionType: 0210 // Not implemented (We do not support creating free functions in CAD mode) 0211 break; 0212 case lvtshr::DiagramType::RepositoryType: 0213 break; 0214 case lvtshr::DiagramType::NoneType: 0215 break; 0216 } 0217 return nullptr; 0218 })(); 0219 assert(entity); 0220 if (!parent) { 0221 addItem(entity); 0222 } 0223 0224 entity->enableLayoutUpdates(); 0225 entity->show(); 0226 0227 if (parent) { 0228 if (!parent->isExpanded()) { 0229 parent->toggleExpansion(QtcUtil::CreateUndoAction::e_No); 0230 } 0231 0232 if (parent->isCovered()) { 0233 parent->toggleCover(PackageEntity::ToggleContentBehavior::Single, QtcUtil::CreateUndoAction::e_No); 0234 } 0235 } 0236 }); 0237 0238 QObject::connect(&d->nodeStorage, &NodeStorage::nodeRemoved, this, [this](LakosianNode *node) { 0239 auto *entity = findLakosEntityFromUid(node->uid()); 0240 if (!entity) { 0241 // This Graphics Scene doesn't have such entity to update 0242 return; 0243 } 0244 0245 unloadEntity(entity); 0246 }); 0247 0248 QObject::connect(&d->nodeStorage, 0249 &NodeStorage::physicalDependencyAdded, 0250 this, 0251 [this](LakosianNode *source, LakosianNode *target) { 0252 auto *fromEntity = findLakosEntityFromUid(source->uid()); 0253 auto *toEntity = findLakosEntityFromUid(target->uid()); 0254 if (!fromEntity || !toEntity) { 0255 // This Graphics Scene doesn't have such entities to update 0256 return; 0257 } 0258 addEdgeBetween(fromEntity, toEntity, lvtshr::LakosRelationType::PackageDependency); 0259 fromEntity->getTopLevelParent()->calculateEdgeVisibility(); 0260 fromEntity->recursiveEdgeRelayout(); 0261 }); 0262 0263 QObject::connect(&d->nodeStorage, 0264 &NodeStorage::physicalDependencyRemoved, 0265 this, 0266 [this](LakosianNode *source, LakosianNode *target) { 0267 auto *fromEntity = findLakosEntityFromUid(source->uid()); 0268 auto *toEntity = findLakosEntityFromUid(target->uid()); 0269 if (!fromEntity || !toEntity) { 0270 // This Graphics Scene doesn't have such entities to update 0271 return; 0272 } 0273 0274 // explicit copy, so we don't mess with internal iterators. 0275 auto allCollections = fromEntity->edgesCollection(); 0276 0277 std::vector<LakosRelation *> toDelete; 0278 for (const auto& collection : allCollections) { 0279 if (collection->to() == toEntity) { 0280 for (auto *relation : collection->relations()) { 0281 fromEntity->removeEdge(relation); 0282 toEntity->removeEdge(relation); 0283 toDelete.push_back(relation); 0284 } 0285 } 0286 } 0287 0288 for (LakosRelation *rel : toDelete) { 0289 rel->setParent(nullptr); 0290 removeItem(rel); 0291 d->relationVec.erase( 0292 std::remove(std::begin(d->relationVec), std::end(d->relationVec), rel), 0293 std::end(d->relationVec)); 0294 0295 delete rel; 0296 } 0297 0298 fromEntity->getTopLevelParent()->calculateEdgeVisibility(); 0299 fromEntity->recursiveEdgeRelayout(); 0300 }); 0301 0302 QObject::connect(&d->nodeStorage, 0303 &NodeStorage::logicalRelationAdded, 0304 this, 0305 [this](LakosianNode *source, LakosianNode *target, lvtshr::LakosRelationType type) { 0306 auto *fromEntity = findLakosEntityFromUid(source->uid()); 0307 auto *toEntity = findLakosEntityFromUid(target->uid()); 0308 if (!fromEntity || !toEntity) { 0309 // This Graphics Scene doesn't have such entities to update 0310 return; 0311 } 0312 addEdgeBetween(fromEntity, toEntity, type); 0313 0314 fromEntity->getTopLevelParent()->calculateEdgeVisibility(); 0315 fromEntity->recursiveEdgeRelayout(); 0316 }); 0317 0318 QObject::connect( 0319 &d->nodeStorage, 0320 &NodeStorage::logicalRelationRemoved, 0321 this, 0322 [this](LakosianNode *source, LakosianNode *target, lvtshr::LakosRelationType type) { 0323 auto *fromEntity = findLakosEntityFromUid(source->uid()); 0324 auto *toEntity = findLakosEntityFromUid(target->uid()); 0325 0326 // explicit copy, so we don't mess with internal iterators. 0327 auto allCollections = fromEntity->edgesCollection(); 0328 0329 std::vector<LakosRelation *> toDelete; 0330 for (const auto& collection : allCollections) { 0331 if (collection->to() == toEntity) { 0332 for (auto *relation : collection->relations()) { 0333 fromEntity->removeEdge(relation); 0334 toEntity->removeEdge(relation); 0335 toDelete.push_back(relation); 0336 } 0337 } 0338 } 0339 0340 for (LakosRelation *rel : toDelete) { 0341 rel->setParent(nullptr); 0342 removeItem(rel); 0343 d->relationVec.erase(std::remove(std::begin(d->relationVec), std::end(d->relationVec), rel), 0344 std::end(d->relationVec)); 0345 0346 delete rel; 0347 } 0348 0349 fromEntity->getTopLevelParent()->calculateEdgeVisibility(); 0350 fromEntity->recursiveEdgeRelayout(); 0351 }); 0352 0353 QObject::connect(&d->nodeStorage, 0354 &NodeStorage::entityReparent, 0355 this, 0356 [this](LakosianNode *lakosianNode, LakosianNode *oldParent, LakosianNode *newParent) { 0357 auto *entity = findLakosEntityFromUid(lakosianNode->uid()); 0358 if (!entity) { 0359 // This Graphics Scene doesn't have such entity to update 0360 return; 0361 } 0362 0363 auto *oldParentEntity = findLakosEntityFromUid(oldParent->uid()); 0364 auto *newParentEntity = findLakosEntityFromUid(newParent->uid()); 0365 if (oldParentEntity && !newParentEntity) { 0366 // Entity must vanish from this scene, as it doesn't have the other parent to go to. 0367 removeItem(entity); 0368 } else if (newParentEntity) { 0369 // Setting the new parent will automatically update the old parent if it is in this scene. 0370 entity->setParentItem(newParentEntity); 0371 } 0372 }); 0373 0374 d->bgMessage = new QGraphicsSimpleTextItem(); 0375 d->bgMessage->setText(tr("Drag And Drop Elements\nTo Visualize Them")); 0376 d->bgMessage->setVisible(true); 0377 addItem(d->bgMessage); 0378 } 0379 0380 GraphicsScene::~GraphicsScene() noexcept = default; 0381 0382 LakosEntity *GraphicsScene::findLakosEntityFromUid(lvtshr::UniqueId uid) const 0383 { 0384 const auto it = std::find_if(d->verticesVec.cbegin(), d->verticesVec.cend(), [uid](LakosEntity *entity) { 0385 return entity->uniqueId() == uid; 0386 }); 0387 if (it == d->verticesVec.cend()) { 0388 return nullptr; 0389 } 0390 return *it; 0391 } 0392 0393 void GraphicsScene::setColorManagement(const std::shared_ptr<lvtclr::ColorManagement>& colorManagement) 0394 { 0395 assert(colorManagement); 0396 d->colorManagement = colorManagement; 0397 } 0398 0399 std::vector<LakosEntity *> GraphicsScene::selectedEntities() const 0400 { 0401 return d->selectedEntities; 0402 } 0403 0404 // TODO: Pass the entity that we don't want to collapse here.' 0405 void GraphicsScene::collapseSecondaryEntities() 0406 { 0407 for (LakosEntity *entity : d->verticesVec) { 0408 if (entity->parentItem() == nullptr) { 0409 entity->shrink(QtcUtil::CreateUndoAction::e_No); 0410 } 0411 } 0412 reLayout(); 0413 } 0414 0415 void GraphicsScene::reLayout() 0416 { 0417 runLayoutAlgorithm(); 0418 0419 for (auto *entity : d->verticesVec) { 0420 if (entity->parentItem() == nullptr) { 0421 entity->calculateEdgeVisibility(); 0422 entity->recursiveEdgeRelayout(); 0423 } 0424 } 0425 updateBoundingRect(); 0426 } 0427 0428 // This class defines what we need to implement on classes that load graphs visually 0429 void GraphicsScene::clearGraph() 0430 { 0431 d->showTransitive = Preferences::showRedundantEdgesDefault(); 0432 d->vertices.clear(); 0433 d->verticesVec.clear(); 0434 d->relationVec.clear(); 0435 d->entityLoadFlags.clear(); 0436 d->transitiveReductionAlg->reset(); 0437 d->physicalLoader.clear(); 0438 0439 removeItem(d->bgMessage); 0440 clear(); 0441 0442 addItem(d->bgMessage); 0443 d->bgMessage->setPos(sceneRect().center()); 0444 d->bgMessage->setVisible(true); 0445 } 0446 0447 namespace { 0448 0449 QString errorKindToStr(ErrorRemoveEntity::Kind kind, const QString& type) 0450 { 0451 switch (kind) { 0452 case lvtldr::ErrorRemoveEntity::Kind::CannotRemoveWithProviders: { 0453 return QObject::tr( 0454 "Currently we can't remove %1 with connected with other packages, break the connections " 0455 "first.") 0456 .arg(type); 0457 } 0458 case lvtldr::ErrorRemoveEntity::Kind::CannotRemoveWithClients: { 0459 return QObject::tr("Currently we can't remove %1 with clients, break the connections first.").arg(type); 0460 } 0461 case lvtldr::ErrorRemoveEntity::Kind::CannotRemoveWithChildren: { 0462 return QObject::tr("Currently we can't remove %1 that contains children, remove the childs first.").arg(type); 0463 } 0464 } 0465 0466 // Unreachable. 0467 return QString(); 0468 } 0469 0470 template<typename EntityType> 0471 LakosEntity *addVertex(GraphicsScene *scene, 0472 GraphicsScene::Private *d, 0473 lvtldr::LakosianNode *node, 0474 bool selected, 0475 LakosEntity *parent, 0476 lvtshr::LoaderInfo info, 0477 lvtldr::NodeStorage& nodeStorage, 0478 std::optional<std::reference_wrapper<lvtplg::PluginManager>> pm) 0479 { 0480 std::string uid = EntityType::getUniqueId(node->id()); 0481 auto search = d->vertices.find(uid); 0482 if (search != d->vertices.end()) { 0483 if (selected) { 0484 search->second->setHighlighted(selected); 0485 } 0486 return search->second; 0487 } 0488 0489 // freed by either the parent or as a top level item in the GraphicsScene 0490 LakosEntity *entity = new EntityType(node, info); 0491 entity->setColorManagement(d->colorManagement.get()); 0492 entity->setHighlighted(selected); 0493 entity->setZValue(QtcUtil::e_NODE_LAYER); 0494 if (pm) { 0495 entity->setPluginManager(*pm); 0496 } 0497 0498 QObject::connect(entity, &LakosEntity::toggleSelection, scene, [scene, d, entity] { 0499 if (std::find(d->selectedEntities.begin(), d->selectedEntities.end(), entity) == d->selectedEntities.end()) { 0500 d->selectedEntities.push_back(entity); 0501 entity->setSelected(true); 0502 } else { 0503 std::erase(d->selectedEntities, entity); 0504 entity->setSelected(false); 0505 } 0506 entity->updateZLevel(); 0507 0508 Q_EMIT scene->selectedEntityChanged(entity); 0509 }); 0510 0511 QObject::connect(entity, &LakosEntity::requestRemoval, scene, [scene, &nodeStorage, node, entity] { 0512 auto *view = qobject_cast<GraphicsView *>(scene->views().constFirst()); 0513 auto name = node->name(); 0514 auto qualifiedName = node->qualifiedName(); 0515 auto parentQualifiedName = node->parent() ? node->parent()->qualifiedName() : ""; 0516 0517 if (node->type() == lvtshr::DiagramType::PackageType) { 0518 auto err = nodeStorage.removePackage(node); 0519 if (err.has_error()) { 0520 Q_EMIT scene->errorMessage(errorKindToStr(err.error().kind, QStringLiteral("packages"))); 0521 return; 0522 } 0523 view->undoCommandReceived(new UndoAddPackage(scene, 0524 entity->pos(), 0525 name, 0526 qualifiedName, 0527 parentQualifiedName, 0528 QtcUtil::UndoActionType::e_Remove, 0529 nodeStorage)); 0530 } else if (node->type() == lvtshr::DiagramType::ComponentType) { 0531 auto err = nodeStorage.removeComponent(node); 0532 if (err.has_error()) { 0533 Q_EMIT scene->errorMessage(errorKindToStr(err.error().kind, "components")); 0534 return; 0535 } 0536 view->undoCommandReceived(new UndoAddComponent(scene, 0537 entity->pos(), 0538 name, 0539 qualifiedName, 0540 parentQualifiedName, 0541 QtcUtil::UndoActionType::e_Remove, 0542 nodeStorage)); 0543 } else if (node->type() == lvtshr::DiagramType::ClassType) { 0544 auto err = nodeStorage.removeLogicalEntity(node); 0545 if (err.has_error()) { 0546 Q_EMIT scene->errorMessage(errorKindToStr(err.error().kind, "user defined type")); 0547 return; 0548 } 0549 view->undoCommandReceived(new UndoAddLogicalEntity(scene, 0550 entity->pos(), 0551 name, 0552 qualifiedName, 0553 parentQualifiedName, 0554 QtcUtil::UndoActionType::e_Remove, 0555 nodeStorage)); 0556 } else { 0557 Q_EMIT scene->errorMessage("Invalid entity type for removal."); 0558 } 0559 }); 0560 0561 scene->connectEntitySignals(entity); 0562 0563 if (parent) { 0564 entity->setParentItem(parent); 0565 } 0566 0567 QObject::connect(entity, &LakosEntity::createReportActionClicked, scene, &GraphicsScene::createReportActionClicked); 0568 0569 d->vertices.insert({uid, entity}); 0570 d->verticesVec.push_back(entity); 0571 d->entityLoadFlags.insert({entity->internalNode(), lvtldr::NodeLoadFlags{}}); 0572 if (Preferences::enableDebugOutput()) { 0573 qDebug() << "Setting empty flags for" << QString::fromStdString(entity->qualifiedName()); 0574 } 0575 d->bgMessage->setVisible(false); 0576 return entity; 0577 } 0578 } // namespace 0579 0580 LakosEntity * 0581 GraphicsScene::addUdtVertex(lvtldr::LakosianNode *node, bool selected, LakosEntity *parent, lvtshr::LoaderInfo info) 0582 { 0583 return addVertex<LogicalEntity>(this, d.get(), node, selected, parent, info, d->nodeStorage, d->pluginManager); 0584 } 0585 0586 LakosEntity * 0587 GraphicsScene::addPkgVertex(lvtldr::LakosianNode *node, bool selected, LakosEntity *parent, lvtshr::LoaderInfo info) 0588 { 0589 return addVertex<PackageEntity>(this, d.get(), node, selected, parent, info, d->nodeStorage, d->pluginManager); 0590 } 0591 0592 LakosEntity *GraphicsScene::addRepositoryVertex(lvtldr::LakosianNode *node, 0593 bool selected, 0594 LakosEntity *parent, 0595 lvtshr::LoaderInfo info) 0596 { 0597 return addVertex<RepositoryEntity>(this, d.get(), node, selected, parent, info, d->nodeStorage, d->pluginManager); 0598 } 0599 0600 LakosEntity * 0601 GraphicsScene::addCompVertex(lvtldr::LakosianNode *node, bool selected, LakosEntity *parent, lvtshr::LoaderInfo info) 0602 { 0603 return addVertex<ComponentEntity>(this, d.get(), node, selected, parent, info, d->nodeStorage, d->pluginManager); 0604 } 0605 0606 namespace { 0607 0608 template<typename RelationType> 0609 LakosRelation *addClassBasedRelation(GraphicsScene *scn, LakosEntity *source, LakosEntity *target) 0610 { 0611 assert(source && source->instanceType() == lvtshr::DiagramType::ClassType); 0612 assert(target && target->instanceType() == lvtshr::DiagramType::ClassType); 0613 if (source->hasRelationshipWith(target)) { 0614 return nullptr; 0615 } 0616 0617 // add an extra edge between the top level containers 0618 LakosEntity *sourceParent = source->getTopLevelParent(); 0619 LakosEntity *targetParent = target->getTopLevelParent(); 0620 if (sourceParent != targetParent) { 0621 if (sourceParent != source || targetParent != target) { 0622 // if both parents are logical, add this kind of logical relation 0623 if (sourceParent->instanceType() == lvtshr::DiagramType::ClassType 0624 && targetParent->instanceType() == lvtshr::DiagramType::ClassType 0625 && !sourceParent->hasRelationshipWith(targetParent)) { 0626 (void) scn->addRelation(new RelationType(sourceParent, targetParent)); 0627 } 0628 // if both parents are physical, add a physical dependency 0629 if (sourceParent->instanceType() != lvtshr::DiagramType::ClassType 0630 && targetParent->instanceType() != lvtshr::DiagramType::ClassType 0631 && !sourceParent->hasRelationshipWith(targetParent)) { 0632 (void) scn->addRelation(new PackageDependency(sourceParent, targetParent)); 0633 } 0634 } 0635 } 0636 0637 return scn->addRelation(new RelationType(source, target)); 0638 } 0639 0640 } // namespace 0641 0642 LakosRelation *GraphicsScene::addIsARelation(LakosEntity *source, LakosEntity *target) 0643 { 0644 return addClassBasedRelation<IsA>(this, source, target); 0645 } 0646 0647 LakosRelation *GraphicsScene::addUsesInTheInterfaceRelation(LakosEntity *source, LakosEntity *target) 0648 { 0649 return addClassBasedRelation<UsesInTheInterface>(this, source, target); 0650 } 0651 0652 LakosRelation *GraphicsScene::addUsesInTheImplementationRelation(LakosEntity *source, LakosEntity *target) 0653 { 0654 return addClassBasedRelation<UsesInTheImplementation>(this, source, target); 0655 } 0656 0657 LakosRelation *GraphicsScene::addPackageDependencyRelation(LakosEntity *source, LakosEntity *target) 0658 { 0659 assert(source && source->instanceType() != lvtshr::DiagramType::ClassType); 0660 assert(target && target->instanceType() != lvtshr::DiagramType::ClassType); 0661 0662 if (source->hasRelationshipWith(target)) { 0663 return nullptr; 0664 } 0665 0666 LakosEntity *sourceParent = source->getTopLevelParent(); 0667 LakosEntity *targetParent = target->getTopLevelParent(); 0668 0669 if (sourceParent != targetParent) { 0670 if ((sourceParent != source || targetParent != target) && !sourceParent->hasRelationshipWith(targetParent)) { 0671 assert(sourceParent->instanceType() != lvtshr::DiagramType::ClassType); 0672 assert(targetParent->instanceType() != lvtshr::DiagramType::ClassType); 0673 (void) addRelation(new PackageDependency(sourceParent, targetParent)); 0674 } 0675 } 0676 0677 return addRelation(new PackageDependency(source, target)); 0678 } 0679 0680 LakosEntity *GraphicsScene::outermostParent(LakosEntity *a, LakosEntity *b) 0681 { 0682 // If an edge has from() and to() on different parents, we still 0683 // could hit a possibility that there's a common parent. such as 0684 // |-------------------------- 0685 // | parent 0686 // | |=====| |---------| 0687 // | |from |-------|-->|to| | 0688 // | |=====| | ---- | 0689 // | |---------| 0690 ///--------------------------- 0691 0692 const QList<LakosEntity *> fromParents = a->parentHierarchy(); 0693 const QList<LakosEntity *> toParents = b->parentHierarchy(); 0694 0695 for (auto *fromParent : fromParents) { 0696 for (auto *toParent : toParents) { 0697 if (fromParent == toParent) { 0698 return fromParent; 0699 } 0700 } 0701 } 0702 return nullptr; 0703 } 0704 0705 // TODO: Move this logic to the LakosEntity code. 0706 LakosRelation *GraphicsScene::addRelation(LakosRelation *relation, bool isVisible) 0707 { 0708 LakosEntity *from = relation->from(); 0709 LakosEntity *to = relation->to(); 0710 0711 if (from->isAncestorOf(to) || to->isAncestorOf(from)) { 0712 if (!relation->scene()) { 0713 delete relation; 0714 } 0715 return nullptr; 0716 } 0717 0718 relation->setShouldBeHidden(!isVisible); 0719 0720 // From Here ----------------- 0721 std::vector<std::shared_ptr<EdgeCollection>>& edges = from->edgesCollection(); 0722 auto it = std::find_if(std::begin(edges), std::end(edges), [to](const std::shared_ptr<EdgeCollection>& edge) { 0723 return edge->to() == to; 0724 }); 0725 0726 if (it == std::end(edges)) { 0727 // TODO: Move he initialization to constructor. 0728 auto edgeCollection = std::make_shared<EdgeCollection>(); 0729 edgeCollection->setFrom(from); 0730 edgeCollection->setTo(to); 0731 edges.push_back(edgeCollection); 0732 to->addTargetCollection(edgeCollection); 0733 it = std::prev(std::end(edges)); 0734 } 0735 0736 relation = (*it)->addRelation(relation); 0737 0738 auto *commonParent = from->commonAncestorItem(to); 0739 0740 if (commonParent) { 0741 relation->setParentItem(commonParent); 0742 } 0743 relation->setZValue(QtcUtil::e_EDGE_LAYER); 0744 d->relationVec.push_back(relation); 0745 (*it)->layoutRelations(); 0746 0747 connect(relation, &LakosRelation::undoCommandCreated, this, [this](QUndoCommand *command) { 0748 auto *view = qobject_cast<GraphicsView *>(views().constFirst()); 0749 assert(view); 0750 view->undoCommandReceived(command); 0751 }); 0752 0753 connect(relation, &LakosRelation::requestRemoval, this, [this, relation] { 0754 using lvtshr::LakosRelationType; 0755 0756 auto *from = relation->from(); 0757 auto *to = relation->to(); 0758 0759 auto relationType = relation->relationType(); 0760 0761 auto isLogicalRelation = 0762 (relationType == LakosRelationType::IsA || relationType == LakosRelationType::UsesInTheImplementation 0763 || relationType == LakosRelationType::UsesInTheInterface); 0764 if (isLogicalRelation) { 0765 auto *fromTypeNode = dynamic_cast<TypeNode *>(from->internalNode()); 0766 auto *toTypeNode = dynamic_cast<TypeNode *>(to->internalNode()); 0767 assert(fromTypeNode && toTypeNode); 0768 0769 auto result = d->nodeStorage.removeLogicalRelation(fromTypeNode, toTypeNode, relationType); 0770 if (result.has_error()) { 0771 using ErrorKind = ErrorRemoveLogicalRelation::Kind; 0772 switch (result.error().kind) { 0773 case (ErrorKind::InexistentRelation): { 0774 assert(false && "GraphicsScene has a relation not present in the model"); 0775 } 0776 case (ErrorKind::InvalidLakosRelationType): { 0777 assert(false && "Trying to remove a LakosRelation with unexpected model type"); 0778 } 0779 } 0780 } 0781 } 0782 0783 auto isPhysicalRelation = relationType == LakosRelationType::PackageDependency; 0784 if (isPhysicalRelation) { 0785 auto *fromPhysicalNode = from->internalNode(); 0786 auto *toPhysicalNode = to->internalNode(); 0787 assert(fromPhysicalNode && toPhysicalNode); 0788 0789 auto result = d->nodeStorage.removePhysicalDependency(fromPhysicalNode, toPhysicalNode); 0790 if (result.has_error()) { 0791 using ErrorKind = ErrorRemovePhysicalDependency::Kind; 0792 switch (result.error().kind) { 0793 case (ErrorKind::InexistentRelation): { 0794 assert(false && "GraphicsScene has a relation not present in the model"); 0795 } 0796 } 0797 } 0798 } 0799 0800 auto *view = qobject_cast<GraphicsView *>(views().constFirst()); 0801 assert(view); 0802 view->undoCommandReceived(new UndoAddEdge(from->qualifiedName(), 0803 to->qualifiedName(), 0804 relationType, 0805 QtcUtil::UndoActionType::e_Remove, 0806 d->nodeStorage)); 0807 }); 0808 0809 return relation; 0810 } 0811 0812 void GraphicsScene::runLayoutAlgorithm() 0813 { 0814 auto topLevelEntities = std::vector<LakosEntity *>(); 0815 for (auto *e : d->verticesVec) { 0816 if (!e->parentItem()) { 0817 topLevelEntities.push_back(e); 0818 } 0819 } 0820 0821 auto direction = Preferences::invertVerticalLevelizationLayout() ? +1 : -1; 0822 std::function<void(LakosEntity *)> recursiveLevelLayout = [&](LakosEntity *e) -> void { 0823 auto childs = e->lakosEntities(); 0824 for (auto *c : childs) { 0825 recursiveLevelLayout(c); 0826 } 0827 e->levelizationLayout(LakosEntity::LevelizationLayoutType::Vertical, direction); 0828 }; 0829 for (auto *e : topLevelEntities) { 0830 recursiveLevelLayout(e); 0831 } 0832 auto entityToLevel = computeLevelForEntities(topLevelEntities); 0833 runLevelizationLayout(entityToLevel, 0834 {LakosEntity::LevelizationLayoutType::Vertical, 0835 direction, 0836 Preferences::spaceBetweenLevels(), 0837 Preferences::spaceBetweenSublevels(), 0838 Preferences::spaceBetweenEntities(), 0839 Preferences::maxEntitiesPerLevel()}); 0840 } 0841 0842 bool GraphicsScene::blockNodeResizeOnHover() const 0843 { 0844 return d->blockNodeResizeOnHover; 0845 } 0846 0847 void GraphicsScene::setEntityPos(const lvtshr::UniqueId& uid, QPointF pos) const 0848 { 0849 auto *entity = findLakosEntityFromUid(uid); 0850 entity->setPos(pos); 0851 0852 // triggers a recalculation of the parent's boundaries. 0853 Q_EMIT entity->moving(); 0854 0855 // Tells the system that the graph updated. 0856 Q_EMIT entity->graphUpdate(); 0857 } 0858 0859 void GraphicsScene::setBlockNodeResizeOnHover(bool block) 0860 { 0861 d->blockNodeResizeOnHover = block; 0862 0863 if (block) { 0864 // View "0" is the main view, view "1" is the minimap. 0865 QGraphicsView *view = views().at(0); 0866 const QPoint viewCoords = view->mapFromGlobal(QCursor::pos()); 0867 const QPointF sceneCoords = view->mapToScene(viewCoords); 0868 const auto itemList = items(sceneCoords); 0869 for (QGraphicsItem *item : itemList) { 0870 if (auto *lakosEntity = qgraphicsitem_cast<LakosEntity *>(item)) { 0871 lakosEntity->setFlag(QGraphicsItem::ItemIgnoresTransformations, false); 0872 } 0873 } 0874 } 0875 update(); 0876 } 0877 0878 // ---------- Our State Machine starts Here. ------------------ 0879 lvtldr::NodeLoadFlags GraphicsScene::loadFlagsFor(lvtldr::LakosianNode *node) const 0880 { 0881 const auto search = d->entityLoadFlags.find(node); 0882 if (search != d->entityLoadFlags.end()) { 0883 return search->second; 0884 } 0885 0886 return NodeLoadFlags{}; 0887 } 0888 0889 // HACK: This should really not exist. 0890 void GraphicsScene::fixRelationsParentRelationship() 0891 { 0892 auto for_each_relation = [this](const std::function<void(LakosRelation *)>& func) { 0893 for (const auto *vertex : d->verticesVec) { 0894 for (const auto& edges : vertex->edgesCollection()) { 0895 for (auto *edge : edges->relations()) { 0896 func(edge); 0897 } 0898 } 0899 } 0900 }; 0901 0902 // TODO: Move this somewhere else. to LakosEntity perhaps. 0903 auto is_connected = [](LakosEntity *a, LakosEntity *b) -> bool { 0904 const auto& collection = a->edgesCollection(); 0905 return std::any_of(collection.begin(), collection.end(), [b](const auto& edges) { 0906 return edges->to() == b; 0907 }); 0908 }; 0909 0910 for_each_relation([this, is_connected](LakosRelation *edge) { 0911 auto *parent = qgraphicsitem_cast<LakosEntity *>(edge->from()->commonAncestorItem(edge->to())); 0912 if (parent) { 0913 edge->setParentItem(parent); 0914 return; 0915 } 0916 0917 // We don't have a common parent, resort to heuristics. 0918 auto *pFrom = qgraphicsitem_cast<LakosEntity *>(edge->from()->parentItem()); 0919 auto *pTo = qgraphicsitem_cast<LakosEntity *>(edge->to()->parentItem()); 0920 if (pFrom && pFrom != pTo) { 0921 // on this case we need to add a edge between the two different parents if there's none. 0922 edge->setParentItem(nullptr); 0923 0924 // A component that has a parent *can* point to a component that 0925 // has no parent, such as Qt classes, STD entities and so on. 0926 // we need to add those to a subcomponent based on the include path, 0927 // but that won't happen now. 0928 if (pFrom && pTo) { 0929 if (!is_connected(pFrom, pTo)) { 0930 if (pFrom->instanceType() != lvtshr::DiagramType::ClassType 0931 && pTo->instanceType() != lvtshr::DiagramType::ClassType) { 0932 addPackageDependencyRelation(pFrom, pTo); 0933 } 0934 } 0935 } 0936 } else { 0937 edge->setParentItem(nullptr); 0938 } 0939 }); 0940 } 0941 0942 void GraphicsScene::enableLayoutUpdates() 0943 { 0944 for (LakosEntity *entity : d->verticesVec) { 0945 entity->enableLayoutUpdates(); 0946 } 0947 } 0948 0949 void GraphicsScene::layoutDone() 0950 { 0951 reLayout(); 0952 } 0953 0954 } // namespace Codethink::lvtqtc 0955 0956 namespace Codethink::lvtqtc { 0957 0958 std::vector<LakosEntity *>& GraphicsScene::allEntities() const 0959 { 0960 return d->verticesVec; 0961 } 0962 0963 void GraphicsScene::connectEntitySignals(LakosEntity *entity) 0964 { 0965 assert(entity); 0966 0967 const std::string qualifiedName = entity->qualifiedName(); 0968 0969 connect(entity, &LogicalEntity::navigateRequested, this, [qualifiedName] { 0970 // TODO: Navigate. 0971 (void) qualifiedName; 0972 }); 0973 0974 connect(entity, &LakosEntity::undoCommandCreated, this, [this](QUndoCommand *command) { 0975 auto *view = qobject_cast<GraphicsView *>(views().constFirst()); 0976 assert(view); 0977 view->undoCommandReceived(command); 0978 }); 0979 0980 connect(entity, &LogicalEntity::graphUpdate, this, [this] { 0981 updateBoundingRect(); 0982 }); 0983 0984 connect(entity, &LakosEntity::entityRenameRequest, this, [this](lvtshr::UniqueId uid, const std::string& newName) { 0985 auto *node = d->nodeStorage.findById(uid); 0986 auto oldName = node->name(); 0987 auto oldQualifiedName = node->qualifiedName(); 0988 node->setName(newName); 0989 0990 auto *view = qobject_cast<GraphicsView *>(views().constFirst()); 0991 view->undoCommandReceived(new UndoRenameEntity(node->qualifiedName(), 0992 oldQualifiedName, 0993 node->type(), 0994 oldName, 0995 newName, 0996 d->nodeStorage)); 0997 }); 0998 0999 connect(entity, &LakosEntity::requestRelayout, this, [this, entity] { 1000 // TODO: Review this 1001 entity->recursiveEdgeRelayout(); 1002 updateBoundingRect(); 1003 }); 1004 1005 connect(entity, &LakosEntity::loadChildren, this, [entity, this] { 1006 auto *view = qobject_cast<GraphicsView *>(views().constFirst()); 1007 view->undoCommandReceived(new UndoLoadEntity(this, 1008 entity->internalNode()->uid(), 1009 UnloadDepth::Children, 1010 QtcUtil::UndoActionType::e_Add)); 1011 view->fitAllInView(); 1012 }); 1013 1014 connect(entity, &LakosEntity::loadClients, this, [entity, this](bool onlyLocal) { 1015 auto *node = entity->internalNode(); 1016 1017 auto& flags = d->entityLoadFlags[node]; 1018 flags.traverseClients = true; 1019 flags.traverseClientsOnlyLocal = onlyLocal; 1020 1021 finalizeEntityPartialLoad(entity); 1022 }); 1023 1024 connect(entity, &LakosEntity::loadProviders, this, [entity, this](bool onlyLocal) { 1025 auto *node = entity->internalNode(); 1026 1027 auto& flags = d->entityLoadFlags[node]; 1028 flags.traverseProviders = true; 1029 flags.traverseProvidersOnlyLocal = onlyLocal; 1030 1031 finalizeEntityPartialLoad(entity); 1032 }); 1033 1034 connect(entity, &LakosEntity::coverChanged, this, [this, entity]() { 1035 if (entity->isCovered()) { 1036 return; 1037 } 1038 1039 d->transitiveReductionAlg->reset(); 1040 searchTransitiveRelations(); 1041 transitiveRelationSearchFinished(); 1042 }); 1043 1044 // Perhaps this should be a toggle? 1045 connect(entity, &LakosEntity::requestGraphRelayout, this, [this] { 1046 if (Preferences::enableDebugOutput()) { 1047 qDebug() << "Running graph relayout"; 1048 } 1049 reLayout(); 1050 }); 1051 1052 connect(entity, &LakosEntity::unloadThis, this, [this, entity] { 1053 auto *view = qobject_cast<GraphicsView *>(views().constFirst()); 1054 view->undoCommandReceived(new UndoLoadEntity(this, 1055 entity->internalNode()->uid(), 1056 UnloadDepth::Entity, 1057 QtcUtil::UndoActionType::e_Remove)); 1058 }); 1059 1060 connect(entity, &LakosEntity::unloadChildren, this, [this, entity] { 1061 auto *view = qobject_cast<GraphicsView *>(views().constFirst()); 1062 view->undoCommandReceived(new UndoLoadEntity(this, 1063 entity->internalNode()->uid(), 1064 UnloadDepth::Children, 1065 QtcUtil::UndoActionType::e_Remove)); 1066 }); 1067 1068 connect(entity, &LakosEntity::requestNewTab, this, &GraphicsScene::requestNewTab); 1069 } 1070 1071 void GraphicsScene::loadEntity(lvtshr::UniqueId uuid, UnloadDepth depth) 1072 { 1073 // TODO: This needs to be improved with the other possible load flags. 1074 if (depth == UnloadDepth::Children) { 1075 LakosEntity *entity = findLakosEntityFromUid(uuid); 1076 auto *node = entity->internalNode(); 1077 d->entityLoadFlags[node].loadChildren = true; 1078 finalizeEntityPartialLoad(entity); 1079 } else { 1080 auto *node = d->nodeStorage.findById(uuid); 1081 const QString qualName = QString::fromStdString(node->qualifiedName()); 1082 1083 loadEntityByQualifiedName(qualName, QPoint(0, 0)); 1084 1085 d->transitiveReductionAlg->reset(); 1086 searchTransitiveRelations(); 1087 reLayout(); 1088 } 1089 } 1090 1091 void GraphicsScene::unloadEntity(lvtshr::UniqueId uuid, UnloadDepth depth) 1092 { 1093 LakosEntity *entity = findLakosEntityFromUid(uuid); 1094 if (!entity) { 1095 return; 1096 } 1097 if (depth == UnloadDepth::Children) { 1098 const auto entities = entity->lakosEntities(); 1099 for (auto *child : entities) { 1100 unloadEntity(child); 1101 } 1102 1103 auto& flags = d->entityLoadFlags[entity->internalNode()]; 1104 flags.loadChildren = false; 1105 } else { 1106 unloadEntity(entity); 1107 } 1108 1109 if (std::find(d->selectedEntities.begin(), d->selectedEntities.end(), entity) != d->selectedEntities.end()) { 1110 std::erase(d->selectedEntities, entity); 1111 } 1112 1113 d->transitiveReductionAlg->reset(); 1114 searchTransitiveRelations(); 1115 reLayout(); 1116 } 1117 1118 void GraphicsScene::unloadEntity(LakosEntity *entity) 1119 { 1120 const auto entities = entity->lakosEntities(); 1121 for (auto *child : entities) { 1122 unloadEntity(child); 1123 } 1124 1125 // lambda to clean collections: 1126 auto cleanCollections = [this](const std::shared_ptr<EdgeCollection>& ec) { 1127 while (!ec->relations().empty()) { 1128 auto *edge = ec->relations().front(); 1129 auto pos = std::find(std::begin(d->relationVec), std::end(d->relationVec), edge); 1130 if (pos != std::end(d->relationVec)) { 1131 d->relationVec.erase(pos); 1132 } 1133 1134 // this can't be in the edge destructor because 1135 // we need to make sure we are deleting all edges *before* nodes 1136 // and in some places that causes a crash. Leave at here for now. 1137 edge->from()->removeEdge(edge); 1138 edge->to()->removeEdge(edge); 1139 delete edge; 1140 } 1141 }; 1142 1143 while (!entity->edgesCollection().empty()) { 1144 auto ec = entity->edgesCollection().front(); 1145 cleanCollections(ec); 1146 } 1147 1148 while (!entity->targetCollection().empty()) { 1149 auto ec = entity->targetCollection().front(); 1150 cleanCollections(ec); 1151 } 1152 1153 auto id = entity->internalNode()->id(); 1154 const std::string uniqueId = entity->instanceType() == lvtshr::DiagramType::PackageType 1155 ? PackageEntity::getUniqueId(id) 1156 : entity->instanceType() == lvtshr::DiagramType::ComponentType ? ComponentEntity::getUniqueId(id) 1157 : entity->instanceType() == lvtshr::DiagramType::ClassType ? LogicalEntity::getUniqueId(id) 1158 : std::string{}; 1159 1160 if (uniqueId.empty()) { 1161 Q_EMIT errorMessage("Tried to remove an invalid element."); 1162 return; 1163 } 1164 1165 d->physicalLoader.unvisitVertex(entity->internalNode()); 1166 d->vertices.erase(uniqueId); 1167 d->verticesVec.erase(std::remove(std::begin(d->verticesVec), std::end(d->verticesVec), entity), 1168 std::end(d->verticesVec)); 1169 1170 if (Preferences::enableDebugOutput()) { 1171 qDebug() << "Unloading entity" << intptr_t(entity) << QString::fromStdString(entity->name()); 1172 } 1173 1174 if (d->vertices.empty()) { 1175 d->bgMessage->setPos(sceneRect().center()); 1176 d->bgMessage->show(); 1177 } 1178 1179 delete entity; 1180 } 1181 1182 void GraphicsScene::finalizeEntityPartialLoad(LakosEntity *entity) 1183 { 1184 auto *node = entity->internalNode(); 1185 auto flags = d->entityLoadFlags[node]; 1186 1187 bool success = d->physicalLoader.load(node, flags).has_value(); 1188 if (!success) { 1189 return; 1190 } 1191 1192 // some relationships could have been added, but not on the scene. 1193 for (auto *vertice : d->verticesVec) { 1194 if (!vertice->scene()) { 1195 addItem(vertice); 1196 } 1197 } 1198 1199 for (auto *relation : d->relationVec) { 1200 if (!relation->scene()) { 1201 addItem(relation); 1202 } 1203 } 1204 1205 d->transitiveReductionAlg->reset(); 1206 searchTransitiveRelations(); 1207 transitiveRelationSearchFinished(); 1208 1209 for (auto *vertice : d->verticesVec) { 1210 vertice->enableLayoutUpdates(); 1211 } 1212 1213 fixRelationsParentRelationship(); 1214 1215 entity->calculateEdgeVisibility(); 1216 reLayout(); 1217 } 1218 1219 void GraphicsScene::populateMenu(QMenu& menu, QMenu *debugMenu) 1220 { 1221 using namespace Codethink::lvtplg; 1222 1223 if (d->pluginManager) { 1224 auto getAllEntitiesInCurrentView = [this]() { 1225 std::vector<Entity> entitiesInView{}; 1226 for (auto *e : allEntities()) { 1227 entitiesInView.emplace_back(createWrappedEntityFromLakosEntity(e)); 1228 } 1229 return entitiesInView; 1230 }; 1231 auto getEntityByQualifiedName = [this](std::string const& qualifiedName) -> std::optional<Entity> { 1232 auto *e = entityByQualifiedName(qualifiedName); 1233 if (!e) { 1234 return std::nullopt; 1235 } 1236 return createWrappedEntityFromLakosEntity(e); 1237 }; 1238 auto getEdgeByQualifiedName = [this](std::string const& fromQualifiedName, 1239 std::string const& toQualifiedName) -> std::optional<Edge> { 1240 auto *fromEntity = entityByQualifiedName(fromQualifiedName); 1241 if (!fromEntity) { 1242 return std::nullopt; 1243 } 1244 auto *toEntity = entityByQualifiedName(toQualifiedName); 1245 if (!toEntity) { 1246 return std::nullopt; 1247 } 1248 return createWrappedEdgeFromLakosEntity(fromEntity, toEntity); 1249 }; 1250 auto loadEntityByQualifiedName = [this](std::string const& qualifiedName) { 1251 this->loadEntityByQualifiedName(QString::fromStdString(qualifiedName), {0, 0}); 1252 this->reLayout(); 1253 }; 1254 auto addEdgeByQualifiedName = [this](std::string const& fromQualifiedName, 1255 std::string const& toQualifiedName) -> std::optional<Edge> { 1256 auto *fromEntity = entityByQualifiedName(fromQualifiedName); 1257 auto *toEntity = entityByQualifiedName(toQualifiedName); 1258 if (!fromEntity || !toEntity || fromEntity == toEntity || fromEntity->hasRelationshipWith(toEntity)) { 1259 return std::nullopt; 1260 } 1261 this->addEdgeBetween(fromEntity, toEntity, lvtshr::LakosRelationType::PackageDependency); 1262 return createWrappedEdgeFromLakosEntity(fromEntity, toEntity); 1263 }; 1264 auto removeEdgeByQualifiedName = [this](std::string const& fromQualifiedName, 1265 std::string const& toQualifiedName) { 1266 auto *fromEntity = entityByQualifiedName(fromQualifiedName); 1267 auto *toEntity = entityByQualifiedName(toQualifiedName); 1268 if (!fromEntity || !toEntity || fromEntity == toEntity || !fromEntity->hasRelationshipWith(toEntity)) { 1269 return; 1270 } 1271 this->removeEdge(*fromEntity, *toEntity); 1272 }; 1273 auto hasEdgeByQualifiedName = [this](std::string const& fromQualifiedName, std::string const& toQualifiedName) { 1274 auto *fromEntity = entityByQualifiedName(fromQualifiedName); 1275 auto *toEntity = entityByQualifiedName(toQualifiedName); 1276 if (!fromEntity || !toEntity || fromEntity == toEntity) { 1277 return false; 1278 } 1279 return fromEntity->hasRelationshipWith(toEntity); 1280 }; 1281 using ctxMenuAction_f = std::function<void(PluginContextMenuActionHandler *)>; 1282 auto registerContextMenu = [=, this, &menu](std::string const& title, ctxMenuAction_f const& userAction) { 1283 // make a copy of all the actions we currently have, so we can 1284 // iterate through it without having problems. 1285 const auto currentActions = menu.actions(); 1286 1287 // Remove pre-existing actions from scripts. 1288 for (QAction *act : currentActions) { 1289 if (act->text() == QString::fromStdString(title)) { 1290 errorMessage( 1291 "Two or more of your plugins declares\n" 1292 "the same context menu, This is not supported."); 1293 return; 1294 } 1295 } 1296 1297 auto *action = menu.addAction(QString::fromStdString(title)); 1298 connect(action, &QAction::triggered, this, [=, this]() { 1299 auto getPluginData = [this](auto&& id) { 1300 auto& pm = d->pluginManager.value().get(); 1301 return pm.getPluginData(id); 1302 }; 1303 auto getTree = [this](std::string const& id) { 1304 auto *pm = &d->pluginManager->get(); 1305 return PluginManagerQtUtils::createPluginTreeWidgetHandler(pm, id, this); 1306 }; 1307 auto getDock = [this](std::string const& id) { 1308 auto *pm = &d->pluginManager->get(); 1309 return PluginManagerQtUtils::createPluginDockWidgetHandler(pm, id); 1310 }; 1311 auto runQueryOnDatabase = [this](std::string const& dbQuery) -> std::vector<std::vector<RawDBData>> { 1312 return lvtmdb::SociHelper::runSingleQuery(d->nodeStorage.getSession(), dbQuery); 1313 }; 1314 auto handler = PluginContextMenuActionHandler{getPluginData, 1315 getAllEntitiesInCurrentView, 1316 getEntityByQualifiedName, 1317 getTree, 1318 getDock, 1319 getEdgeByQualifiedName, 1320 loadEntityByQualifiedName, 1321 addEdgeByQualifiedName, 1322 removeEdgeByQualifiedName, 1323 hasEdgeByQualifiedName, 1324 runQueryOnDatabase}; 1325 1326 try { 1327 userAction(&handler); 1328 } catch (std::exception& e) { 1329 Q_EMIT errorMessage(QString::fromStdString(e.what())); 1330 } 1331 }); 1332 }; 1333 1334 auto& pm = d->pluginManager.value().get(); 1335 pm.callHooksContextMenu(getAllEntitiesInCurrentView, 1336 getEntityByQualifiedName, 1337 getEdgeByQualifiedName, 1338 registerContextMenu); 1339 } 1340 1341 if (d->showTransitive) { 1342 auto *action = menu.addAction(tr("Hide redundant edges")); 1343 connect(action, &QAction::triggered, this, [this] { 1344 toggleTransitiveRelationVisibility(false); 1345 }); 1346 } else { 1347 auto *action = menu.addAction(tr("Show redundant edges")); 1348 connect(action, &QAction::triggered, this, [this] { 1349 searchTransitiveRelations(); 1350 toggleTransitiveRelationVisibility(true); 1351 }); 1352 } 1353 { 1354 auto *action = menu.addAction(tr("Collapse Entities")); 1355 connect(action, &QAction::triggered, this, [this] { 1356 collapseSecondaryEntities(); 1357 }); 1358 } 1359 if (debugMenu) { 1360 QAction *action = debugMenu->addAction(tr("Show Edge Bounding Rects")); 1361 action->setCheckable(true); 1362 action->setChecked(LakosRelation::showBoundingRect()); 1363 connect(action, &QAction::triggered, this, &GraphicsScene::toggleEdgeBoundingRects); 1364 1365 action = debugMenu->addAction(tr("Show Edge Shapes")); 1366 action->setCheckable(true); 1367 action->setChecked(LakosRelation::showShape()); 1368 1369 connect(action, &QAction::triggered, this, &GraphicsScene::toggleEdgeShapes); 1370 1371 action = debugMenu->addAction(tr("Show Edge Textual Information")); 1372 action->setCheckable(true); 1373 action->setChecked(LakosRelation::showTextualInformation()); 1374 connect(action, &QAction::triggered, this, &GraphicsScene::toggleEdgeTextualInformation); 1375 1376 action = debugMenu->addAction(tr("Show Edge Intersection Paths")); 1377 action->setCheckable(true); 1378 action->setChecked(LakosRelation::showIntersectionPaths()); 1379 1380 connect(action, &QAction::triggered, this, &GraphicsScene::toggleEdgeIntersectionPaths); 1381 1382 action = debugMenu->addAction(tr("Show Edge original line")); 1383 action->setCheckable(true); 1384 action->setChecked(LakosRelation::showOriginalLine()); 1385 connect(action, &QAction::triggered, this, &GraphicsScene::toggleEdgeOriginalLine); 1386 } 1387 } 1388 1389 void GraphicsScene::searchTransitiveRelations() 1390 { 1391 if (d->transitiveReductionAlg->hasRun()) { 1392 toggleTransitiveRelationVisibility(d->showTransitive); 1393 return; 1394 } 1395 1396 for (auto *entity : d->verticesVec) { 1397 entity->resetRedundantRelations(); 1398 } 1399 1400 auto visibleEntities = std::vector<LakosEntity *>(); 1401 for (auto *e : d->verticesVec) { 1402 visibleEntities.push_back(e); 1403 } 1404 d->transitiveReductionAlg->setVertices(visibleEntities); 1405 d->transitiveReductionAlg->run(); 1406 transitiveRelationSearchFinished(); 1407 } 1408 1409 void GraphicsScene::updateBoundingRect() 1410 { 1411 setSceneRect(itemsBoundingRect().adjusted(-20, -20, 20, 20)); 1412 } 1413 1414 void GraphicsScene::transitiveRelationSearchFinished() 1415 { 1416 if (d->transitiveReductionAlg->hasError()) { 1417 Q_EMIT errorMessage(d->transitiveReductionAlg->errorMessage()); 1418 } 1419 1420 // mark all of the redundant edges 1421 for (const auto& [node, edgeVector] : d->transitiveReductionAlg->redundantEdgesByNode()) { 1422 for (const auto& collection : edgeVector) { 1423 node->setRelationRedundant(collection); 1424 } 1425 } 1426 1427 toggleTransitiveRelationVisibility(d->showTransitive); 1428 } 1429 1430 void GraphicsScene::toggleTransitiveRelationVisibility(bool show) 1431 { 1432 d->showTransitive = show; 1433 fixTransitiveEdgeVisibility(); 1434 } 1435 1436 void GraphicsScene::fixTransitiveEdgeVisibility() 1437 { 1438 for (LakosEntity *node : d->verticesVec) { 1439 node->showRedundantRelations(d->showTransitive); 1440 } 1441 1442 if (d->showTransitive) { 1443 for (LakosEntity *node : d->verticesVec) { 1444 node->recursiveEdgeRelayout(); 1445 } 1446 } 1447 } 1448 1449 void GraphicsScene::toggleEdgeBoundingRects() 1450 { 1451 LakosRelation::toggleBoundingRect(); 1452 updateEdgeDebugInfo(); 1453 } 1454 1455 void GraphicsScene::toggleEdgeShapes() 1456 { 1457 LakosRelation::toggleShape(); 1458 updateEdgeDebugInfo(); 1459 } 1460 1461 void GraphicsScene::toggleEdgeTextualInformation() 1462 { 1463 LakosRelation::toggleTextualInformation(); 1464 updateEdgeDebugInfo(); 1465 } 1466 1467 void GraphicsScene::toggleEdgeIntersectionPaths() 1468 { 1469 LakosRelation::toggleIntersectionPaths(); 1470 updateEdgeDebugInfo(); 1471 } 1472 1473 void GraphicsScene::toggleEdgeOriginalLine() 1474 { 1475 LakosRelation::toggleOriginalLine(); 1476 updateEdgeDebugInfo(); 1477 } 1478 1479 void GraphicsScene::updateEdgeDebugInfo() 1480 { 1481 for (auto *relation : d->relationVec) { 1482 relation->updateDebugInformation(); 1483 } 1484 } 1485 1486 LakosEntity *GraphicsScene::entityById(const std::string& uniqueId) const 1487 { 1488 try { 1489 return d->vertices.at(uniqueId); 1490 } catch (...) { 1491 return nullptr; 1492 } 1493 } 1494 1495 LakosEntity *GraphicsScene::entityByQualifiedName(const std::string& qualName) const 1496 { 1497 const bool showDebug = Preferences::enableDebugOutput(); 1498 1499 if (d->verticesVec.empty()) { 1500 if (showDebug) { 1501 qDebug() << "There are no entities on the vector"; 1502 } 1503 return nullptr; 1504 } 1505 1506 const auto findIt = std::find_if(std::cbegin(d->verticesVec), std::cend(d->verticesVec), [&qualName](auto *entity) { 1507 return entity->qualifiedName() == qualName; 1508 }); 1509 if (findIt == std::cend(d->verticesVec)) { 1510 if (showDebug) { 1511 qDebug() << "Could not find " << QString::fromStdString(qualName); 1512 qDebug() << "Available entities:"; 1513 for (auto *entity : d->verticesVec) { 1514 qDebug() << "> " << QString::fromStdString(entity->qualifiedName()); 1515 } 1516 } 1517 return nullptr; 1518 } 1519 1520 return *findIt; 1521 } 1522 1523 void GraphicsScene::loadEntityByQualifiedName(const QString& qualifiedName, const QPointF& pos) 1524 { 1525 const std::string qualName = qualifiedName.toStdString(); 1526 qDebug() << "Loading" << qualName; 1527 if (entityByQualifiedName(qualName)) { 1528 Q_EMIT errorMessage(tr("The element is already loaded")); 1529 return; 1530 } 1531 1532 auto *lakosianNode = d->nodeStorage.findByQualifiedName(qualName); 1533 if (!lakosianNode) { 1534 Q_EMIT errorMessage(tr("Element %1 not found").arg(qualifiedName)); 1535 return; 1536 } 1537 1538 assert(lakosianNode); 1539 1540 LakosEntity *lastAddedEntity = nullptr; 1541 size_t parentIdx = -1; 1542 const auto hierarchy = lakosianNode->parentHierarchy(); 1543 1544 // Traverse the hierarchy to find the bottom-most of the items already on the view. 1545 // If there are already elements of the hierarchy of the items dropped, those 1546 // elements should be used as the drop target. 1547 for (size_t i = 0; i < hierarchy.size(); i++) { 1548 lvtldr::LakosianNode *thisNode = hierarchy[i]; 1549 LakosEntity *entity = entityByQualifiedName(thisNode->qualifiedName()); 1550 if (entity == nullptr) { 1551 break; 1552 } 1553 lastAddedEntity = entity; 1554 parentIdx = i; 1555 } 1556 1557 // this will iterate first from the parents, then to the children. the last element is the one we 1558 // dragged to the view, but first, we need to create all of the parents on the view, with 1559 // the exception of the already created elements, calculated on the for above. 1560 // since parentIdx starts with -1, when there's no parents, we start the for below on zero 1561 // and everything should be good. 1562 std::vector<LakosEntity *> newEntities; 1563 std::random_device rd; 1564 std::mt19937 mt(rd()); 1565 std::uniform_real_distribution<double> dist(0.0, 100.0); 1566 QPointF scenePos = lastAddedEntity == nullptr ? pos : QPointF(dist(mt), dist(mt)); 1567 1568 for (size_t i = parentIdx + 1; i < hierarchy.size(); i++) { 1569 lvtldr::LakosianNode *node = hierarchy[i]; 1570 1571 // TODO: Remove the boolean traps. 1572 lvtshr::LoaderInfo info(false, lastAddedEntity != nullptr, false); 1573 info.setHasParent(lastAddedEntity != nullptr); 1574 1575 lvtshr::DiagramType nodeType = node->type(); 1576 auto methodPtr = [&]() -> decltype(&GraphicsScene::addUdtVertex) { 1577 switch (nodeType) { 1578 case lvtshr::DiagramType::ClassType: 1579 return &GraphicsScene::addUdtVertex; 1580 case lvtshr::DiagramType::ComponentType: 1581 return &GraphicsScene::addCompVertex; 1582 case lvtshr::DiagramType::PackageType: 1583 return &GraphicsScene::addPkgVertex; 1584 case lvtshr::DiagramType::RepositoryType: 1585 return &GraphicsScene::addRepositoryVertex; 1586 case lvtshr::DiagramType::FreeFunctionType: 1587 return &GraphicsScene::addUdtVertex; 1588 case lvtshr::DiagramType::NoneType: 1589 break; 1590 } 1591 return nullptr; 1592 }(); 1593 assert(methodPtr); 1594 1595 // the parameter after newPackageId has the name `selected`, but that actually serves to tell if this 1596 // entity will be a "graph" or a "leaf". 1597 LakosEntity *pkgEntity = (this->*methodPtr)(node, true, lastAddedEntity, info); 1598 1599 pkgEntity->enableLayoutUpdates(); 1600 pkgEntity->setPos(scenePos); 1601 pkgEntity->show(); 1602 // When an item has no parent, it needs to be added to the view manually. 1603 if (!lastAddedEntity) { 1604 addItem(pkgEntity); 1605 } else { 1606 if (!lastAddedEntity->isExpanded()) { 1607 lastAddedEntity->toggleExpansion(QtcUtil::CreateUndoAction::e_No); 1608 } else { 1609 // HACK: Toggle expansion twice so it shrinks / expands to recalculate the rectangle. 1610 // the recalculateRectangle() method exists but triggering it just after adding the child 1611 // is not correctly setting up the boundaries. 1612 lastAddedEntity->toggleExpansion(QtcUtil::CreateUndoAction::e_No); 1613 lastAddedEntity->toggleExpansion(QtcUtil::CreateUndoAction::e_No); 1614 } 1615 } 1616 1617 lastAddedEntity = pkgEntity; 1618 newEntities.push_back(pkgEntity); 1619 1620 // Position the item inside of the previous element with a 1621 // bit of randomness so they don't stack vertically 1622 scenePos = QPointF(dist(mt), dist(mt)); 1623 } 1624 1625 // Add the edges between the entities that are currently loaded. 1626 std::unordered_map<LakosianNode *, LakosEntity *> entity_to_node; 1627 for (auto const& node : d->verticesVec) { 1628 entity_to_node[node->internalNode()] = node; 1629 } 1630 auto sceneContainsNode = [&entity_to_node](LakosianNode *node) { 1631 return entity_to_node.count(node) > 0; 1632 }; 1633 for (auto *newEntity : newEntities) { 1634 auto *newEntityNode = newEntity->internalNode(); 1635 for (const auto& edge : newEntityNode->providers()) { 1636 if (sceneContainsNode(edge.other())) { 1637 addEdgeBetween(newEntity, entity_to_node[edge.other()], edge.type()); 1638 } 1639 } 1640 for (const auto& edge : newEntityNode->clients()) { 1641 if (sceneContainsNode(edge.other())) { 1642 addEdgeBetween(entity_to_node[edge.other()], newEntity, edge.type()); 1643 } 1644 } 1645 } 1646 1647 for (auto *newEntity : newEntities) { 1648 newEntity->recursiveEdgeRelayout(); 1649 } 1650 1651 Q_EMIT graphLoadFinished(); 1652 } 1653 1654 void GraphicsScene::addEdgeBetween(LakosEntity *fromEntity, LakosEntity *toEntity, lvtshr::LakosRelationType type) 1655 { 1656 LakosRelation *relation = nullptr; 1657 switch (type) { 1658 // Package groups, packages and components 1659 case lvtshr::PackageDependency: 1660 relation = addPackageDependencyRelation(fromEntity, toEntity); 1661 break; 1662 // Logical entities 1663 case lvtshr::IsA: 1664 relation = addIsARelation(fromEntity, toEntity); 1665 break; 1666 case lvtshr::UsesInNameOnly: 1667 assert(false && "Not implemented"); 1668 break; 1669 case lvtshr::UsesInTheImplementation: 1670 relation = addUsesInTheImplementationRelation(fromEntity, toEntity); 1671 break; 1672 case lvtshr::UsesInTheInterface: 1673 relation = addUsesInTheInterfaceRelation(fromEntity, toEntity); 1674 break; 1675 1676 case lvtshr::None: 1677 assert(false && "Unexpected unknown relation type"); 1678 break; 1679 } 1680 1681 if (relation && !relation->parentItem()) { 1682 addItem(relation); 1683 } 1684 } 1685 1686 lvtprj::ProjectFile const& GraphicsScene::projectFile() const 1687 { 1688 return d->projectFile; 1689 } 1690 1691 QJsonObject GraphicsScene::toJson() const 1692 { 1693 // filter all toplevel items: 1694 QJsonArray array; 1695 1696 for (LakosEntity *e : d->verticesVec) { 1697 if (!e->parentItem()) { 1698 array.append(e->toJson()); 1699 } 1700 } 1701 return { 1702 {"elements", array}, 1703 {"transitive_visibility", d->showTransitive}, 1704 }; 1705 } 1706 1707 void recursiveJsonToLakosEntity(GraphicsScene *scene, const QJsonValue& entity) 1708 { 1709 const QJsonObject obj = entity.toObject(); 1710 const QString qualName = obj["qualifiedName"].toString(); 1711 const QJsonObject posObj = obj["pos"].toObject(); 1712 const QPointF pos(posObj["x"].toDouble(), posObj["y"].toDouble()); 1713 1714 scene->loadEntityByQualifiedName(qualName, pos); 1715 1716 // TODO: return the entity directly by the above method. 1717 LakosEntity *thisObj = scene->entityByQualifiedName(qualName.toStdString()); 1718 if (!thisObj) { 1719 return; 1720 } 1721 thisObj->fromJson(obj); 1722 thisObj->enableLayoutUpdates(); 1723 1724 const auto children = obj["children"].toArray(); 1725 for (const auto& child : children) { 1726 recursiveJsonToLakosEntity(scene, child); 1727 } 1728 1729 thisObj->setPos(pos); 1730 } 1731 1732 void GraphicsScene::fromJson(const QJsonObject& doc) 1733 { 1734 Q_EMIT graphLoadStarted(); 1735 clearGraph(); 1736 const auto elements = doc["elements"].toArray(); 1737 1738 for (const auto& element : elements) { 1739 recursiveJsonToLakosEntity(this, element); 1740 } 1741 1742 // Invalidate transitive reduction caches 1743 d->transitiveReductionAlg->reset(); 1744 searchTransitiveRelations(); 1745 1746 // Calculate Default Visibility of edges 1747 for (auto entity : d->verticesVec) { 1748 if (!entity->parentItem()) { 1749 entity->calculateEdgeVisibility(); 1750 } 1751 } 1752 1753 const auto show_transitive = doc["transitive_visibility"].toBool(); 1754 toggleTransitiveRelationVisibility(show_transitive); 1755 Q_EMIT graphLoadFinished(); 1756 } 1757 1758 void GraphicsScene::setPluginManager(Codethink::lvtplg::PluginManager& pm) 1759 { 1760 d->pluginManager = pm; 1761 for (auto *e : allEntities()) { 1762 e->setPluginManager(pm); 1763 } 1764 } 1765 1766 void GraphicsScene::removeEdge(LakosEntity& fromEntity, LakosEntity& toEntity) 1767 { 1768 auto edgeCollection = fromEntity.getRelationshipWith(&toEntity); 1769 if (!edgeCollection || edgeCollection->relations().empty()) { 1770 return; 1771 } 1772 1773 // explicit copy, so we don't mess with internal iterators. 1774 auto allCollections = fromEntity.edgesCollection(); 1775 1776 std::vector<LakosRelation *> toDelete; 1777 for (const auto& collection : allCollections) { 1778 if (collection->to() == &toEntity) { 1779 for (auto *relation : collection->relations()) { 1780 fromEntity.removeEdge(relation); 1781 toEntity.removeEdge(relation); 1782 toDelete.push_back(relation); 1783 } 1784 } 1785 } 1786 1787 for (LakosRelation *rel : toDelete) { 1788 rel->setParent(nullptr); 1789 removeItem(rel); 1790 d->relationVec.erase(std::remove(std::begin(d->relationVec), std::end(d->relationVec), rel), 1791 std::end(d->relationVec)); 1792 1793 rel->deleteLater(); 1794 } 1795 1796 // Invalidate transitive reduction caches 1797 d->transitiveReductionAlg->reset(); 1798 1799 fromEntity.getTopLevelParent()->calculateEdgeVisibility(); 1800 fromEntity.recursiveEdgeRelayout(); 1801 } 1802 1803 } // end namespace Codethink::lvtqtc