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