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

0001 // ct_lvtqtc_graphicsview.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_graphicsview.h>
0021 
0022 #include <ct_lvtprj_projectfile.h>
0023 #include <ct_lvtqtc_graphicsscene.h>
0024 #include <ct_lvtqtc_lakosentity.h>
0025 #include <ct_lvtqtc_lakosrelation.h>
0026 #include <ct_lvtqtc_minimap.h>
0027 #include <ct_lvtqtc_tooltip.h>
0028 #include <ct_lvtqtc_undo_manager.h>
0029 
0030 #include <QBrush>
0031 #include <QCursor>
0032 #include <QDropEvent>
0033 #include <QMenu>
0034 #include <QMimeData>
0035 #include <QPainter>
0036 #include <QPainterPath>
0037 #include <QScrollBar>
0038 #include <QToolTip>
0039 #include <QTransform>
0040 
0041 #include <qnamespace.h>
0042 #include <set>
0043 
0044 #include <preferences.h>
0045 
0046 using namespace Codethink::lvtldr;
0047 
0048 namespace Codethink::lvtqtc {
0049 
0050 struct GraphicsView::Private {
0051     QString fullyQualifiedName;
0052     GraphicsScene *scene = nullptr;
0053     Minimap *minimap = nullptr;
0054     ToolTipItem *toolTipItem = nullptr;
0055     int zoomFactor = 100;
0056     bool initialized = false;
0057     ITool *currentTool = nullptr;
0058     UndoManager *undoManager = nullptr;
0059 
0060     struct {
0061         bool isActive = false;
0062         QPoint start;
0063         QPoint end;
0064         const QPen pen = QPen(QBrush(Qt::black), 1, Qt::PenStyle::DashLine);
0065         const QBrush brush = QBrush(QColor(0, 0, 0, 30));
0066     } multiSelect;
0067 
0068     bool isMultiDragging = false;
0069 
0070     struct {
0071         QString text;
0072         lvtshr::SearchMode mode = lvtshr::SearchMode::CaseInsensitive;
0073         int current = 0;
0074         QList<LakosEntity *> searchResult;
0075     } search;
0076 };
0077 
0078 // --------------------------------------------
0079 // class GraphicsView
0080 // --------------------------------------------
0081 
0082 GraphicsView::GraphicsView(NodeStorage& nodeStorage, lvtprj::ProjectFile const& projectFile, QWidget *parent):
0083     QGraphicsView(parent), d(std::make_unique<GraphicsView::Private>())
0084 {
0085     d->scene = new GraphicsScene(nodeStorage, projectFile, this);
0086     setScene(d->scene);
0087     setBackgroundBrush(QBrush(Qt::white));
0088     setRenderHints(QPainter::RenderHint::Antialiasing | QPainter::RenderHint::TextAntialiasing
0089                    | QPainter::RenderHint::SmoothPixmapTransform);
0090 
0091     connect(d->scene, &GraphicsScene::graphLoadStarted, this, &GraphicsView::graphLoadStarted);
0092     connect(d->scene, &GraphicsScene::graphLoadFinished, this, &GraphicsView::graphLoadFinished);
0093 
0094     setVerticalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAlwaysOn);
0095     setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAlwaysOn);
0096     d->minimap = new Minimap(d->scene, this);
0097     d->minimap->setVisible(false);
0098 
0099     connect(d->minimap, &Minimap::focusSceneAt, this, [this](const QPointF& p) {
0100         centerOn(p);
0101     });
0102 
0103     d->toolTipItem = new ToolTipItem(this);
0104     d->toolTipItem->addToolTip("Is A");
0105     d->toolTipItem->addToolTip("Uses in the Implementation");
0106     d->toolTipItem->addToolTip("Uses in the Interface");
0107     d->toolTipItem->move(10, 110);
0108     d->toolTipItem->setVisible(false);
0109 
0110     connect(verticalScrollBar(), &QScrollBar::valueChanged, this, [this] {
0111         d->minimap->setSceneRect(mapToScene(rect()).boundingRect());
0112     });
0113 
0114     connect(horizontalScrollBar(), &QScrollBar::valueChanged, this, [this] {
0115         d->minimap->setSceneRect(mapToScene(rect()).boundingRect());
0116     });
0117 
0118     connect(Preferences::self(), &Preferences::backgroundColorChanged, this, [this] {
0119         setBackgroundBrush(QBrush(Preferences::backgroundColor()));
0120     });
0121     setBackgroundBrush(QBrush(Preferences::backgroundColor()));
0122 
0123     setCacheMode(QGraphicsView::CacheNone);
0124     setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
0125     setAcceptDrops(true);
0126     setTransformationAnchor(ViewportAnchor::AnchorUnderMouse);
0127 }
0128 
0129 GraphicsView::~GraphicsView() noexcept = default;
0130 
0131 void GraphicsView::undoCommandReceived(QUndoCommand *command)
0132 {
0133     if (!d->undoManager) {
0134         return;
0135     }
0136     d->undoManager->addUndoCommand(command);
0137     Q_EMIT onUndoCommandReceived(this, command);
0138 }
0139 
0140 void GraphicsView::toggleMinimap(bool toggle)
0141 {
0142     d->minimap->setVisible(toggle);
0143     Preferences::setShowMinimap(toggle);
0144 }
0145 
0146 void GraphicsView::toggleLegend(bool toggle)
0147 {
0148     d->toolTipItem->setVisible(toggle);
0149     Preferences::setShowLegend(toggle);
0150 }
0151 
0152 void GraphicsView::setUndoManager(UndoManager *undoManager)
0153 {
0154     d->undoManager = undoManager;
0155 }
0156 
0157 void GraphicsView::debugVisibleScreen()
0158 {
0159     qDebug() << d->scene->toJson();
0160 }
0161 
0162 void GraphicsView::setColorManagement(const std::shared_ptr<lvtclr::ColorManagement>& colorManagement)
0163 {
0164     assert(colorManagement);
0165     d->scene->setColorManagement(colorManagement);
0166 }
0167 
0168 void GraphicsView::fitAllInView()
0169 {
0170     // Note: Do not use `d->scene->itemsBoundingRect()` to fit all in view, because it takes in consideration hidden
0171     // items, and we do not update the position of hidden items. Thus, using the aforementioned method will compute a
0172     // wrong rect for the view.
0173     auto boundingRect = QRectF{};
0174     for (auto const& entity : d->scene->allEntities()) {
0175         if (entity->isVisible()) {
0176             boundingRect |= entity->mapRectToScene(entity->boundingRect());
0177         }
0178     }
0179     fitRectInView(boundingRect);
0180 }
0181 
0182 void GraphicsView::fitItemInView(QGraphicsItem *entity)
0183 {
0184     auto w = entity->boundingRect().width();
0185     auto h = entity->boundingRect().height();
0186     auto x = entity->scenePos().x() - w / 2.;
0187     auto y = entity->scenePos().y() - h / 2.;
0188 
0189     auto r = QRectF{x, y, w, h};
0190     fitRectInView(r);
0191 }
0192 
0193 void GraphicsView::fitRectInView(QRectF const& r)
0194 {
0195     constexpr qreal PAD = 100;
0196     constexpr qreal PREFERRED_ZOOM_FACTOR = 100; // percent
0197 
0198     QRectF boundingRect = r.adjusted(-PAD / 2, -PAD / 2, PAD / 2, PAD / 2);
0199     fitInView(boundingRect, Qt::AspectRatioMode::KeepAspectRatio);
0200     calculateCurrentZoomFactor();
0201 
0202     // If the zoom factor is higher than the preferred factor, use the preferred factor to avoid zooming in too much.
0203     if (d->zoomFactor > PREFERRED_ZOOM_FACTOR) {
0204         setZoomFactor(PREFERRED_ZOOM_FACTOR);
0205     }
0206 }
0207 
0208 void GraphicsView::wheelEvent(QWheelEvent *event)
0209 {
0210     // Handle Zoom
0211     auto zoomModifier = Preferences::zoomModifier();
0212     if (event->modifiers() & zoomModifier || zoomModifier == Qt::KeyboardModifier::NoModifier) {
0213         if (event->angleDelta().y() > 0) {
0214             setZoomFactor(d->zoomFactor + 2);
0215         } else {
0216             setZoomFactor(d->zoomFactor - 2);
0217         }
0218         return;
0219     }
0220 
0221     QGraphicsView::wheelEvent(event);
0222 }
0223 
0224 void GraphicsView::setZoomFactor(int zoomFactorInPercent)
0225 {
0226     if (d->zoomFactor == zoomFactorInPercent) {
0227         return;
0228     }
0229 
0230     // don't let the zoom factor go below 1%:
0231     // 0% looks like a divide by zero and displays nothing
0232     // negative zoom flips the diagram
0233     constexpr int zoomLimit = 1;
0234     if (zoomFactorInPercent < zoomLimit) {
0235         zoomFactorInPercent = zoomLimit;
0236     }
0237 
0238     resetTransform();
0239     double scaleFactor = zoomFactorInPercent / 100.0;
0240     scale(scaleFactor, scaleFactor);
0241     d->zoomFactor = zoomFactorInPercent;
0242     Q_EMIT zoomFactorChanged(zoomFactorInPercent);
0243 
0244     d->scene->setBlockNodeResizeOnHover(zoomFactorInPercent >= 100);
0245 }
0246 
0247 int GraphicsView::zoomFactor() const
0248 {
0249     return d->zoomFactor;
0250 }
0251 
0252 void GraphicsView::calculateCurrentZoomFactor()
0253 {
0254     d->zoomFactor = static_cast<int>(transform().m11() * 100);
0255     Q_EMIT zoomFactorChanged(d->zoomFactor);
0256 }
0257 
0258 void GraphicsView::mouseMoveEvent(QMouseEvent *event)
0259 {
0260     assert(event);
0261 
0262     const bool mouseHasNewPosition = d->multiSelect.end != event->pos();
0263 
0264     if (d->multiSelect.isActive && mouseHasNewPosition) {
0265         d->multiSelect.end = event->pos();
0266         auto selection = QRect(d->multiSelect.start, d->multiSelect.end).normalized();
0267         QSet<LakosEntity *> currentSelection;
0268         static QSet<LakosEntity *> oldSelection;
0269 
0270         // Fill currentSelection with all LakosEntities in selection
0271         const auto itemsInSelection = items(selection, Qt::IntersectsItemBoundingRect);
0272         for (QGraphicsItem *item : itemsInSelection) {
0273             if (auto *lEntity = qgraphicsitem_cast<LakosEntity *>(item)) {
0274                 currentSelection.insert(lEntity);
0275             }
0276         }
0277 
0278         // Update selection
0279         for (const auto lEntity : currentSelection) {
0280             if (!lEntity->isSelected()) {
0281                 lEntity->setSelected(true);
0282             }
0283         }
0284 
0285         auto toUnselect = oldSelection;
0286         toUnselect.subtract(currentSelection);
0287         for (const auto lEntity : toUnselect) {
0288             lEntity->setSelected(false);
0289         }
0290 
0291         if (oldSelection != currentSelection) {
0292             std::deque<LakosianNode *> selectedNodes;
0293             for (const auto& lEntity : currentSelection) {
0294                 selectedNodes.push_back(lEntity->internalNode());
0295             }
0296             Q_EMIT newSelectionMade(selectedNodes);
0297             oldSelection = currentSelection;
0298         }
0299 
0300         viewport()->update();
0301     }
0302 
0303     if (d->isMultiDragging && mouseHasNewPosition) {
0304         for (const auto& entity : d->scene->selectedEntities()) {
0305             entity->doDrag(mapToScene(event->pos()));
0306         }
0307     }
0308 
0309     QList<QGraphicsItem *> underMouse = items(event->pos());
0310 
0311     d->toolTipItem->clear();
0312     for (QGraphicsItem *item : underMouse) {
0313         if (auto *relation = qgraphicsitem_cast<LakosRelation *>(item)) {
0314             QString label = QString::fromStdString(relation->legendText());
0315             d->toolTipItem->addToolTip(label);
0316             break;
0317         }
0318         if (auto *entity = qgraphicsitem_cast<LakosEntity *>(item)) {
0319             QString label = QString::fromStdString(entity->legendText());
0320             d->toolTipItem->addToolTip(label);
0321             break;
0322         }
0323     }
0324     QGraphicsView::mouseMoveEvent(event);
0325 }
0326 
0327 void GraphicsView::zoomIntoRect(const QPoint& topLeft, const QPoint& bottomRight)
0328 {
0329     auto mapTopLeft = mapToScene(topLeft);
0330     auto mapBottomRight = mapToScene(bottomRight);
0331     fitInView(QRectF(mapTopLeft, mapBottomRight), Qt::AspectRatioMode::KeepAspectRatio);
0332 
0333     d->zoomFactor = static_cast<int>(transform().m11() * 100);
0334     // m11 is the matrix coordinate that takes care of the
0335     // scale factor.
0336 
0337     Q_EMIT zoomFactorChanged(d->zoomFactor);
0338 }
0339 
0340 void GraphicsView::keyPressEvent(QKeyEvent *event)
0341 {
0342     if (event->modifiers() & Preferences::panModifier()) {
0343         setDragMode(QGraphicsView::DragMode::ScrollHandDrag);
0344         event->accept();
0345         return;
0346     }
0347 
0348     QGraphicsView::keyPressEvent(event);
0349 }
0350 
0351 void GraphicsView::keyReleaseEvent(QKeyEvent *event)
0352 {
0353     Qt::Key modifier = Qt::Key_Alt;
0354     switch (Preferences::panModifier()) {
0355     case Qt::AltModifier:
0356         modifier = Qt::Key_Alt;
0357         break;
0358     case Qt::ControlModifier:
0359         modifier = Qt::Key_Control;
0360         break;
0361     case Qt::ShiftModifier:
0362         modifier = Qt::Key_Shift;
0363         break;
0364     default:
0365         modifier = Qt::Key_Alt;
0366     }
0367 
0368     if (event->key() == modifier) {
0369         setDragMode(QGraphicsView::DragMode::NoDrag);
0370         event->accept();
0371     }
0372 
0373     QGraphicsView::keyReleaseEvent(event);
0374 }
0375 
0376 namespace {
0377 template<typename T>
0378 T castUpToParent(QGraphicsItem *item)
0379 {
0380     if (!item) {
0381         return nullptr;
0382     }
0383     if (auto entity = dynamic_cast<T>(item)) {
0384         return entity;
0385     }
0386     if (item->parentItem() == nullptr) {
0387         return nullptr;
0388     }
0389     return castUpToParent<T>(item->parentItem());
0390 }
0391 } // namespace
0392 
0393 void GraphicsView::mousePressEvent(QMouseEvent *event)
0394 {
0395     if (Preferences::enableDebugOutput()) {
0396         qDebug() << "GraphicsView mousePressEvent";
0397     }
0398 
0399     if (event->button() == Qt::ForwardButton) {
0400         Q_EMIT requestNext();
0401         return;
0402     }
0403     if (event->button() == Qt::BackButton) {
0404         Q_EMIT requestPrevious();
0405         return;
0406     }
0407 
0408     if (event->modifiers() & Preferences::panModifier()
0409         || Preferences::panModifier() == Qt::KeyboardModifier::NoModifier) {
0410         setDragMode(QGraphicsView::DragMode::ScrollHandDrag);
0411     } else {
0412         if (!itemAt(event->pos())) {
0413             if (event->button() == Qt::LeftButton) {
0414                 d->multiSelect.start = event->pos();
0415                 d->multiSelect.end = event->pos();
0416                 d->multiSelect.isActive = true;
0417             } else {
0418                 d->multiSelect.isActive = false;
0419             }
0420         }
0421 
0422         if (event->button() == Qt::LeftButton) {
0423             if (QGraphicsItem *item = itemAt(event->pos())) {
0424                 if (d->scene->selectedEntities().size() > 1) {
0425                     if (const auto *entity = castUpToParent<LakosEntity *>(item)) {
0426                         if (entity->isSelected()) {
0427                             d->isMultiDragging = true;
0428                         }
0429                     }
0430                 }
0431             }
0432         }
0433 
0434         if (d->isMultiDragging) {
0435             for (auto& entity : d->scene->selectedEntities()) {
0436                 entity->startDrag(mapToScene(event->pos()));
0437             }
0438         }
0439     }
0440 
0441     // Qt loses the selection if we right click. So we need to
0442     // store the selection, run the virtual call, then restore
0443     // the selection.
0444     if (event->button() == Qt::RightButton) {
0445         const auto selectedItems = scene()->selectedItems();
0446         QGraphicsView::mousePressEvent(event);
0447         for (auto *item : selectedItems) {
0448             item->setSelected(true);
0449         }
0450     } else {
0451         QGraphicsView::mousePressEvent(event);
0452     }
0453 }
0454 
0455 void GraphicsView::mouseReleaseEvent(QMouseEvent *event)
0456 {
0457     if (Preferences::enableDebugOutput()) {
0458         qDebug() << "GraphicsView mouseReleaseEvent";
0459     }
0460 
0461     // update selectedEntities using entity->toggleSelection()
0462     for (const auto& entity : d->scene->allEntities()) {
0463         const bool isSelected = entity->isSelected();
0464         const auto selectedEntities = d->scene->selectedEntities();
0465         const bool isContainedInSelectedEntities =
0466             std::find(selectedEntities.begin(), selectedEntities.end(), entity) != selectedEntities.end();
0467 
0468         if ((isSelected && !isContainedInSelectedEntities) || (!isSelected && isContainedInSelectedEntities)) {
0469             entity->toggleSelection();
0470         }
0471     }
0472 
0473     d->multiSelect.isActive = false;
0474 
0475     if (d->isMultiDragging) {
0476         for (const auto& entity : d->scene->selectedEntities()) {
0477             entity->endDrag(mapToScene(event->pos()));
0478         }
0479         d->isMultiDragging = false;
0480     }
0481 
0482     viewport()->update();
0483 
0484     setDragMode(QGraphicsView::DragMode::NoDrag);
0485 
0486     QGraphicsView::mouseReleaseEvent(event);
0487 }
0488 
0489 void GraphicsView::showEvent(QShowEvent *event)
0490 {
0491     if (!d->initialized) {
0492         d->initialized = true;
0493         fitAllInView();
0494     }
0495     QGraphicsView::showEvent(event);
0496 }
0497 
0498 void GraphicsView::setCurrentTool(ITool *tool)
0499 {
0500     d->currentTool = tool;
0501 }
0502 
0503 ITool *GraphicsView::currentTool() const
0504 {
0505     return d->currentTool;
0506 }
0507 
0508 void GraphicsView::drawForeground(QPainter *painter, const QRectF& rect)
0509 {
0510     if (d->currentTool) {
0511         d->currentTool->drawForeground(painter, rect);
0512     }
0513 
0514     if (d->multiSelect.isActive) {
0515         painter->save();
0516         painter->setWorldMatrixEnabled(false);
0517         painter->setPen(d->multiSelect.pen);
0518         painter->setBrush(d->multiSelect.brush);
0519         painter->drawRect(QRect(d->multiSelect.start, d->multiSelect.end));
0520         painter->setWorldMatrixEnabled(true);
0521         painter->restore();
0522     }
0523 
0524     if (d->search.text.length() == 0) {
0525         return;
0526     }
0527 
0528     // Semi translucent yellow
0529     const QColor foundColor(0x55, 0xff, 0xff, 0x99);
0530 
0531     // semi translucent blue
0532     const QColor currentColor(0x55, 0x38, 0xB0, 0xDE);
0533 
0534     // semi translucent black
0535     const QColor foreground(155, 155, 155, 220);
0536 
0537     QPainterPath path;
0538     path.addRect(rect);
0539 
0540     painter->save();
0541     // draw the whole area with a blackish translucent brush.
0542 
0543     int idx = 1;
0544     std::set<LakosEntity *> parentsNotInSearch;
0545 
0546     for (LakosEntity *entity : qAsConst(d->search.searchResult)) {
0547         auto scenePos = entity->sceneBoundingRect().center();
0548         if (rect.contains(scenePos)) {
0549             QRectF rectHole = entity->boundingRect();
0550 
0551             // The LakosEntities have the center on the middle, we need to fix
0552             // the rect.
0553             rectHole.moveTo(QPointF(scenePos.x() - rectHole.width() / 2, scenePos.y() - rectHole.height() / 2));
0554             painter->setBrush(QBrush(idx == d->search.current ? currentColor : foundColor));
0555 
0556             // We can't paint the parent item unless it's the currently selected
0557             // one, because it covers all the child items that could have been
0558             // selected.
0559             if (!entity->parentItem() || idx == d->search.current) {
0560                 painter->drawRect(rectHole);
0561             }
0562 
0563             // Add the parents that are not found on a set so it's faster to
0564             // look them up for the subsequent children. We assume that there
0565             // are way more children than parents on the searches, since `bal`
0566             // will match the package, and all the children. looking the parent
0567             // for every child on a vector can be slow.
0568             auto *parentEntity = dynamic_cast<LakosEntity *>(entity->parentItem());
0569             auto it = parentsNotInSearch.find(parentEntity);
0570             if (it == std::end(parentsNotInSearch)) {
0571                 if (!d->search.searchResult.contains(parentEntity)) {
0572                     it = parentsNotInSearch.insert(parentEntity).first;
0573                 }
0574             }
0575 
0576             if (!entity->parentItem() || it != std::end(parentsNotInSearch)) {
0577                 path.addRect(rectHole);
0578             }
0579         }
0580 
0581         idx += 1;
0582     }
0583     painter->restore();
0584 
0585     painter->setBrush(QBrush(foreground));
0586     painter->drawPath(path);
0587 }
0588 
0589 void GraphicsView::setSearchMode(lvtshr::SearchMode mode)
0590 {
0591     d->search.mode = mode;
0592     doSearch();
0593 }
0594 
0595 void GraphicsView::setSearchString(const QString& search)
0596 {
0597     d->search.text = search;
0598     invalidateScene();
0599     doSearch();
0600 }
0601 
0602 void GraphicsView::highlightedNextSearchElement()
0603 {
0604     d->search.current = d->search.current % d->search.searchResult.size() + 1;
0605 
0606     Q_EMIT currentSearchItemHighlighted(d->search.current);
0607 
0608     // We use 1 based index because this is for user facing entries, we need
0609     // to subtract -1 here.
0610 
0611     LakosEntity *entity = d->search.searchResult[d->search.current - 1];
0612     ensureVisible(entity);
0613 
0614     viewport()->update();
0615 }
0616 
0617 void GraphicsView::highlightedPreviousSearchElement()
0618 {
0619     d->search.current = d->search.current == 1 ? d->search.searchResult.size() : d->search.current - 1;
0620 
0621     Q_EMIT currentSearchItemHighlighted(d->search.current);
0622 
0623     // We use 1 based index because this is for user facing entries, we need
0624     // to subtract -1 here.
0625     LakosEntity *entity = d->search.searchResult[d->search.current - 1];
0626     ensureVisible(entity);
0627 
0628     viewport()->update();
0629 }
0630 
0631 void GraphicsView::doSearch()
0632 {
0633     d->search.searchResult.clear();
0634     std::vector<LakosEntity *> entities = d->scene->allEntities();
0635     const QString searchText =
0636         d->search.mode == lvtshr::SearchMode::CaseInsensitive ? d->search.text.toLower() : d->search.text;
0637 
0638     if (searchText.size() == 0) {
0639         Q_EMIT searchTotal(0);
0640         Q_EMIT currentSearchItemHighlighted(0);
0641         viewport()->update();
0642         return;
0643     }
0644 
0645     for (LakosEntity *entity : entities) {
0646         if (!entity->isVisible()) {
0647             continue;
0648         }
0649 
0650         // QString allow us to use some nice things that std::string lacks.
0651         const QString nameEntity = d->search.mode == lvtshr::SearchMode::CaseInsensitive
0652             ? QString::fromStdString(entity->name()).toLower()
0653             : QString::fromStdString(entity->name());
0654 
0655         if (nameEntity.contains(searchText)) {
0656             d->search.searchResult.append(entity);
0657         };
0658     }
0659 
0660     if (d->search.searchResult.empty()) {
0661         Q_EMIT searchTotal(0);
0662         Q_EMIT currentSearchItemHighlighted(0);
0663         viewport()->update();
0664         return;
0665     }
0666 
0667     d->search.current = 1;
0668     Q_EMIT searchTotal(d->search.searchResult.size());
0669     Q_EMIT currentSearchItemHighlighted(d->search.current);
0670     viewport()->update();
0671 }
0672 
0673 void GraphicsView::contextMenuEvent(QContextMenuEvent *event)
0674 {
0675     QMenu menu;
0676     menu.setToolTipsVisible(true);
0677 
0678     QMenu *debugMenu = nullptr;
0679 
0680     if (Preferences::enableSceneContextMenu()) {
0681         debugMenu = new QMenu(tr("Debug tools"));
0682         debugMenu->setToolTipsVisible(true);
0683     }
0684 
0685     QGraphicsItem *item = itemAt(event->pos());
0686     if (!item) {
0687         d->scene->populateMenu(menu, debugMenu);
0688     } else {
0689         if (auto *relation = dynamic_cast<LakosRelation *>(item)) {
0690             relation->populateMenu(menu, debugMenu);
0691         } else if (auto *entity = castUpToParent<LakosEntity *>(item)) {
0692             entity->populateMenu(menu, debugMenu, mapToScene(event->pos()));
0693         }
0694     }
0695 
0696     if (debugMenu && !debugMenu->isEmpty()) {
0697         menu.addMenu(debugMenu);
0698     }
0699 
0700     if (menu.isEmpty()) {
0701         return;
0702     }
0703     menu.exec(event->globalPos());
0704 }
0705 
0706 void GraphicsView::dragEnterEvent(QDragEnterEvent *event)
0707 {
0708     if (event->mimeData()->hasFormat("codevis/qualifiednames")) {
0709         event->acceptProposedAction();
0710     }
0711 }
0712 
0713 void GraphicsView::dragMoveEvent(QDragMoveEvent *event)
0714 {
0715     event->acceptProposedAction();
0716 }
0717 
0718 void GraphicsView::dropEvent(QDropEvent *event)
0719 {
0720     const QString qualNames = event->mimeData()->data("codevis/qualifiednames");
0721 #ifdef KDE_FRAMEWORKS_IS_OLD
0722     QStringList qualNameList = qualNames.split(";");
0723     qualNameList.removeAll(QString(";"));
0724 #else
0725     const QStringList qualNameList = qualNames.split(";", Qt::SplitBehaviorFlags::SkipEmptyParts);
0726 #endif
0727 
0728     for (const auto& qualName : qualNameList) {
0729         d->scene->loadEntityByQualifiedName(qualName, mapToScene(event->pos()));
0730     }
0731 
0732     if (qualNameList.size() > 1) {
0733         d->scene->reLayout();
0734     }
0735 }
0736 
0737 GraphicsScene *GraphicsView::graphicsScene() const
0738 {
0739     return d->scene;
0740 }
0741 
0742 void GraphicsView::setPluginManager(Codethink::lvtplg::PluginManager& pm)
0743 {
0744     d->scene->setPluginManager(pm);
0745 }
0746 
0747 } // namespace Codethink::lvtqtc