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