#include "FlowView.hpp" #include #include #include #include #include #include #include #include #include #include #include "FlowScene.hpp" #include "DataModelRegistry.hpp" #include "Node.hpp" #include "NodeGraphicsObject.hpp" #include "ConnectionGraphicsObject.hpp" #include "StyleCollection.hpp" using QtNodes::FlowView; using QtNodes::FlowScene; FlowView:: FlowView(QWidget *parent) : QGraphicsView(parent) , _clearSelectionAction(Q_NULLPTR) , _deleteSelectionAction(Q_NULLPTR) , _scene(Q_NULLPTR) { setDragMode(QGraphicsView::ScrollHandDrag); setRenderHint(QPainter::Antialiasing); auto const &flowViewStyle = StyleCollection::flowViewStyle(); setBackgroundBrush(flowViewStyle.BackgroundColor); //setViewportUpdateMode(QGraphicsView::FullViewportUpdate); //setViewportUpdateMode(QGraphicsView::MinimalViewportUpdate); setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setTransformationAnchor(QGraphicsView::AnchorUnderMouse); setCacheMode(QGraphicsView::CacheBackground); //setViewport(new QGLWidget(QGLFormat(QGL::SampleBuffers))); } FlowView:: FlowView(FlowScene *scene, QWidget *parent) : FlowView(parent) { setScene(scene); } QAction* FlowView:: clearSelectionAction() const { return _clearSelectionAction; } QAction* FlowView:: deleteSelectionAction() const { return _deleteSelectionAction; } void FlowView::setScene(FlowScene *scene) { _scene = scene; QGraphicsView::setScene(_scene); // setup actions delete _clearSelectionAction; _clearSelectionAction = new QAction(QStringLiteral("Clear Selection"), this); _clearSelectionAction->setShortcut(Qt::Key_Escape); connect(_clearSelectionAction, &QAction::triggered, _scene, &QGraphicsScene::clearSelection); addAction(_clearSelectionAction); delete _deleteSelectionAction; _deleteSelectionAction = new QAction(QStringLiteral("Delete Selection"), this); _deleteSelectionAction->setShortcut(Qt::Key_Delete); connect(_deleteSelectionAction, &QAction::triggered, this, &FlowView::deleteSelectedNodes); addAction(_deleteSelectionAction); } void FlowView:: contextMenuEvent(QContextMenuEvent *event) { if (itemAt(event->pos())) { QGraphicsView::contextMenuEvent(event); return; } QMenu modelMenu; auto skipText = QStringLiteral("skip me"); //Add filterbox to the context menu auto *txtBox = new QLineEdit(&modelMenu); txtBox->setPlaceholderText(QStringLiteral("Filter")); txtBox->setClearButtonEnabled(true); auto *txtBoxAction = new QWidgetAction(&modelMenu); txtBoxAction->setDefaultWidget(txtBox); modelMenu.addAction(txtBoxAction); //Add result treeview to the context menu auto *treeView = new QTreeWidget(&modelMenu); treeView->header()->close(); auto *treeViewAction = new QWidgetAction(&modelMenu); treeViewAction->setDefaultWidget(treeView); modelMenu.addAction(treeViewAction); QMap topLevelItems; for (auto const &cat : _scene->registry().categories()) { auto item = new QTreeWidgetItem(treeView); item->setText(0, cat); item->setData(0, Qt::UserRole, skipText); topLevelItems[cat] = item; } for (auto const &assoc : _scene->registry().registeredModelsCategoryAssociation()) { auto parent = topLevelItems[assoc.second]; auto item = new QTreeWidgetItem(parent); item->setText(0, assoc.first); item->setData(0, Qt::UserRole, assoc.first); } treeView->expandAll(); connect(treeView, &QTreeWidget::itemClicked, [&](QTreeWidgetItem *item, int) { QString modelName = item->data(0, Qt::UserRole).toString(); if (modelName == skipText) { return; } auto type = _scene->registry().create(modelName); if (type) { auto& node = _scene->createNode(std::move(type)); QPoint pos = event->pos(); QPointF posView = this->mapToScene(pos); node.nodeGraphicsObject().setPos(posView); _scene->nodePlaced(node); } else { qDebug() << "Model not found"; } modelMenu.close(); }); //Setup filtering connect(txtBox, &QLineEdit::textChanged, [&](const QString &text) { for (auto& topLvlItem : topLevelItems) { for (int i = 0; i < topLvlItem->childCount(); ++i) { auto child = topLvlItem->child(i); auto modelName = child->data(0, Qt::UserRole).toString(); const bool match = (modelName.contains(text, Qt::CaseInsensitive)); child->setHidden(!match); } } }); // make sure the text box gets focus so the user doesn't have to click on it txtBox->setFocus(); modelMenu.exec(event->globalPos()); } void FlowView:: wheelEvent(QWheelEvent *event) { QPoint delta = event->angleDelta(); if (delta.y() == 0) { event->ignore(); return; } double const d = delta.y() / std::abs(delta.y()); if (d > 0.0) scaleUp(); else scaleDown(); } void FlowView:: scaleUp() { double const step = 1.2; double const factor = std::pow(step, 1.0); QTransform t = transform(); if (t.m11() > 2.0) return; scale(factor, factor); } void FlowView:: scaleDown() { double const step = 1.2; double const factor = std::pow(step, -1.0); scale(factor, factor); } void FlowView:: deleteSelectedNodes() { // Delete the selected connections first, ensuring that they won't be // automatically deleted when selected nodes are deleted (deleting a node // deletes some connections as well) for (QGraphicsItem * item : _scene->selectedItems()) { if (auto c = qgraphicsitem_cast(item)) _scene->deleteConnection(c->connection()); } // Delete the nodes; this will delete many of the connections. // Selected connections were already deleted prior to this loop, otherwise // qgraphicsitem_cast(item) could be a use-after-free // when a selected connection is deleted by deleting the node. for (QGraphicsItem * item : _scene->selectedItems()) { if (auto n = qgraphicsitem_cast(item)) _scene->removeNode(n->node()); } } void FlowView:: keyPressEvent(QKeyEvent *event) { switch (event->key()) { case Qt::Key_Shift: setDragMode(QGraphicsView::RubberBandDrag); break; default: break; } QGraphicsView::keyPressEvent(event); } void FlowView:: keyReleaseEvent(QKeyEvent *event) { switch (event->key()) { case Qt::Key_Shift: setDragMode(QGraphicsView::ScrollHandDrag); break; default: break; } QGraphicsView::keyReleaseEvent(event); } void FlowView:: mousePressEvent(QMouseEvent *event) { QGraphicsView::mousePressEvent(event); if (event->button() == Qt::LeftButton) { _clickPos = mapToScene(event->pos()); } } void FlowView:: mouseMoveEvent(QMouseEvent *event) { QGraphicsView::mouseMoveEvent(event); if (scene()->mouseGrabberItem() == nullptr && event->buttons() == Qt::LeftButton) { // Make sure shift is not being pressed if ((event->modifiers() & Qt::ShiftModifier) == 0) { QPointF difference = _clickPos - mapToScene(event->pos()); setSceneRect(sceneRect().translated(difference.x(), difference.y())); } } } void FlowView:: drawBackground(QPainter* painter, const QRectF& r) { QGraphicsView::drawBackground(painter, r); auto drawGrid = [&](double gridStep) { QRect windowRect = rect(); QPointF tl = mapToScene(windowRect.topLeft()); QPointF br = mapToScene(windowRect.bottomRight()); double left = std::floor(tl.x() / gridStep - 0.5); double right = std::floor(br.x() / gridStep + 1.0); double bottom = std::floor(tl.y() / gridStep - 0.5); double top = std::floor (br.y() / gridStep + 1.0); // vertical lines for (int xi = int(left); xi <= int(right); ++xi) { QLineF line(xi * gridStep, bottom * gridStep, xi * gridStep, top * gridStep ); painter->drawLine(line); } // horizontal lines for (int yi = int(bottom); yi <= int(top); ++yi) { QLineF line(left * gridStep, yi * gridStep, right * gridStep, yi * gridStep ); painter->drawLine(line); } }; auto const &flowViewStyle = StyleCollection::flowViewStyle(); QBrush bBrush = backgroundBrush(); QPen pfine(flowViewStyle.FineGridColor, 1.0); painter->setPen(pfine); drawGrid(15); QPen p(flowViewStyle.CoarseGridColor, 1.0); painter->setPen(p); drawGrid(150); } void FlowView:: showEvent(QShowEvent *event) { _scene->setSceneRect(this->rect()); QGraphicsView::showEvent(event); } FlowScene * FlowView:: scene() { return _scene; }