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

0001 // ct_lvtqtc_lakosentity.cpp                                        -*-C++-*-
0002 
0003 /*
0004 // Copyright 2023 Codethink Ltd <codethink@codethink.co.uk>
0005 // SPDX-License-Identifier: Apache-2.0
0006 //
0007 // Licensed under the Apache License, Version 2.0 (the "License");
0008 // you may not use this file except in compliance with the License.
0009 // You may obtain a copy of the License at
0010 //
0011 //     http://www.apache.org/licenses/LICENSE-2.0
0012 //
0013 // Unless required by applicable law or agreed to in writing, software
0014 // distributed under the License is distributed on an "AS IS" BASIS,
0015 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0016 // See the License for the specific language governing permissions and
0017 // limitations under the License.
0018 */
0019 
0020 #include <ct_lvtqtc_lakosentity.h>
0021 
0022 #include <ct_lvtqtc_edgecollection.h>
0023 #include <ct_lvtqtc_ellipsistextitem.h>
0024 #include <ct_lvtqtc_graphicsscene.h>
0025 #include <ct_lvtqtc_lakosentitypluginutils.h>
0026 #include <ct_lvtqtc_lakosrelation.h>
0027 
0028 #include <ct_lvtqtc_alg_level_layout.h>
0029 #include <ct_lvtqtc_undo_cover.h>
0030 #include <ct_lvtqtc_undo_expand.h>
0031 #include <ct_lvtqtc_undo_move.h>
0032 #include <ct_lvtqtc_undo_notes.h>
0033 
0034 #include <ct_lvtldr_lakosiannode.h>
0035 
0036 #include <mrichtextedit.h>
0037 
0038 #include <QAction>
0039 #include <QApplication>
0040 #include <QBoxLayout>
0041 #include <QBrush>
0042 #include <QClipboard>
0043 #include <QColor>
0044 #include <QCursor>
0045 #include <QDebug>
0046 #include <QDialog>
0047 #include <QDialogButtonBox>
0048 #include <QDir>
0049 #include <QDirIterator>
0050 #include <QDockWidget>
0051 #include <QFont>
0052 #include <QGraphicsPixmapItem>
0053 #include <QGraphicsScene>
0054 #include <QGraphicsSceneContextMenuEvent>
0055 #include <QInputDialog>
0056 #include <QJsonArray>
0057 #include <QMenu>
0058 #include <QPen>
0059 #include <QPointF>
0060 #include <QPropertyAnimation>
0061 #include <QTextBrowser>
0062 #include <QVector2D>
0063 
0064 #include <cmath>
0065 #include <fstream>
0066 #include <optional>
0067 #include <preferences.h>
0068 
0069 // too many false positives for loops over QList<T>: qAsConst<QList<T>> has
0070 // been explicitly deleted clazy:excludeall=range-loop,range-loop-detach
0071 
0072 namespace {
0073 static QPointF s_lastClick; // NOLINT
0074 // used to track the last click on the screen.
0075 
0076 constexpr int TITLE_LAYER = 1;
0077 constexpr int COVER_LAYER = 100;
0078 constexpr int ONE_CHILD_BORDER = 20;
0079 constexpr int DEFAULT_BORDER = 10;
0080 constexpr int SHRINK_BORDER = 20;
0081 
0082 } // namespace
0083 
0084 namespace Codethink::lvtqtc {
0085 
0086 struct LakosEntity::Private {
0087     std::string name;
0088     // The name of the source file or C++ class
0089 
0090     std::string qualifiedName;
0091     // The fully qualified name of the source file or C++ class
0092 
0093     std::string toolTip;
0094     // the tooltip that appears when hovered.
0095     // if tooltip is empty, then we use the QualifiedName.
0096 
0097     std::string id;
0098     // the unique id of this node
0099 
0100     std::string colorId;
0101     // The string used to generate a color for this entity. This should be
0102     // set by subclasses in their constructor. The actual color is set when
0103     // setColorManagement() is called
0104 
0105     std::vector<std::shared_ptr<EdgeCollection>> edges;
0106     // the collections of edges that have this node as source
0107 
0108     std::vector<std::shared_ptr<EdgeCollection>> targetEdges;
0109     // the collection of edges that have this node as target
0110 
0111     std::vector<std::shared_ptr<EdgeCollection>> redundantRelations;
0112     // Edges found to be rendundant by transitive reduction
0113 
0114     std::vector<QGraphicsItem *> ignoredItems;
0115     // childrens that the layout mechanis ignores.
0116     // TODO: Perhaps we could eliminate this, since we also store
0117     // the list of LakosEntity* that are the children.
0118 
0119     long long shortId = 0;
0120     // Non-unique. Used for UniqueId.
0121 
0122     QList<LakosEntity *> lakosChildren;
0123     // All the elements that belongs to this LakosEntity
0124 
0125     QVector2D movementDelta;
0126     // When in drag movement, this is the relative amount of
0127     // movement we did at each mouseMove event.
0128     // TODO: pack this together with isMoving?
0129 
0130     QColor color = QColor(204, 229, 255);
0131     // The background color of the entity
0132 
0133     QColor selectedNodeColor = QColor(204, 229, 255);
0134     // The background color of the entity when it's selected
0135 
0136     Qt::BrushStyle fillPattern = Qt::BrushStyle::SolidPattern;
0137     // The brush pattern for this node.
0138 
0139     lvtshr::LoaderInfo loaderInfo;
0140     // the instance of the class that holds the information on how
0141     // this entity should be loaded.
0142 
0143     EllipsisTextItem *text = nullptr;
0144     // Pointer owned by Qt (it should be a child object of this Container)
0145 
0146     GraphicsRectItem *cover = nullptr;
0147     // Cover item for this item, hiding content.
0148 
0149     QGraphicsPixmapItem *notesIcon = nullptr;
0150     // notes, with a hover activation.
0151 
0152     QGraphicsSimpleTextItem *levelNr = nullptr;
0153     // The level number on this entity.
0154 
0155     bool forceHideLevelNr = false;
0156 
0157     QGraphicsEllipseItem *centerDbg = nullptr;
0158     QGraphicsRectItem *boundingRectDbg = nullptr;
0159 
0160     QGraphicsSimpleTextItem *notAllChildrenLoadedInfo = nullptr;
0161     // show an icon on the top right when this element misses child
0162     // elements (if we loaded the item partially).
0163     // TODO: 453
0164 
0165     QPropertyAnimation *expandAnimation = nullptr;
0166     // handles animated expanding / shrink.
0167 
0168     bool isExpanded = true;
0169     // Is this item exanded or collapsed?
0170 
0171     bool isOpaque = false;
0172     // We have an item covering all our internal items.
0173 
0174     bool initialConstruction = true;
0175     // Are we just constructing this item or it's already constructed?
0176 
0177     bool layoutUpdatesEnabled = false;
0178     // is this item layout automatically being triggered?
0179 
0180     bool showRedundantEdges = false;
0181     // Are we showing redundant edges on this entity?
0182 
0183     bool highlighted = false;
0184     // is this highlighted ?
0185 
0186     bool isMainNode = false;
0187     // is this the main node on the visualization?
0188 
0189     bool showBackground = true;
0190     // are we showing the bg?
0191 
0192     lvtldr::LakosianNode *node = nullptr;
0193     // the in-memory node that represents this visual node
0194 
0195     QFont font = QFont();
0196     // the current font being used by this node
0197 
0198     bool enableGradientOnMainNode = true;
0199 
0200     int presentationFlags = PresentationFlags::NoFlag;
0201 
0202     std::optional<std::reference_wrapper<Codethink::lvtplg::PluginManager>> pluginManager = std::nullopt;
0203 };
0204 
0205 LakosEntity::LakosEntity(const std::string& uniqueId, lvtldr::LakosianNode *node, lvtshr::LoaderInfo loaderInfo):
0206     d(std::make_unique<LakosEntity::Private>())
0207 {
0208     d->node = node;
0209     d->name = node->name();
0210     d->id = uniqueId;
0211     d->qualifiedName = node->qualifiedName();
0212     d->shortId = node->id();
0213     d->loaderInfo = loaderInfo;
0214 
0215     setAcceptHoverEvents(true);
0216     setBrush(QBrush(Qt::white));
0217     setFlag(ItemIsSelectable);
0218 
0219     QPen currentPen = pen();
0220     currentPen.setCosmetic(true);
0221     currentPen.setWidthF(0.5);
0222     setPen(currentPen);
0223 
0224     // Default to right-angle corners
0225     setRoundRadius(0);
0226 
0227     d->notesIcon = new QGraphicsPixmapItem(this);
0228     setNotes(node->notes());
0229 
0230     d->text = new EllipsisTextItem(QString::fromStdString(node->name()), this);
0231     d->text->setZValue(TITLE_LAYER);
0232 
0233     d->expandAnimation = new QPropertyAnimation(this, "rect");
0234     d->expandAnimation->setDuration(250);
0235     d->expandAnimation->setEasingCurve(QEasingCurve::InOutQuad);
0236 
0237     ignoreItemOnLayout(d->text);
0238     ignoreItemOnLayout(d->notesIcon);
0239 
0240     d->cover = new GraphicsRectItem(this);
0241     d->cover->setBrush(QBrush(QColor(255, 255, 255, 195)));
0242     d->cover->setPen(QPen(Qt::NoPen));
0243     d->cover->setZValue(COVER_LAYER);
0244     d->cover->setVisible(false);
0245     d->cover->setRoundRadius(roundRadius());
0246     ignoreItemOnLayout(d->cover);
0247 
0248     d->levelNr = new QGraphicsSimpleTextItem(this);
0249     ignoreItemOnLayout(d->levelNr);
0250 
0251     connect(this, &GraphicsRectItem::rectangleChanged, this, [this] {
0252         if (d->isOpaque) {
0253             hideContent(ToggleContentBehavior::Single, QtcUtil::CreateUndoAction::e_No);
0254         }
0255 
0256         updateBackground();
0257         layoutAllRelations();
0258     });
0259 
0260     connect(this, &LakosEntity::moving, this, [this] {
0261         recursiveEdgeRelayout();
0262     });
0263 
0264     d->isExpanded = true;
0265     toggleExpansion(QtcUtil::CreateUndoAction::e_No);
0266 
0267     QObject::connect(d->node, &lvtldr::LakosianNode::onChildCountChanged, this, [this](size_t) {
0268         updateChildrenLoadedInfo();
0269         layoutIgnoredItems();
0270     });
0271 
0272     QObject::connect(d->node, &lvtldr::LakosianNode::onNotesChanged, this, [this](const std::string& notes) {
0273         setNotes(notes);
0274     });
0275 
0276     updateChildrenLoadedInfo();
0277     layoutIgnoredItems();
0278 
0279     d->color = Preferences::entityBackgroundColor();
0280     d->enableGradientOnMainNode = Preferences::enableGradientOnMainNode();
0281     d->selectedNodeColor = Preferences::selectedEntityBackgroundColor();
0282     connect(Preferences::self(), &Preferences::lakosEntityNamePosChanged, this, [this] {
0283         layoutIgnoredItems();
0284     });
0285     connect(Preferences::self(), &Preferences::entityBackgroundColorChanged, this, [this] {
0286         d->color = Preferences::entityBackgroundColor();
0287         updateBackground();
0288     });
0289     connect(Preferences::self(), &Preferences::enableGradientOnMainNodeChanged, this, [this] {
0290         d->enableGradientOnMainNode = Preferences::enableGradientOnMainNode();
0291         updateBackground();
0292     });
0293     connect(Preferences::self(), &Preferences::selectedEntityBackgroundColorChanged, this, [this] {
0294         d->selectedNodeColor = Preferences::selectedEntityBackgroundColor();
0295         updateBackground();
0296     });
0297 
0298     connect(Preferences::self(), &Preferences::showLevelNumbersChanged, this, [this] {
0299         d->levelNr->setVisible(Preferences::showLevelNumbers() && !d->forceHideLevelNr);
0300     });
0301 
0302     // static, we don't need to open the file more than once.
0303     QFontMetrics fm(qApp->font());
0304     static auto pixmap = QPixmap::fromImage(QImage(":/icons/notes").scaled(fm.height(), fm.height()));
0305 
0306     d->notesIcon->setPixmap(pixmap);
0307     d->levelNr->setVisible(Preferences::showLevelNumbers() && !d->forceHideLevelNr);
0308 }
0309 
0310 LakosEntity::~LakosEntity()
0311 {
0312     setParentItem(nullptr);
0313 }
0314 
0315 void LakosEntity::setNotes(const std::string& notes)
0316 {
0317     if (notes.empty()) {
0318         d->notesIcon->hide();
0319     } else {
0320         d->notesIcon->show();
0321     }
0322     recalculateRectangle();
0323 }
0324 
0325 void LakosEntity::setFont(const QFont& f)
0326 {
0327     d->text->setFont(f);
0328     d->levelNr->setFont(f);
0329 
0330     const bool updatesEnabled = d->layoutUpdatesEnabled;
0331 
0332     d->layoutUpdatesEnabled = true;
0333     recalculateRectangle();
0334     update();
0335 
0336     d->layoutUpdatesEnabled = updatesEnabled;
0337 }
0338 
0339 lvtldr::LakosianNode *LakosEntity::internalNode() const
0340 {
0341     return d->node;
0342 }
0343 
0344 void LakosEntity::toggleExpansion(QtcUtil::CreateUndoAction createUndoAction,
0345                                   std::optional<QPointF> moveToPosition,
0346                                   RelayoutBehavior behavior)
0347 {
0348     bool shouldExpand = !isExpanded();
0349     if (lakosEntities().empty()) {
0350         shouldExpand = false;
0351     }
0352 
0353     if (shouldExpand) {
0354         expand(createUndoAction, moveToPosition, behavior);
0355     } else {
0356         shrink(createUndoAction, moveToPosition, behavior);
0357     }
0358 
0359     Q_SIGNAL void formFactorChanged();
0360 }
0361 
0362 void LakosEntity::layoutIgnoredItems()
0363 {
0364     const auto titleCorner = Preferences::lakosEntityNamePos();
0365     const auto textRect = d->text->boundingRect();
0366 
0367     const auto x = [&]() -> qreal {
0368         if (d->isExpanded && !d->lakosChildren.empty()) {
0369             if (titleCorner == Qt::TopLeftCorner || titleCorner == Qt::BottomLeftCorner) {
0370                 return boundingRect().left();
0371             }
0372             return boundingRect().right() - textRect.width();
0373         }
0374         return boundingRect().left() + boundingRect().width() / 2 - textRect.width() / 2;
0375     }();
0376 
0377     const auto y = [&]() -> qreal {
0378         if (d->isExpanded && !d->lakosChildren.empty()) {
0379             if (titleCorner == Qt::TopLeftCorner || titleCorner == Qt::TopRightCorner) {
0380                 return boundingRect().top() - textRect.height();
0381             }
0382             return boundingRect().bottom();
0383         }
0384         return boundingRect().center().y() - d->text->boundingRect().height() / 2;
0385     }();
0386 
0387     if (d->isOpaque) {
0388         d->cover->setVisible(true);
0389     }
0390 
0391     d->notesIcon->setPos(boundingRect().left(), boundingRect().top());
0392     d->text->setPos(x, y);
0393     d->text->setVisible(true);
0394 
0395     const auto topRight = boundingRect().topRight();
0396     d->levelNr->setPos(topRight.x() - d->levelNr->boundingRect().width() - 10, topRight.y());
0397 
0398     if (d->notAllChildrenLoadedInfo) {
0399         constexpr auto x_spacing = 10;
0400         constexpr auto y_spacing = 5;
0401         const auto bottomRight = boundingRect().bottomRight();
0402         const auto children_info_x = bottomRight.x() - d->notAllChildrenLoadedInfo->boundingRect().width() - x_spacing;
0403         const auto children_info_y = bottomRight.y() - d->notAllChildrenLoadedInfo->boundingRect().height() - y_spacing;
0404         d->notAllChildrenLoadedInfo->setPos(QPointF(children_info_x, children_info_y));
0405         d->notAllChildrenLoadedInfo->setVisible(true);
0406     }
0407 }
0408 
0409 void LakosEntity::expand(QtcUtil::CreateUndoAction createUndoAction,
0410                          std::optional<QPointF> moveToPosition,
0411                          RelayoutBehavior behavior)
0412 {
0413     if (createUndoAction == QtcUtil::CreateUndoAction::e_Yes) {
0414         Q_EMIT undoCommandCreated(
0415             new UndoExpand(qobject_cast<GraphicsScene *>(scene()), d->id, moveToPosition, behavior));
0416         return;
0417     }
0418 
0419     // should be set even if layout updates are disabled
0420     // because when the layout update is re-enabled, it will
0421     // run the layout logic and needs to know the form factor.
0422 
0423     d->isExpanded = true;
0424     if (!layoutUpdatesEnabled()) {
0425         return;
0426     }
0427     if (lakosEntities().empty()) {
0428         return;
0429     }
0430 
0431     setFlag(QGraphicsItem::ItemIgnoresTransformations, false);
0432 
0433     auto font = d->text->font();
0434     font.setBold(false);
0435     d->text->setFont(font);
0436 
0437     const int children = d->lakosChildren.size();
0438     const int border = children == 1 ? ONE_CHILD_BORDER : DEFAULT_BORDER;
0439     const QRectF currentRect = rect();
0440     if (moveToPosition) {
0441         setPos(*moveToPosition);
0442     }
0443     const QRectF nextRect = childrenBoundingRect().adjusted(-border, -border, border, border);
0444 
0445     if (d->initialConstruction) {
0446         setRectangle(nextRect);
0447         d->initialConstruction = false;
0448         recalculateRectangle();
0449         Q_EMIT formFactorChanged();
0450         return;
0451     }
0452 
0453     d->expandAnimation->setStartValue(currentRect);
0454     d->expandAnimation->setEndValue(nextRect);
0455 
0456     disconnect(d->expandAnimation, nullptr, this, nullptr);
0457 
0458     connect(d->expandAnimation, &QPropertyAnimation::finished, this, [this, behavior] {
0459         const QList<QGraphicsItem *> childList = childItems();
0460         for (auto *child : childList) {
0461             if (child != d->cover && child != d->notesIcon) {
0462                 child->setVisible(true);
0463             }
0464         }
0465 
0466         calculateEdgeVisibility();
0467 
0468         for (auto *child : lakosEntities()) {
0469             layoutEdges(child);
0470         }
0471 
0472         layoutEdges(this);
0473         if (d->isOpaque) {
0474             hideContent(ToggleContentBehavior::Single, QtcUtil::CreateUndoAction::e_No);
0475         }
0476 
0477         recalculateRectangle();
0478         Q_EMIT formFactorChanged();
0479 
0480         if (behavior == RelayoutBehavior::e_RequestRelayout) {
0481             Q_EMIT requestGraphRelayout();
0482         }
0483     });
0484 
0485     d->expandAnimation->start();
0486 }
0487 
0488 void LakosEntity::shrink(QtcUtil::CreateUndoAction createUndoAction,
0489                          std::optional<QPointF> moveToPosition,
0490                          RelayoutBehavior behavior)
0491 {
0492     if (createUndoAction == QtcUtil::CreateUndoAction::e_Yes) {
0493         Q_EMIT undoCommandCreated(
0494             new UndoExpand(qobject_cast<GraphicsScene *>(scene()), d->id, moveToPosition, behavior));
0495         return;
0496     }
0497 
0498     // should be set even if layout updates are disabled
0499     // because when the layout update is re-enabled, it will
0500     // run the layout logic and needs to know the form factor.
0501     d->isExpanded = false;
0502     if (!layoutUpdatesEnabled()) {
0503         qDebug() << "Layout updates disabled!";
0504         return;
0505     }
0506 
0507     auto font = d->text->font();
0508     font.setBold(true);
0509     d->text->setFont(font);
0510 
0511     const QRectF currentRect = rect();
0512     if (moveToPosition) {
0513         setPos(*moveToPosition);
0514     }
0515     const QRectF nextRect =
0516         d->text->boundingRect().adjusted(-SHRINK_BORDER, -SHRINK_BORDER, SHRINK_BORDER, SHRINK_BORDER);
0517 
0518     for (LakosEntity *child : lakosEntities()) {
0519         child->setVisible(false);
0520         child->setRelationshipVisibility(QtcUtil::VisibilityMode::e_Hidden);
0521     }
0522 
0523     setRelationshipVisibility(QtcUtil::VisibilityMode::e_Hidden);
0524 
0525     if (d->initialConstruction) {
0526         // skip animation, going straight to how things should be
0527         setRectangle(nextRect);
0528         d->initialConstruction = false;
0529         layoutIgnoredItems();
0530         Q_EMIT formFactorChanged();
0531         setRelationshipVisibility(QtcUtil::VisibilityMode::e_Visible);
0532         return;
0533     }
0534 
0535     // This looks weird, as I'm setting the rect multiple times, but it's not:
0536     // I need to shrink the size so that the edges can point to the right place
0537     // before the animation starts (if not, the edges would point outside of the
0538     // package during the collapse animation), then I need to set it back to the
0539     // previous size, so I can animate.
0540     setRectangle(currentRect);
0541     if (currentRect == nextRect) {
0542         return;
0543     }
0544 
0545     if (currentRect.width() == nextRect.width() || currentRect.height() == nextRect.height()) {
0546         return;
0547     }
0548 
0549     d->expandAnimation->setStartValue(currentRect);
0550     d->expandAnimation->setEndValue(nextRect);
0551 
0552     disconnect(d->expandAnimation, nullptr, this, nullptr);
0553 
0554     connect(d->expandAnimation, &QPropertyAnimation::finished, this, [this, behavior] {
0555         d->cover->setVisible(false);
0556         layoutIgnoredItems();
0557         calculateEdgeVisibility();
0558         layoutEdges(this);
0559         setRelationshipVisibility(QtcUtil::VisibilityMode::e_Visible);
0560         Q_EMIT formFactorChanged();
0561         if (behavior == RelayoutBehavior::e_RequestRelayout) {
0562             Q_EMIT requestGraphRelayout();
0563         }
0564     });
0565 
0566     d->expandAnimation->start();
0567 }
0568 
0569 void LakosEntity::setRelationshipVisibility(QtcUtil::VisibilityMode mode)
0570 {
0571     for (const auto& vec : {d->edges, d->targetEdges}) {
0572         for (const auto& collection : vec) {
0573             if (mode == QtcUtil::VisibilityMode::e_Visible && collection->isRedundant() && !d->showRedundantEdges) {
0574                 continue;
0575             }
0576 
0577             for (auto *relationship : collection->relations()) {
0578                 relationship->setVisible(mode == QtcUtil::VisibilityMode::e_Visible);
0579             }
0580         }
0581     }
0582 }
0583 
0584 void LakosEntity::layoutEdges(LakosEntity *child)
0585 {
0586     if (!child) {
0587         return;
0588     }
0589     // top level call to layout edges for the container
0590     const EdgeCollection::PointFrom pointFrom =
0591         d->isExpanded ? EdgeCollection::PointFrom::SOURCE : EdgeCollection::PointFrom::PARENT;
0592     const EdgeCollection::PointTo pointTo =
0593         d->isExpanded ? EdgeCollection::PointTo::TARGET : EdgeCollection::PointTo::PARENT;
0594 
0595     layoutEdges(child, pointFrom, pointTo);
0596 }
0597 
0598 void LakosEntity::layoutEdges(LakosEntity *child,
0599                               const EdgeCollection::PointFrom pointFrom,
0600                               const EdgeCollection::PointTo pointTo)
0601 {
0602     assert(child);
0603 
0604     // recursive call to layout edges in the child and then in grandchildren etc
0605     for (const auto& edgeCollection : child->edgesCollection()) {
0606         edgeCollection->setPointFrom(pointFrom);
0607     }
0608     for (const auto& edgeCollection : child->targetCollection()) {
0609         edgeCollection->setPointTo(pointTo);
0610     }
0611 
0612     for (LakosEntity *grandchild : child->lakosEntities()) {
0613         layoutEdges(grandchild, pointFrom, pointTo);
0614     }
0615 }
0616 
0617 LakosEntity *LakosEntity::getTopLevelParent()
0618 {
0619     LakosEntity *oldParent = nullptr;
0620     LakosEntity *parent = this;
0621     while (parent) {
0622         oldParent = parent;
0623         parent = qgraphicsitem_cast<LakosEntity *>(parent->parentItem());
0624     }
0625     return oldParent;
0626 }
0627 
0628 void LakosEntity::setHighlighted(bool highlighted)
0629 {
0630     if (d->highlighted == highlighted) {
0631         return;
0632     }
0633 
0634     d->highlighted = highlighted;
0635 
0636     if (layoutUpdatesEnabled()) {
0637         update();
0638     }
0639 }
0640 
0641 void LakosEntity::setName(const std::string& name)
0642 {
0643     d->name = name;
0644     setText(name);
0645 
0646     if (layoutUpdatesEnabled()) {
0647         update();
0648     }
0649 }
0650 
0651 void LakosEntity::setPresentationFlags(PresentationFlags flags, bool value)
0652 {
0653     if (value) {
0654         d->presentationFlags |= flags;
0655     } else {
0656         d->presentationFlags &= ~flags;
0657     }
0658 
0659     if (d->presentationFlags & PresentationFlags::Highlighted) {
0660         setZValue(1000);
0661     } else {
0662         setZValue(1);
0663     }
0664     update();
0665 }
0666 
0667 void LakosEntity::setQualifiedName(const std::string& qname)
0668 {
0669     d->qualifiedName = qname;
0670 }
0671 
0672 void LakosEntity::setText(const std::string& text)
0673 {
0674     d->text->setText(QString::fromStdString(text));
0675     auto tmp = d->layoutUpdatesEnabled;
0676     d->layoutUpdatesEnabled = true;
0677     this->recalculateRectangle();
0678     d->layoutUpdatesEnabled = tmp;
0679 }
0680 
0681 void LakosEntity::setTooltipString(const std::string& tt)
0682 {
0683     d->toolTip = tt;
0684     setToolTip(QString::fromStdString(d->toolTip));
0685 }
0686 
0687 QList<QAction *> LakosEntity::actionsForMenu(QPointF scenePosition)
0688 {
0689     if (parentItem()) {
0690         scenePosition = parentItem()->mapFromScene(scenePosition);
0691     }
0692 
0693     QList<QAction *> retValues;
0694 
0695     if (instanceType() != lvtshr::DiagramType::ClassType) {
0696         auto *selectAction = new QAction(isSelected() ? tr("Deselect") : tr("Select"));
0697         connect(selectAction, &QAction::triggered, this, &LakosEntity::toggleSelection);
0698         retValues.append(selectAction);
0699     }
0700 
0701     if (instanceType() != lvtshr::DiagramType::RepositoryType) {
0702         if (d->loaderInfo.isValid()) {
0703             auto *loadClientsAction = new QAction();
0704             loadClientsAction->setText(tr("Load all clients"));
0705             connect(loadClientsAction, &QAction::triggered, this, [this] {
0706                 Q_EMIT loadClients(/*onlyLocal=*/false);
0707             });
0708 
0709             auto *loadLocalClientsAction = new QAction();
0710             loadLocalClientsAction->setText(tr("Load local clients"));
0711             connect(loadLocalClientsAction, &QAction::triggered, this, [this] {
0712                 Q_EMIT loadClients(/*onlyLocal=*/true);
0713             });
0714 
0715             auto *loadProvidersAction = new QAction();
0716             loadProvidersAction->setText(tr("Load all providers"));
0717             connect(loadProvidersAction, &QAction::triggered, this, [this] {
0718                 Q_EMIT loadProviders(/*onlyLocal=*/false);
0719             });
0720 
0721             auto *loadLocalProvidersAction = new QAction();
0722             loadLocalProvidersAction->setText(tr("Load local providers"));
0723             connect(loadLocalProvidersAction, &QAction::triggered, this, [this] {
0724                 Q_EMIT loadProviders(/*onlyLocal=*/true);
0725             });
0726 
0727             retValues.append(loadProvidersAction);
0728             retValues.append(loadLocalProvidersAction);
0729             retValues.append(loadClientsAction);
0730             retValues.append(loadLocalClientsAction);
0731         }
0732     }
0733 
0734     if (instanceType() != lvtshr::DiagramType::RepositoryType) {
0735         auto *unloadAction = new QAction();
0736         unloadAction->setText(tr("Unload %1").arg(QString::fromStdString(name())));
0737         connect(unloadAction, &QAction::triggered, this, &LakosEntity::unloadThis);
0738         retValues.append(unloadAction);
0739     }
0740 
0741     auto *thisScene = qobject_cast<GraphicsScene *>(scene());
0742     if (thisScene) {
0743         auto selectedEntities = thisScene->selectedEntities();
0744         if (!selectedEntities.empty()) {
0745             auto *unloadAllAction = new QAction();
0746             unloadAllAction->setText(tr("Unload all selected entities"));
0747             for (auto& e : selectedEntities) {
0748                 connect(unloadAllAction, &QAction::triggered, e, &LakosEntity::unloadThis);
0749             }
0750             retValues.append(unloadAllAction);
0751 
0752             auto *loadAllInNewTabAction = new QAction();
0753             loadAllInNewTabAction->setText(tr("Load selected entities in new tab"));
0754             connect(loadAllInNewTabAction, &QAction::triggered, this, [this, selectedEntities] {
0755                 QSet<QString> qualifiedNames;
0756                 for (const auto& entity : selectedEntities) {
0757                     qualifiedNames.insert(QString::fromStdString(entity->qualifiedName()));
0758                 }
0759                 Q_EMIT requestNewTab(qualifiedNames);
0760             });
0761             retValues.append(loadAllInNewTabAction);
0762         }
0763     }
0764 
0765     if (loaderInfo().isValid()) {
0766         if (d->notAllChildrenLoadedInfo) {
0767             auto *childAction = new QAction();
0768 
0769             const auto type = internalNode()->type();
0770             const auto text = [type]() -> QString {
0771                 switch (type) {
0772                 case lvtshr::DiagramType::RepositoryType:
0773                     return tr("Load packages");
0774                 case lvtshr::DiagramType::PackageType:
0775                     return tr("Load packages or components");
0776                 case lvtshr::DiagramType::ComponentType:
0777                     return tr("Load UDT's");
0778                 case lvtshr::DiagramType::ClassType:
0779                     return tr("Load Inner Classes and Structs");
0780                 case lvtshr::DiagramType::FreeFunctionType:
0781                     // Currently, we do not expect types or functions inside free functions
0782                     return {};
0783                 case lvtshr::DiagramType::NoneType:
0784                     assert(false && "Should never hit.");
0785                 }
0786                 return {};
0787             }();
0788 
0789             childAction->setText(text);
0790             connect(childAction, &QAction::triggered, this, &LakosEntity::loadChildren);
0791             retValues.append(childAction);
0792         } else {
0793             if (!internalNode()->children().empty()) {
0794                 auto *noChildAction = new QAction();
0795                 const auto type = internalNode()->type();
0796                 const auto text = [type]() -> QString {
0797                     switch (type) {
0798                     case lvtshr::DiagramType::RepositoryType:
0799                         return tr("Unload packages");
0800                     case lvtshr::DiagramType::PackageType:
0801                         return tr("Unload packages or components");
0802                     case lvtshr::DiagramType::ComponentType:
0803                         return tr("Unload UDT's");
0804                     case lvtshr::DiagramType::ClassType:
0805                         return tr("Unload Inner Classes and Structs");
0806                     case lvtshr::DiagramType::FreeFunctionType:
0807                         // Currently, we do not expect types or functions inside free functions
0808                         return {};
0809                     case lvtshr::DiagramType::NoneType:
0810                         assert(false && "Should never hit.");
0811                     }
0812                     return {};
0813                 }();
0814 
0815                 noChildAction->setText(text);
0816                 connect(noChildAction, &QAction::triggered, this, [this] {
0817                     Q_EMIT unloadChildren();
0818                 });
0819                 retValues.append(noChildAction);
0820             }
0821         }
0822     }
0823 
0824     auto *separator = new QAction();
0825     separator->setSeparator(true);
0826     retValues.append(separator);
0827 
0828     {
0829         auto *action = new QAction();
0830         action->setText(tr("Rename entity"));
0831         connect(action, &QAction::triggered, this, [this] {
0832             bool ok = false;
0833             auto newName = QInputDialog::getText(nullptr,
0834                                                  tr("Rename entity"),
0835                                                  tr("New name:"),
0836                                                  QLineEdit::Normal,
0837                                                  QString::fromStdString(name()),
0838                                                  &ok)
0839                                .toStdString();
0840             if (ok && !newName.empty()) {
0841                 Q_EMIT entityRenameRequest(uniqueId(), newName);
0842             }
0843         });
0844         retValues.append(action);
0845     }
0846     if (instanceType() != lvtshr::DiagramType::RepositoryType) {
0847         auto *action = new QAction();
0848         const QString toolTip = d->node->notes().empty() ? tr("Add Notes") : tr("Change Notes");
0849 
0850         action->setText(toolTip);
0851         connect(action, &QAction::triggered, this, [this] {
0852             showNotesDialog();
0853         });
0854         retValues.append(action);
0855     }
0856 
0857     if (isExpanded()) {
0858         auto *action = new QAction(tr("Layout"));
0859 
0860         auto *layoutMenu = new QMenu();
0861         action->setMenu(layoutMenu);
0862         auto *verticalLayout = new QAction(tr("Vertical"));
0863 
0864         connect(verticalLayout, &QAction::triggered, this, [this, scenePosition] {
0865             auto direction = Preferences::invertVerticalLevelizationLayout() ? +1 : -1;
0866             levelizationLayout(LevelizationLayoutType::Vertical, direction, scenePosition);
0867             Q_EMIT graphUpdate();
0868         });
0869         layoutMenu->addAction(verticalLayout);
0870 
0871         auto *horizontalLayout = new QAction(tr("Horizontal"));
0872         connect(horizontalLayout, &QAction::triggered, this, [this, scenePosition] {
0873             auto direction = Preferences::invertHorizontalLevelizationLayout() ? +1 : -1;
0874             levelizationLayout(LevelizationLayoutType::Horizontal, direction, scenePosition);
0875             Q_EMIT graphUpdate();
0876         });
0877         layoutMenu->addAction(horizontalLayout);
0878 
0879         retValues.append(action);
0880     }
0881 
0882     if (!isMainEntity()) {
0883         auto *action = new QAction();
0884         action->setText(tr("Navigate to %1").arg(QString::fromStdString(d->qualifiedName)));
0885         connect(action, &QAction::triggered, this, [this] {
0886             Q_EMIT navigateRequested();
0887         });
0888         retValues.append(action);
0889     }
0890 
0891     auto *removeAction = new QAction(tr("Remove"));
0892     removeAction->setToolTip(tr("Removes this entity from the database"));
0893     connect(removeAction, &QAction::triggered, this, &LakosEntity::requestRemoval);
0894     retValues.append(removeAction);
0895 
0896     if (d->showRedundantEdges) {
0897         auto *hideEdgesAction = new QAction();
0898         hideEdgesAction->setText(tr("Hide redundant edges"));
0899         connect(hideEdgesAction, &QAction::triggered, this, [this] {
0900             showRedundantRelations(false);
0901             showChildRedundantRelations(false);
0902         });
0903         retValues.append(hideEdgesAction);
0904     } else {
0905         auto *showEdgesAction = new QAction();
0906         showEdgesAction->setText(tr("Show redundant edges"));
0907         connect(showEdgesAction, &QAction::triggered, this, [this] {
0908             showRedundantRelations(true);
0909             showChildRedundantRelations(true);
0910         });
0911         retValues.append(showEdgesAction);
0912     }
0913 
0914     if (lakosEntities().empty()) {
0915         return retValues;
0916     }
0917 
0918     if (d->isExpanded) {
0919         auto *action = new QAction(tr("Collapse"));
0920         connect(action, &QAction::triggered, this, [this, scenePosition] {
0921             toggleExpansion(QtcUtil::CreateUndoAction::e_Yes, scenePosition, RelayoutBehavior::e_RequestRelayout);
0922         });
0923         retValues.append(action);
0924     } else {
0925         auto *action = new QAction(tr("Expand"));
0926         connect(action, &QAction::triggered, this, [this] {
0927             toggleExpansion(QtcUtil::CreateUndoAction::e_Yes, std::nullopt, RelayoutBehavior::e_RequestRelayout);
0928         });
0929         retValues.append(action);
0930     }
0931 
0932     if (d->isExpanded) {
0933         if (d->isOpaque) {
0934             auto *action = new QAction(tr("Show Content"));
0935             action->setToolTip(
0936                 tr("Uncovers this entity, showing all entities "
0937                    "within it, but does not uncovers the children "
0938                    "entities if they are covered."));
0939 
0940             connect(action, &QAction::triggered, this, [this] {
0941                 showContent(ToggleContentBehavior::Single, QtcUtil::CreateUndoAction::e_Yes);
0942                 Q_EMIT graphUpdate();
0943             });
0944             retValues.append(action);
0945 
0946             action = new QAction(tr("Show Content Recursively"));
0947             action->setToolTip(tr("Uncovers this entity, and all boxes within it, recursively"));
0948             connect(action, &QAction::triggered, this, [this] {
0949                 showContent(ToggleContentBehavior::Recursive, QtcUtil::CreateUndoAction::e_Yes);
0950                 Q_EMIT graphUpdate();
0951             });
0952 
0953             retValues.append(action);
0954         } else {
0955             // We can't have a non recursive version for the hide, because
0956             // edges crossing from other containers will appear quite broken.
0957             auto *action = new QAction(tr("Hide Content"));
0958             action->setToolTip(tr("Covers this entity, and all boxes within it, recursively"));
0959             connect(action, &QAction::triggered, this, [this] {
0960                 hideContent(ToggleContentBehavior::Recursive, QtcUtil::CreateUndoAction::e_Yes);
0961                 Q_EMIT graphUpdate();
0962             });
0963             retValues.append(action);
0964         }
0965     }
0966 
0967     return retValues;
0968 }
0969 
0970 std::unordered_map<LakosEntity *, int> LakosEntity::childrenLevels() const
0971 {
0972     auto children = lakosEntities();
0973     auto childrenStdVec = std::vector<LakosEntity *>(children.begin(), children.end());
0974     return computeLevelForEntities(childrenStdVec, this);
0975 }
0976 
0977 std::unique_ptr<QDialog> LakosEntity::createNotesDialog()
0978 {
0979     auto notesDialog = std::make_unique<QDialog>();
0980     notesDialog->setWindowTitle(tr("%1 Notes").arg(QString::fromStdString(name())));
0981     auto *notesTextEdit = new MRichTextEdit();
0982     auto *layout = new QBoxLayout(QBoxLayout::TopToBottom);
0983     auto *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
0984 
0985     notesDialog->setLayout(layout);
0986     layout->addWidget(notesTextEdit);
0987     layout->addWidget(buttonBox);
0988 
0989     connect(buttonBox, &QDialogButtonBox::accepted, notesDialog.get(), &QDialog::accept);
0990     connect(buttonBox, &QDialogButtonBox::rejected, notesDialog.get(), &QDialog::reject);
0991     connect(notesDialog.get(), &QDialog::accepted, this, [=, this] {
0992         const std::string oldNotes = d->node->notes();
0993         const std::string newNotes = notesTextEdit->toHtml().toStdString();
0994         const QString text = notesTextEdit->toHtml();
0995         d->node->setNotes(text.toStdString());
0996 
0997         Q_EMIT undoCommandCreated(
0998             new UndoNotes(qualifiedName(), instanceType(), oldNotes, newNotes, qobject_cast<GraphicsScene *>(scene())));
0999     });
1000     return notesDialog;
1001 }
1002 
1003 void LakosEntity::showNotesDialog()
1004 {
1005     auto notesDialog = createNotesDialog();
1006     auto *textEdit = notesDialog->findChild<MRichTextEdit *>();
1007     textEdit->setText(QString::fromStdString(d->node->notes()));
1008     notesDialog->exec();
1009 }
1010 
1011 void LakosEntity::populateMenu(QMenu& menu, QMenu *debugMenu, QPointF scenePosition)
1012 {
1013     // We need to test the parent to see if we can access this element.
1014     // if the parent is covered, the contextMenu is just for the parent.
1015     auto *containerParent = dynamic_cast<LakosEntity *>(parentItem());
1016     if (containerParent) {
1017         if (containerParent->isCovered()) {
1018             return;
1019         }
1020     }
1021 
1022     if (d->pluginManager) {
1023         auto& pm = d->pluginManager->get();
1024         auto getEntity = [this]() {
1025             return createWrappedEntityFromLakosEntity(this);
1026         };
1027 
1028         auto addReport = [this, &menu](std::string const& actionLabel,
1029                                        std::string const& reportTitle,
1030                                        std::function<void(PluginEntityReportActionHandler *)> const& userAction) {
1031             auto *action = menu.addAction(QString::fromStdString(actionLabel));
1032             connect(action, &QAction::triggered, this, [this, reportTitle, userAction]() {
1033                 auto& pm = d->pluginManager->get();
1034                 auto getPluginData = [&pm](auto&& id) { // clazy:exclude=lambda-in-connect
1035                     return pm.getPluginData(id);
1036                 };
1037                 auto getEntity = [this]() {
1038                     return createWrappedEntityFromLakosEntity(this);
1039                 };
1040                 auto reportContents = std::string{};
1041                 // Clazy is afraid of the lambda capture by reference below, but it is safe since the lambda is consumed
1042                 // only within the scope of the current function, and not used elsewhere. There's a bug report on:
1043                 // https://bugs.kde.org/show_bug.cgi?id=443342
1044                 auto setReportContents =
1045                     [&reportContents](std::string const& contentsHTML) { // clazy:exclude=lambda-in-connect
1046                         reportContents = contentsHTML;
1047                     };
1048 
1049                 auto h = PluginEntityReportActionHandler{getPluginData, getEntity, setReportContents};
1050                 userAction(&h);
1051 
1052                 Q_EMIT createReportActionClicked(reportTitle, reportContents);
1053             });
1054         };
1055 
1056         pm.callHooksSetupEntityReport(getEntity, addReport);
1057     }
1058 
1059     QList<QAction *> actions = actionsForMenu(scenePosition);
1060     if (actions.empty()) {
1061         return;
1062     }
1063 
1064     {
1065         auto *action = menu.addAction(QString::fromStdString(name()));
1066         action->setToolTip(tr("Copy element name"));
1067         connect(action, &QAction::triggered, this, [this] {
1068             auto *clip = QGuiApplication::clipboard();
1069             clip->setText(QString::fromStdString(name()));
1070         });
1071     }
1072 
1073     menu.addSeparator();
1074 
1075     if (debugMenu) {
1076         auto *toggleBackgroundAction = debugMenu->addAction(tr("Toggle background"));
1077         toggleBackgroundAction->setCheckable(true);
1078         toggleBackgroundAction->setChecked(d->showBackground);
1079 
1080         connect(toggleBackgroundAction, &QAction::triggered, this, [this] {
1081             d->showBackground = !d->showBackground;
1082             updateBackground();
1083         });
1084 
1085         auto *toggleCenterPoint = debugMenu->addAction(tr("Toggle Center"));
1086         toggleCenterPoint->setChecked(d->centerDbg ? d->centerDbg->isVisible() : false);
1087 
1088         connect(toggleCenterPoint, &QAction::triggered, this, [this](bool toggled) {
1089             if (!d->centerDbg) {
1090                 d->centerDbg = new QGraphicsEllipseItem(-5, -5, 10, 10);
1091                 d->centerDbg->setPos(pos());
1092                 d->centerDbg->setZValue(zValue() + 1);
1093                 scene()->addItem(d->centerDbg);
1094             }
1095             d->centerDbg->setVisible(toggled);
1096         });
1097 
1098         auto *boundingRectBtn = debugMenu->addAction(tr("Show Enveloping Rectangle"));
1099         boundingRectBtn->setToolTip(
1100             tr("Displays the rectangle of this item, <br/> that might be bigger than what the visual element is."));
1101         boundingRectBtn->setCheckable(true);
1102         boundingRectBtn->setChecked(false);
1103         connect(boundingRectBtn, &QAction::triggered, this, [this](bool checked) {
1104             if (!checked) {
1105                 delete d->boundingRectDbg;
1106                 d->boundingRectDbg = nullptr;
1107                 return;
1108             }
1109 
1110             delete d->boundingRectDbg;
1111             d->boundingRectDbg = new QGraphicsRectItem(boundingRect());
1112             d->boundingRectDbg->setPos(pos());
1113             // we need to set the bounding rect behind this item for mouse event propagation
1114             d->boundingRectDbg->setZValue(zValue() - 1);
1115             scene()->addItem(d->boundingRectDbg);
1116             d->boundingRectDbg->setVisible(true);
1117         });
1118 
1119         auto *childBoundingRect = debugMenu->addAction(tr("Show Children Rectangle"));
1120         childBoundingRect->setToolTip(
1121             tr("Display this element's children bounding rectangle <br/> This should be the same size of the item if "
1122                "expanded <br/> and larger if shrinked."));
1123         childBoundingRect->setCheckable(true);
1124         childBoundingRect->setChecked(false);
1125         connect(childBoundingRect, &QAction::triggered, this, [this](bool checked) {
1126             if (!checked) {
1127                 delete d->boundingRectDbg;
1128                 d->boundingRectDbg = nullptr;
1129                 return;
1130             }
1131 
1132             delete d->boundingRectDbg;
1133             d->boundingRectDbg = new QGraphicsRectItem(childrenBoundingRect());
1134             d->boundingRectDbg->setPos(pos());
1135             // we need to set the bounding rect behind this item for mouse event propagation
1136             d->boundingRectDbg->setZValue(zValue() - 1);
1137             scene()->addItem(d->boundingRectDbg);
1138             d->boundingRectDbg->setVisible(true);
1139         });
1140     }
1141 
1142     for (auto *action : actions) {
1143         menu.addAction(action);
1144     }
1145 }
1146 
1147 bool LakosEntity::isBlockingEvents() const
1148 {
1149     auto *containerParent = dynamic_cast<LakosEntity *>(parentItem());
1150     if (containerParent) {
1151         return containerParent->isCovered();
1152     }
1153     return false;
1154 }
1155 
1156 /* Variables that control the Moving / Dragging of the LakosEntities. */
1157 namespace {
1158 // Shared variables. Only one element can be moved at a time.
1159 bool s_isDraggingItem = false; // NOLINT
1160 // Are we moving this node with the mouse?
1161 
1162 bool s_tooSlowToMove = false; // NOLINT
1163 // Qt does not manages correctly huge items,
1164 // and if we have an item that's too
1165 // slow to move, we need to use a hack.
1166 // this is visible if we try to load bal/balb and try to move
1167 // BSL. 80% of the time is on PrepareGeometryChange and moving
1168 // a single pixel can take up to 1 second.
1169 
1170 std::optional<QPointF> s_originalPos; // NOLINT
1171 // holds the original position before a move starts.
1172 
1173 QGraphicsSimpleTextItem *s_dragTextItem = nullptr; // NOLINT
1174 // if it's too slow to move, show a text to the new position.
1175 } // namespace
1176 
1177 void LakosEntity::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *ev)
1178 {
1179     if (ev->button() == Qt::LeftButton) {
1180         Q_EMIT toggleSelection();
1181         ev->accept();
1182         return;
1183     }
1184 }
1185 
1186 void LakosEntity::startDrag(QPointF startPosition)
1187 {
1188     QLineF line(mapToParent(mapFromScene(startPosition)), pos());
1189     s_isDraggingItem = true;
1190     s_originalPos = pos();
1191 
1192     // it isn't clear why Qt uses qreal (double) for QLineF, but float for
1193     // QVector2D (decltype(d->movementDelta)). Anyway, float should be enough
1194     // precision for this use case
1195     d->movementDelta.setX(static_cast<float>(line.dx()));
1196     d->movementDelta.setY(static_cast<float>(line.dy()));
1197 
1198     Q_EMIT dragStarted();
1199 }
1200 
1201 void LakosEntity::doDrag(QPointF movePosition)
1202 {
1203     const auto nextPos = mapToParent(mapFromScene(movePosition) + d->movementDelta.toPointF());
1204 
1205     if (nextPos == pos()) {
1206         return;
1207     }
1208 
1209     if (!s_tooSlowToMove) {
1210         QElapsedTimer timer;
1211         timer.start();
1212         setPos(nextPos);
1213         if (timer.elapsed() > 35) { // ~30 FPS
1214             s_tooSlowToMove = true;
1215             s_dragTextItem = new QGraphicsSimpleTextItem();
1216             scene()->addItem(s_dragTextItem);
1217             s_dragTextItem->setPos(mapToScene(movePosition));
1218         } else {
1219             Q_EMIT moving();
1220         }
1221         if (d->boundingRectDbg) {
1222             d->boundingRectDbg->setPos(nextPos);
1223         }
1224         if (d->centerDbg) {
1225             d->centerDbg->setPos(nextPos);
1226         }
1227     } else {
1228         s_dragTextItem->setText(tr("Move Update is too slow\nFrom (%1, %2) to (%3, %4)")
1229                                     .arg(s_originalPos->x())
1230                                     .arg(s_originalPos->y())
1231                                     .arg(movePosition.x())
1232                                     .arg(movePosition.y()));
1233         s_dragTextItem->setFlag(ItemIgnoresTransformations);
1234         s_dragTextItem->setPos(mapToScene(movePosition));
1235         s_dragTextItem->setZValue(QtcUtil::e_INFORMATION);
1236     }
1237 }
1238 
1239 void LakosEntity::endDrag(QPointF endPosition)
1240 {
1241     if (s_isDraggingItem) {
1242         s_isDraggingItem = false;
1243         const QPointF newPos = mapToParent(mapFromScene(endPosition) + d->movementDelta.toPointF());
1244 
1245         // QFuzzyCompare fails here.
1246         constexpr float EPISILON = 0.00001;
1247 
1248         const bool xEqual = fabs(newPos.x() - s_originalPos.value().x()) < EPISILON;
1249         const bool yEqual = fabs(newPos.y() - s_originalPos.value().y()) < EPISILON;
1250         if (!xEqual || !yEqual) {
1251             auto *thisScene = qobject_cast<GraphicsScene *>(scene());
1252             Q_EMIT undoCommandCreated(new UndoMove(thisScene, d->qualifiedName, *s_originalPos, newPos));
1253 
1254             recursiveEdgeRelayout();
1255             Q_EMIT graphUpdate();
1256         }
1257         s_originalPos = {};
1258         Q_EMIT dragFinished();
1259 
1260         if (s_tooSlowToMove) {
1261             s_tooSlowToMove = false;
1262             delete s_dragTextItem;
1263             s_dragTextItem = nullptr;
1264         }
1265     }
1266 }
1267 
1268 void LakosEntity::mousePressEvent(QGraphicsSceneMouseEvent *ev)
1269 {
1270     s_lastClick = ev->scenePos();
1271 
1272     qDebug() << "LakosEntity MousePressEvent" << QString::fromStdString(name());
1273 
1274     if (isBlockingEvents()) {
1275         // If it has a parent item, then it's an item inside a container.
1276         if (parentItem()) {
1277             ev->ignore();
1278             return;
1279         }
1280         ev->accept();
1281         return;
1282     }
1283 
1284     if (ev->button() != Qt::LeftButton || ev->modifiers() != Qt::KeyboardModifier::NoModifier) {
1285         ev->ignore();
1286         return;
1287     }
1288 
1289     startDrag(mapToScene(ev->pos()));
1290 }
1291 
1292 void LakosEntity::mouseMoveEvent(QGraphicsSceneMouseEvent *ev)
1293 {
1294     if (!s_isDraggingItem) {
1295         return;
1296     }
1297 
1298     doDrag(mapToScene(ev->pos()));
1299 }
1300 
1301 void LakosEntity::mouseReleaseEvent(QGraphicsSceneMouseEvent *ev)
1302 {
1303     if (Preferences::enableDebugOutput()) {
1304         qDebug() << "LakosEntity MouseReleaseEvent" << QString::fromStdString(name());
1305     }
1306 
1307     endDrag(mapToScene(ev->pos()));
1308 
1309     if (s_lastClick != ev->scenePos()) {
1310         return;
1311     }
1312 
1313     ev->accept();
1314 
1315     // Hide or show content based on the number of children.
1316     if (isExpanded() && !lakosEntities().empty()) {
1317         if (ev->button() == Qt::LeftButton) {
1318             if (d->isOpaque) {
1319                 showContent(ToggleContentBehavior::Single, QtcUtil::CreateUndoAction::e_Yes);
1320                 Q_EMIT graphUpdate();
1321             } else {
1322                 hideContent(ToggleContentBehavior::Single, QtcUtil::CreateUndoAction::e_Yes);
1323                 Q_EMIT graphUpdate();
1324             }
1325             Q_EMIT coverChanged();
1326             return;
1327         }
1328     }
1329 }
1330 
1331 void LakosEntity::recursiveEdgeRelayout()
1332 {
1333     if (!layoutUpdatesEnabled()) {
1334         return;
1335     }
1336 
1337     if (!d->isOpaque) {
1338         for (auto *child : d->lakosChildren) {
1339             if (child->isVisible()) {
1340                 child->recursiveEdgeRelayout();
1341             }
1342         }
1343     }
1344 
1345     for (auto& edges : d->edges) {
1346         if (!edges->isRedundant() || d->showRedundantEdges) {
1347             edges->layoutRelations();
1348         }
1349     }
1350 
1351     for (auto& edges : d->targetEdges) {
1352         if (!edges->isRedundant() || d->showRedundantEdges) {
1353             edges->layoutRelations();
1354         }
1355     }
1356 }
1357 
1358 void LakosEntity::layoutAllRelations()
1359 {
1360     if (!layoutUpdatesEnabled()) {
1361         return;
1362     }
1363 
1364     for (auto& edges : d->edges) {
1365         edges->layoutRelations();
1366     }
1367     for (auto& edges : d->targetEdges) {
1368         edges->layoutRelations();
1369     }
1370 }
1371 
1372 void LakosEntity::setMainEntity()
1373 {
1374     d->isMainNode = true;
1375     updateBackground();
1376 }
1377 
1378 void LakosEntity::recursiveEdgeHighlight(bool highlight)
1379 {
1380     for (auto *item : childItems()) {
1381         auto *childEntity = dynamic_cast<LakosEntity *>(item);
1382         if (childEntity) {
1383             for (const auto& eCollection : childEntity->edgesCollection()) {
1384                 eCollection->setHighlighted(highlight);
1385             }
1386         }
1387     }
1388     for (const auto& eCollection : edgesCollection()) {
1389         eCollection->setHighlighted(highlight);
1390     }
1391 }
1392 
1393 void LakosEntity::hoverEnterEvent(QGraphicsSceneHoverEvent *ev)
1394 {
1395     if (isBlockingEvents()) {
1396         return;
1397     }
1398 
1399     Q_UNUSED(ev);
1400 
1401     auto *pItem = dynamic_cast<LakosEntity *>(parentItem());
1402     if (!pItem) {
1403         recursiveEdgeHighlight(true);
1404     }
1405 
1406     if (signalsBlocked()) {
1407         return;
1408     }
1409 
1410     if (ev->modifiers()) {
1411         return;
1412     }
1413 
1414     QGuiApplication::setOverrideCursor(Qt::CursorShape::PointingHandCursor);
1415 
1416     auto *ourScene = qobject_cast<GraphicsScene *>(scene());
1417     if (ourScene && !ourScene->blockNodeResizeOnHover()) {
1418         if (!d->isExpanded) {
1419             setFlag(QGraphicsItem::ItemIgnoresTransformations, true);
1420         }
1421     }
1422 
1423     if (!isMainEntity()) {
1424         if (d->showBackground) {
1425             QBrush thisBrush = brush();
1426             const QColor& thisColor = thisBrush.color();
1427             setBrush(QBrush(QColor(thisColor.red(), thisColor.green(), thisColor.blue(), 180)));
1428         }
1429     }
1430 }
1431 
1432 void LakosEntity::hoverLeaveEvent(QGraphicsSceneHoverEvent *ev)
1433 {
1434     if (isBlockingEvents()) {
1435         return;
1436     }
1437 
1438     Q_UNUSED(ev);
1439     QGuiApplication::restoreOverrideCursor();
1440 
1441     auto *pItem = dynamic_cast<LakosEntity *>(parentItem());
1442     if (!pItem) {
1443         recursiveEdgeHighlight(false);
1444     }
1445 
1446     setFlag(QGraphicsItem::ItemIgnoresTransformations, false);
1447 
1448     if (!isMainEntity()) {
1449         if (d->showBackground) {
1450             QBrush thisBrush = brush();
1451             const QColor& thisColor = thisBrush.color();
1452             setBrush(QBrush(QColor(thisColor.red(), thisColor.green(), thisColor.blue())));
1453         }
1454     }
1455 }
1456 
1457 void LakosEntity::updateBackground()
1458 {
1459     if (!d->showBackground) {
1460         QBrush b = QBrush();
1461         b.setColor(QColor(10, 10, 10, 0));
1462         setBrush(b);
1463         return;
1464     }
1465 
1466     if (!layoutUpdatesEnabled()) {
1467         return;
1468     }
1469 
1470     if (d->isMainNode) {
1471         const QRectF bRect = rect();
1472         if (d->enableGradientOnMainNode) {
1473             QLinearGradient linearGrad(bRect.topLeft(), bRect.bottomRight());
1474             linearGrad.setColorAt(0, d->color);
1475             linearGrad.setColorAt(1, d->color.lighter());
1476             setBrush(QBrush(linearGrad));
1477         } else {
1478             QBrush b = brush();
1479             b.setColor(d->selectedNodeColor);
1480             b.setStyle(d->fillPattern);
1481             setBrush(b);
1482         }
1483         setPen(QPen(QBrush(Qt::blue), 1));
1484     } else {
1485         QBrush b = brush();
1486         b.setColor(d->color);
1487         b.setStyle(d->fillPattern);
1488         setBrush(b);
1489     }
1490 }
1491 
1492 void LakosEntity::setColorManagement(lvtclr::ColorManagement *colorManagement)
1493 {
1494     assert(colorManagement);
1495     if (Preferences::colorBlindMode()) {
1496         d->color = colorManagement->getColorFor(d->colorId);
1497     } else {
1498         d->color = Preferences::entityBackgroundColor();
1499     }
1500     d->fillPattern = colorManagement->fillPattern(d->colorId);
1501 
1502     updateBackground();
1503 
1504     connect(colorManagement, &lvtclr::ColorManagement::requestNewColor, this, [this, colorManagement] {
1505         if (Preferences::colorBlindMode()) {
1506             d->color = colorManagement->getColorFor(d->colorId);
1507         } else {
1508             d->color = Preferences::entityBackgroundColor();
1509         }
1510         d->fillPattern = colorManagement->fillPattern(d->colorId);
1511         updateBackground();
1512     });
1513 }
1514 
1515 void LakosEntity::makeToolTip(const std::string& noColorStr)
1516 {
1517     if (d->toolTip.length()) {
1518         setToolTip(QString::fromStdString(d->toolTip));
1519         return;
1520     }
1521 
1522     std::string toolTip(d->qualifiedName + '\n');
1523     if (d->colorId.empty()) {
1524         toolTip += noColorStr;
1525     } else {
1526         toolTip += d->colorId;
1527     }
1528     setToolTip(QString::fromStdString(toolTip));
1529 }
1530 
1531 void LakosEntity::setColorId(const std::string& colorId)
1532 {
1533     if (d->colorId == colorId) {
1534         return;
1535     }
1536     d->colorId = colorId;
1537 
1538     if (layoutUpdatesEnabled()) {
1539         update();
1540     }
1541 }
1542 
1543 void LakosEntity::enableLayoutUpdates()
1544 {
1545     d->layoutUpdatesEnabled = true;
1546 
1547     // We need to invert the logic of the isExpanded
1548     // here, because we set it when the layout updates
1549     // where disabled, so invert, and toggle back to
1550     // the correct value - doing the calculations.
1551     d->isExpanded = !d->isExpanded;
1552     toggleExpansion(QtcUtil::CreateUndoAction::e_No);
1553 
1554     updateBackground();
1555     layoutAllRelations();
1556 
1557     // After testing a bit, the best scenario on enableLayoutUpdates
1558     // is to hide everything recursively, but show only the toplevel.
1559     if (d->isOpaque) {
1560         hideContent(ToggleContentBehavior::Recursive, QtcUtil::CreateUndoAction::e_No);
1561     } else {
1562         showContent(ToggleContentBehavior::Single, QtcUtil::CreateUndoAction::e_No);
1563     }
1564 }
1565 
1566 void LakosEntity::setTextPos(qreal x, qreal y)
1567 {
1568     const auto textRect = d->text->boundingRect();
1569     d->text->setPos(mapFromScene(QPointF(x - textRect.width() / 2, y - textRect.height() / 2)));
1570 }
1571 
1572 QPointF LakosEntity::getTextPos() const
1573 {
1574     const QPointF pos = d->text->pos();
1575     const QRectF textRect = d->text->boundingRect();
1576     return mapToScene(pos.x() + textRect.width() / 2, pos.y() + textRect.height() / 2);
1577 }
1578 
1579 void LakosEntity::removeEdge(LakosRelation *relation)
1580 {
1581     auto removeEdgeFrom = [relation](std::vector<std::shared_ptr<EdgeCollection>>& collection) {
1582         auto itSource = std::find_if(std::begin(collection),
1583                                      std::end(collection),
1584                                      [relation](const std::shared_ptr<EdgeCollection>& ec) {
1585                                          return ec && (ec->from() == relation->from() && ec->to() == relation->to());
1586                                      });
1587 
1588         if (itSource != std::end(collection)) {
1589             auto& ec = *itSource;
1590             ec->removeEdge(relation);
1591             if (ec->relations().empty()) {
1592                 collection.erase(itSource);
1593             }
1594         }
1595     };
1596 
1597     removeEdgeFrom(d->edges);
1598     removeEdgeFrom(d->targetEdges);
1599     removeEdgeFrom(d->redundantRelations);
1600 }
1601 
1602 bool LakosEntity::isCoveredByParent() const
1603 {
1604     auto *parent = dynamic_cast<LakosEntity *>(parentItem());
1605     while (parent) {
1606         if (parent->isCovered()) {
1607             return true;
1608         }
1609         parent = dynamic_cast<LakosEntity *>(parent->parentItem());
1610     }
1611     return false;
1612 }
1613 
1614 bool LakosEntity::isParentCollapsed() const
1615 {
1616     auto *parent = dynamic_cast<LakosEntity *>(parentItem());
1617     while (parent) {
1618         if (!parent->isExpanded()) {
1619             return true;
1620         }
1621         parent = dynamic_cast<LakosEntity *>(parent->parentItem());
1622     }
1623     return false;
1624 }
1625 
1626 bool LakosEntity::hasRelationshipWith(LakosEntity *entity) const
1627 {
1628     if (entity == nullptr) {
1629         return false;
1630     }
1631 
1632     return std::any_of(d->edges.begin(), d->edges.end(), [entity](const auto& ec) {
1633         return ec->to() == entity;
1634     });
1635 }
1636 
1637 std::shared_ptr<EdgeCollection> LakosEntity::getRelationshipWith(LakosEntity *entity) const
1638 {
1639     for (auto const& ec : d->edges) {
1640         if (ec->to() == entity) {
1641             return ec;
1642         }
1643     }
1644     return nullptr;
1645 }
1646 
1647 /* This is really about the children, not this node. We want to know if the
1648  * child has an edge that crosses boundaries with the boundingRect of this entity.
1649  */
1650 bool LakosEntity::childrenHasVisibleRelationshipWith(LakosEntity *otherEntity) const
1651 {
1652     for (auto *child : d->lakosChildren) {
1653         if (child->childrenHasVisibleRelationshipWith(otherEntity)) {
1654             return true;
1655         }
1656 
1657         for (const auto& collection : {child->edgesCollection(), child->targetCollection()}) {
1658             for (const auto& ec : collection) {
1659                 if (ec->from() == otherEntity || ec->to() == otherEntity) {
1660                     for (LakosRelation *relation : ec->relations()) {
1661                         if (relation->isVisible()) {
1662                             return true;
1663                         }
1664                     }
1665                 }
1666             }
1667         }
1668     }
1669 
1670     return false;
1671 }
1672 
1673 bool childrenConnected(LakosEntity *a, LakosEntity *b)
1674 {
1675     for (LakosEntity *childA : a->lakosEntities()) {
1676         for (const LakosEntity *childB : b->lakosEntities()) {
1677             for (const auto& ec : childA->edgesCollection()) {
1678                 if (ec->to() == childB) {
1679                     return true;
1680                 }
1681             }
1682             for (const auto& ec : childA->targetCollection()) {
1683                 if (ec->from() == childB) {
1684                     return true;
1685                 }
1686             }
1687         }
1688     }
1689     return false;
1690 }
1691 
1692 void LakosEntity::calculateEdgeVisibility(const std::shared_ptr<EdgeCollection>& ec)
1693 {
1694     /* Edge Visibility Rules: check file lvtqtc/edge_rules.svg */
1695     // TODO: let's remove those std::shared_ptr's. this is just adding complexity.
1696     if (!ec) {
1697         return;
1698     }
1699 
1700     // rule 1: Entities inside a collapsed or covered panel are always hidden
1701     auto *from = ec->from();
1702     auto *to = ec->to();
1703     if (from->isParentCollapsed() || to->isParentCollapsed()) {
1704         ec->setVisible(false);
1705         return;
1706     }
1707 
1708     // Rule 2 - hide redudant edges, if we can.
1709     if (ec->isRedundant() && !d->showRedundantEdges) {
1710         ec->setVisible(false);
1711         return;
1712     }
1713 
1714     auto *pFrom = qgraphicsitem_cast<LakosEntity *>(from->parentItem());
1715     auto *pTo = qgraphicsitem_cast<LakosEntity *>(to->parentItem());
1716     if (pFrom && pTo && pFrom == pTo) {
1717         ec->setVisible(true);
1718         return;
1719     }
1720 
1721     if (from->isCoveredByParent() || to->isCoveredByParent()) {
1722         ec->setVisible(false);
1723         return;
1724     }
1725 
1726     // Rule 3, we (unfortunately) really need to check if a child
1727     // has a visible edge to a node, before drawing this edge.
1728     // it would be really good to not have a specific rule for this, but,
1729     // I could think of a way to make this more generic.
1730     LakosEntity *otherEntity = ec->from() == this ? ec->to() : ec->from();
1731     if (childrenHasVisibleRelationshipWith(otherEntity)) {
1732         ec->setVisible(false);
1733         return;
1734     }
1735 
1736     // Rule 4 - If there's a connection between 2 entities, but no connections between the children, we must show the
1737     // parent edge.
1738     if (!childrenConnected(ec->from(), ec->to())) {
1739         ec->setVisible(true);
1740         return;
1741     }
1742 
1743     // Rule 5 - If there's a connection between 2 entities, and also between the children, we must only show the parent
1744     // edge if it is covering the children.
1745     const bool fVisibility = ec->from()->isCovered() || !ec->from()->isExpanded();
1746     const bool tVisibility = ec->to()->isCovered() || !ec->to()->isExpanded();
1747     ec->setVisible(fVisibility || tVisibility);
1748 }
1749 
1750 void LakosEntity::calculateEdgeVisibility()
1751 {
1752     // When we cover / uncover, collapse, etc. this might
1753     // affect edges on children, so this needs to be recursive.
1754     for (const auto& child : d->lakosChildren) {
1755         child->calculateEdgeVisibility();
1756     }
1757 
1758     for (const auto& ec : d->edges) {
1759         calculateEdgeVisibility(ec);
1760     }
1761 
1762     for (const auto& ec : d->targetEdges) {
1763         calculateEdgeVisibility(ec);
1764     }
1765 }
1766 
1767 void LakosEntity::toggleCover(ToggleContentBehavior behavior, QtcUtil::CreateUndoAction create)
1768 {
1769     if (d->isOpaque) {
1770         showContent(behavior, create);
1771     } else {
1772         hideContent(behavior, create);
1773     }
1774 }
1775 
1776 void LakosEntity::hideContent(ToggleContentBehavior behavior, QtcUtil::CreateUndoAction create)
1777 {
1778     if (create == QtcUtil::CreateUndoAction::e_Yes) {
1779         Q_EMIT undoCommandCreated(new UndoCover(qobject_cast<GraphicsScene *>(scene()), d->id));
1780         return;
1781     }
1782 
1783     if (lakosEntities().empty()) {
1784         return;
1785     }
1786 
1787     d->isOpaque = true;
1788 
1789     if (!layoutUpdatesEnabled()) {
1790         return;
1791     }
1792 
1793     d->cover->setRectangle(rect());
1794     d->cover->setRoundRadius(roundRadius());
1795 
1796     d->cover->setVisible(true);
1797 
1798     calculateEdgeVisibility();
1799 
1800     for (LakosEntity *child : d->lakosChildren) {
1801         if (behavior == ToggleContentBehavior::Recursive) {
1802             child->hideContent(behavior, QtcUtil::CreateUndoAction::e_No);
1803         }
1804     }
1805     recursiveEdgeRelayout();
1806 }
1807 
1808 void LakosEntity::showContent(ToggleContentBehavior behavior, QtcUtil::CreateUndoAction create)
1809 {
1810     if (create == QtcUtil::CreateUndoAction::e_Yes) {
1811         Q_EMIT undoCommandCreated(new UndoCover(qobject_cast<GraphicsScene *>(scene()), d->id));
1812         return;
1813     }
1814 
1815     d->isOpaque = false;
1816 
1817     if (!layoutUpdatesEnabled()) {
1818         return;
1819     }
1820 
1821     d->cover->setVisible(false);
1822 
1823     calculateEdgeVisibility();
1824 
1825     for (LakosEntity *child : d->lakosChildren) {
1826         if (behavior == ToggleContentBehavior::Recursive) {
1827             child->showContent(behavior, QtcUtil::CreateUndoAction::e_No);
1828         }
1829     }
1830     recursiveEdgeRelayout();
1831 }
1832 
1833 void LakosEntity::reactChildRemoved(QGraphicsItem *child)
1834 {
1835     if (auto *lEntity = qgraphicsitem_cast<LakosEntity *>(child)) {
1836         // This is not slow as there is no memory deallocations / reallocations
1837         // for the remove. the vector will be completely deallocated only
1838         // on destruction of this object.
1839         d->lakosChildren.removeOne(lEntity);
1840         updateChildrenLoadedInfo();
1841 
1842         if (!d->lakosChildren.isEmpty()) {
1843             recalculateRectangle();
1844         } else {
1845             shrink(QtcUtil::CreateUndoAction::e_No);
1846         }
1847     }
1848 }
1849 
1850 void LakosEntity::reactChildAdded(QGraphicsItem *child)
1851 {
1852     if (auto *lEntity = qgraphicsitem_cast<LakosEntity *>(child)) {
1853         d->lakosChildren.append(lEntity);
1854         if (d->lakosChildren.size() == 1 && !d->isExpanded) {
1855             // Automatically expand a package that just received it's first item
1856             expand(QtcUtil::CreateUndoAction::e_No);
1857         }
1858 
1859         // update our size now we have a new child
1860         recalculateRectangle();
1861 
1862         // update our size whenever a child is moved
1863         connect(lEntity, &LakosEntity::moving, this, &LakosEntity::recalculateRectangle);
1864 
1865         // update our size whenever a child is resized
1866         connect(lEntity, &LakosEntity::rectangleChanged, this, &LakosEntity::recalculateRectangle);
1867 
1868         updateChildrenLoadedInfo();
1869     }
1870 }
1871 
1872 void LakosEntity::updateChildrenLoadedInfo()
1873 {
1874     auto drawMissingChildsMark = [&]() {
1875         if (d->notAllChildrenLoadedInfo != nullptr) {
1876             return;
1877         }
1878         d->notAllChildrenLoadedInfo = new QGraphicsSimpleTextItem(QStringLiteral("..."));
1879         d->notAllChildrenLoadedInfo->setParentItem(this);
1880         d->notAllChildrenLoadedInfo->setVisible(true);
1881         d->notAllChildrenLoadedInfo->setBrush(QBrush(Qt::gray));
1882         d->notAllChildrenLoadedInfo->setPen(Qt::NoPen);
1883         d->ignoredItems.push_back(d->notAllChildrenLoadedInfo);
1884     };
1885     auto removeMissingChildsMark = [&]() {
1886         if (d->notAllChildrenLoadedInfo == nullptr) {
1887             return;
1888         }
1889         auto it = std::remove(std::begin(d->ignoredItems), std::end(d->ignoredItems), d->notAllChildrenLoadedInfo);
1890         d->ignoredItems.erase(it, std::end(d->ignoredItems));
1891         delete d->notAllChildrenLoadedInfo;
1892         d->notAllChildrenLoadedInfo = nullptr;
1893     };
1894 
1895     auto numberOfChildrenLoadedOnScreen = static_cast<size_t>(d->lakosChildren.size());
1896     auto numberOfChildrenAvailableOnModel = d->node->children().size();
1897     if (numberOfChildrenAvailableOnModel != numberOfChildrenLoadedOnScreen) {
1898         drawMissingChildsMark();
1899     } else {
1900         removeMissingChildsMark();
1901     }
1902 }
1903 
1904 QVariant LakosEntity::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant& value)
1905 {
1906     GraphicsRectItem::itemChange(change, value);
1907 
1908     switch (change) {
1909     case QGraphicsItem::ItemChildRemovedChange:
1910         reactChildRemoved(value.value<QGraphicsItem *>());
1911         break;
1912     case QGraphicsItem::ItemChildAddedChange:
1913         reactChildAdded(value.value<QGraphicsItem *>());
1914         break;
1915     case QGraphicsItem::ItemSelectedHasChanged: {
1916         for (auto&& e : d->edges) {
1917             e->toggleRelationFlags(EdgeCollection::RelationIsSelected, value.value<bool>());
1918         }
1919         for (auto&& e : d->targetEdges) {
1920             e->toggleRelationFlags(EdgeCollection::RelationIsSelected, value.value<bool>());
1921         }
1922         break;
1923     }
1924     default:
1925         break;
1926     }
1927 
1928     return value;
1929 }
1930 
1931 void LakosEntity::showRedundantRelations(bool show)
1932 {
1933     d->showRedundantEdges = show;
1934     calculateEdgeVisibility();
1935 }
1936 
1937 void LakosEntity::showChildRedundantRelations(bool show)
1938 {
1939     for (LakosEntity *entity : lakosEntities()) {
1940         entity->showRedundantRelations(show);
1941         entity->showChildRedundantRelations(show);
1942     }
1943 }
1944 
1945 std::string LakosEntity::colorIdText() const
1946 {
1947     if (colorId().empty()) {
1948         return name() + " has no namespace";
1949     }
1950 
1951     return name() + " has namespace " + colorId();
1952 }
1953 
1954 std::string LakosEntity::legendText() const
1955 {
1956     const auto bRect = boundingRect();
1957     std::string information;
1958     information += "Name: " + name() + "\n";
1959     information += "Qualified Name: " + qualifiedName() + "\n";
1960     information += "Color ID: " + colorIdText() + "\n";
1961 
1962     if (QtcUtil::isDebugMode() && Preferences::enableSceneContextMenu()) {
1963         information += "Cover Status " + std::to_string(d->isOpaque) + "\n";
1964         information += "Item Visibility: " + std::to_string(d->cover->isVisible()) + "\n";
1965         information += "Pos: " + std::to_string(pos().x()) + ',' + std::to_string(pos().y()) + "\n";
1966         information +=
1967             "Top Left: " + std::to_string(bRect.topLeft().x()) + ',' + std::to_string(bRect.topLeft().y()) + "\n";
1968         information += "Bottom Right: " + std::to_string(bRect.bottomRight().x()) + ','
1969             + std::to_string(bRect.bottomRight().y()) + "\n";
1970     }
1971 
1972     information += "\n";
1973 
1974     auto displayEdgesLambda =
1975         [](const std::string& none, const std::string& some, std::string& information, auto& edges) {
1976             if (edges.empty()) {
1977                 information += none + '\n';
1978             } else {
1979                 information += some + ": " + std::to_string(edges.size()) + '\n';
1980                 std::string edge_names;
1981                 for (auto& edge : edges) {
1982                     edge_names += edge->to()->name() + " ";
1983                     if (edge_names.size() > 40) {
1984                         information += edge_names + "\n";
1985                         edge_names = std::string{};
1986                     }
1987                 }
1988                 information += "\n";
1989             }
1990         };
1991 
1992     displayEdgesLambda("No outgoing edges", "Connections from this node", information, d->edges);
1993     information += "\n";
1994     displayEdgesLambda("No incoming edges", "Connections to this node", information, d->targetEdges);
1995     information += "\n";
1996 
1997     return information;
1998 }
1999 
2000 void LakosEntity::recalculateRectangle()
2001 {
2002     if (!layoutUpdatesEnabled()) {
2003         return;
2004     }
2005 
2006     constexpr int TEXT_ADJUST = 20;
2007     if (!d->isExpanded) {
2008         const QRectF textRect = d->text->boundingRect().adjusted(-TEXT_ADJUST, // x1
2009                                                                  -TEXT_ADJUST, // y1
2010                                                                  TEXT_ADJUST, // x2
2011                                                                  TEXT_ADJUST); // y2
2012         setRectangle(textRect);
2013         layoutIgnoredItems();
2014         return;
2015     }
2016 
2017     // we can't use childrenBoundingRect as it takes in consideration the size of
2018     // the children of the children, even if they are hidden.
2019     QRectF childRect;
2020     for (auto *child : lakosEntities()) {
2021         if (!child->isVisible()) {
2022             continue;
2023         }
2024 
2025         QRectF thisChild = child->boundingRect();
2026         QPointF thisChildPoint = child->mapToParent(child->boundingRect().center());
2027 
2028         if (child->isExpanded()) {
2029             // We need to take in consideratio nthe text size at the bottom.
2030             thisChild.setHeight(thisChild.height() + TEXT_ADJUST);
2031         }
2032 
2033         thisChild.moveCenter(thisChildPoint);
2034         childRect = childRect.united(thisChild);
2035     }
2036 
2037     const int border =
2038         d->lakosChildren.size() <= 1 ? ONE_CHILD_BORDER : static_cast<int>(d->text->boundingRect().height());
2039     setRectangle(childRect.adjusted(-border, -border, border, border));
2040     layoutIgnoredItems();
2041 }
2042 
2043 // ONE LINERS.
2044 QList<LakosEntity *> LakosEntity::lakosEntities() const
2045 {
2046     return d->lakosChildren;
2047 }
2048 
2049 int LakosEntity::type() const
2050 {
2051     return Type;
2052 }
2053 
2054 std::string LakosEntity::name() const
2055 {
2056     return d->name;
2057 }
2058 
2059 long long LakosEntity::shortId() const
2060 {
2061     return d->shortId;
2062 }
2063 
2064 std::string LakosEntity::qualifiedName() const
2065 {
2066     return d->qualifiedName;
2067 }
2068 
2069 std::string LakosEntity::colorId() const
2070 {
2071     return d->colorId;
2072 }
2073 
2074 QColor LakosEntity::color() const
2075 {
2076     return d->color;
2077 }
2078 
2079 std::vector<std::shared_ptr<EdgeCollection>>& LakosEntity::edgesCollection() const
2080 {
2081     return d->edges;
2082 }
2083 
2084 bool LakosEntity::layoutUpdatesEnabled() const
2085 {
2086     return d->layoutUpdatesEnabled;
2087 }
2088 
2089 std::string LakosEntity::tooltipString() const
2090 {
2091     return d->toolTip;
2092 }
2093 
2094 const std::string& LakosEntity::uniqueIdStr() const
2095 {
2096     return d->id;
2097 }
2098 
2099 [[nodiscard]] lvtshr::UniqueId LakosEntity::uniqueId() const
2100 {
2101     return {instanceType(), d->shortId};
2102 }
2103 
2104 // TODO: This is being used only on the GraphicsScene on a single bit:
2105 //  mutEntity->setIsBranch(qtcEntity->highlighted());
2106 //  and there's no actual meaning of 'highlithed' at all.
2107 //  either we fix this (rename for a correct meaning) or remove it.
2108 bool LakosEntity::highlighted() const
2109 {
2110     return d->highlighted;
2111 }
2112 
2113 bool LakosEntity::isMainEntity() const
2114 {
2115     return d->isMainNode;
2116 }
2117 
2118 void LakosEntity::setRelationRedundant(const std::shared_ptr<EdgeCollection>& edgeCollection)
2119 {
2120     d->redundantRelations.push_back(edgeCollection);
2121 }
2122 
2123 void LakosEntity::resetRedundantRelations()
2124 {
2125     for (const auto& ec : d->redundantRelations) {
2126         ec->setRedundant(false);
2127     }
2128 
2129     d->redundantRelations.clear();
2130 }
2131 
2132 const std::vector<std::shared_ptr<EdgeCollection>>& LakosEntity::redundantRelationshipsCollection() const
2133 {
2134     return d->redundantRelations;
2135 }
2136 
2137 std::vector<std::shared_ptr<EdgeCollection>>& LakosEntity::targetCollection() const
2138 {
2139     return d->targetEdges;
2140 }
2141 
2142 const lvtshr::LoaderInfo& LakosEntity::loaderInfo() const
2143 {
2144     return d->loaderInfo;
2145 }
2146 
2147 bool LakosEntity::isCovered() const
2148 {
2149     return d->isOpaque;
2150 }
2151 
2152 bool LakosEntity::isExpanded() const
2153 {
2154     return d->isExpanded;
2155 }
2156 
2157 void LakosEntity::addTargetCollection(const std::shared_ptr<EdgeCollection>& collection)
2158 {
2159     d->targetEdges.push_back(collection);
2160 }
2161 
2162 void LakosEntity::ignoreItemOnLayout(QGraphicsItem *item)
2163 {
2164     d->ignoredItems.push_back(item);
2165 }
2166 
2167 QList<LakosEntity *> LakosEntity::parentHierarchy() const
2168 {
2169     QList<LakosEntity *> parents;
2170     QGraphicsItem *temp = parentItem();
2171     while (temp) {
2172         auto *pEntity = qgraphicsitem_cast<LakosEntity *>(temp);
2173         assert(pEntity);
2174         parents.prepend(pEntity);
2175         temp = temp->parentItem();
2176     }
2177     return parents;
2178 }
2179 
2180 void LakosEntity::updateZLevel()
2181 {
2182     setZValue(isSelected() ? 100 : 1);
2183 }
2184 
2185 void LakosEntity::levelizationLayout(LevelizationLayoutType type, int direction, std::optional<QPointF> moveToPosition)
2186 {
2187     auto entityToLevel = childrenLevels();
2188     for (auto [entity, level] : entityToLevel) {
2189         if (!entity->d->forceHideLevelNr) {
2190             entity->d->levelNr->setText(QString::number(level + 1));
2191         }
2192         entity->layoutIgnoredItems();
2193     }
2194 
2195     runLevelizationLayout(entityToLevel,
2196                           {type,
2197                            direction,
2198                            Preferences::spaceBetweenLevels(),
2199                            Preferences::spaceBetweenSublevels(),
2200                            Preferences::spaceBetweenEntities(),
2201                            Preferences::maxEntitiesPerLevel()});
2202 
2203     recalculateRectangle();
2204     if (moveToPosition) {
2205         setPos(*moveToPosition);
2206     }
2207     recursiveEdgeRelayout();
2208     auto *parent = qgraphicsitem_cast<LakosEntity *>(this->parentItem());
2209     while (parent) {
2210         parent->recalculateRectangle();
2211         parent = qgraphicsitem_cast<LakosEntity *>(parent->parentItem());
2212     }
2213 }
2214 
2215 void LakosEntity::truncateTitle(EllipsisTextItem::Truncate v)
2216 {
2217     d->text->truncate(v);
2218 }
2219 
2220 void LakosEntity::forceHideLevelNumbers()
2221 {
2222     d->forceHideLevelNr = true;
2223     d->levelNr->setVisible(false);
2224 }
2225 
2226 QJsonObject LakosEntity::toJson() const
2227 {
2228     const auto rect = boundingRect();
2229     const auto children = lakosEntities();
2230 
2231     QJsonArray childrenJson;
2232     for (LakosEntity *childEntity : children) {
2233         childrenJson.append(childEntity->toJson());
2234     }
2235 
2236     QJsonObject posObj = {
2237         {"x", pos().x()},
2238         {"y", pos().y()},
2239     };
2240 
2241     QJsonObject jsonRect = {
2242         {"x", rect.x()},
2243         {"y", rect.y()},
2244         {"width", rect.width()},
2245         {"height", rect.height()},
2246     };
2247 
2248     QJsonObject thisEntity = {
2249         {"pos", posObj},
2250         {"rect", jsonRect},
2251         {"qualifiedName", QString::fromStdString(qualifiedName())},
2252         {"children", childrenJson},
2253         {"expanded", isExpanded()},
2254         {"covered", isCovered()},
2255     };
2256 
2257     return thisEntity;
2258 }
2259 
2260 void LakosEntity::fromJson(const QJsonObject& obj)
2261 {
2262     const QJsonObject rectObj = obj["rect"].toObject();
2263     setRectangle(QRectF(rectObj["x"].toDouble(),
2264                         rectObj["y"].toDouble(),
2265                         rectObj["width"].toDouble(),
2266                         rectObj["height"].toDouble()));
2267 
2268     // After loading all elements, we need to cover / shrink if needed.
2269     if (obj["expanded"].toBool()) {
2270         expand(QtcUtil::CreateUndoAction::e_No);
2271     } else {
2272         shrink(QtcUtil::CreateUndoAction::e_No);
2273     }
2274 
2275     if (obj["covered"].toBool()) {
2276         hideContent(LakosEntity::ToggleContentBehavior::Single, QtcUtil::CreateUndoAction::e_No);
2277     }
2278 }
2279 
2280 void LakosEntity::setColor(const QColor& color)
2281 {
2282     d->color = color;
2283     updateBackground();
2284 }
2285 
2286 // cppcheck-suppress constParameter // This parameter cannot be marked as const, but cppcheck thinks it can. Marking it
2287 // as const wouldn't compile.
2288 void LakosEntity::setPluginManager(Codethink::lvtplg::PluginManager& pm)
2289 {
2290     d->pluginManager = pm;
2291 }
2292 
2293 } // end namespace Codethink::lvtqtc