From 0e0881e3ac9de7d361977606c1bc8eb15622b2b3 Mon Sep 17 00:00:00 2001 From: g-abilio Date: Thu, 7 Aug 2025 14:09:56 -0300 Subject: [PATCH 01/11] include freeze and unfreeze tools to nodeeditor --- .../QtNodes/internal/BasicGraphicsScene.hpp | 1 - include/QtNodes/internal/ConnectionState.hpp | 6 ++++++ .../QtNodes/internal/DataFlowGraphicsScene.hpp | 14 ++++++++++++++ src/DefaultConnectionPainter.cpp | 18 +++++++++++------- 4 files changed, 31 insertions(+), 8 deletions(-) diff --git a/include/QtNodes/internal/BasicGraphicsScene.hpp b/include/QtNodes/internal/BasicGraphicsScene.hpp index 83424c5d8..72a308578 100644 --- a/include/QtNodes/internal/BasicGraphicsScene.hpp +++ b/include/QtNodes/internal/BasicGraphicsScene.hpp @@ -17,7 +17,6 @@ #include #include - class QUndoStack; namespace QtNodes { diff --git a/include/QtNodes/internal/ConnectionState.hpp b/include/QtNodes/internal/ConnectionState.hpp index ddd31cdb6..d13738260 100644 --- a/include/QtNodes/internal/ConnectionState.hpp +++ b/include/QtNodes/internal/ConnectionState.hpp @@ -25,6 +25,7 @@ class NODE_EDITOR_PUBLIC ConnectionState ConnectionState(ConnectionGraphicsObject &cgo) : _cgo(cgo) , _hovered(false) + , _frozen(false) {} ConnectionState(ConnectionState const &) = delete; @@ -42,6 +43,9 @@ class NODE_EDITOR_PUBLIC ConnectionState bool hovered() const; void setHovered(bool hovered); + bool frozen() const { return _frozen; } + void setFrozen(bool frozen) { _frozen = frozen; } + public: /// Caches NodeId for further interaction. void setLastHoveredNode(NodeId const nodeId); @@ -56,5 +60,7 @@ class NODE_EDITOR_PUBLIC ConnectionState bool _hovered; NodeId _lastHoveredNode{InvalidNodeId}; + + bool _frozen; }; } // namespace QtNodes diff --git a/include/QtNodes/internal/DataFlowGraphicsScene.hpp b/include/QtNodes/internal/DataFlowGraphicsScene.hpp index e9f89cac0..aeeae4065 100644 --- a/include/QtNodes/internal/DataFlowGraphicsScene.hpp +++ b/include/QtNodes/internal/DataFlowGraphicsScene.hpp @@ -1,8 +1,10 @@ #pragma once #include "BasicGraphicsScene.hpp" +#include "ConnectionGraphicsObject.hpp" #include "DataFlowGraphModel.hpp" #include "Export.hpp" +#include "NodeConnectionInteraction.hpp" namespace QtNodes { @@ -22,6 +24,18 @@ class NODE_EDITOR_PUBLIC DataFlowGraphicsScene : public BasicGraphicsScene public: std::vector selectedNodes() const; QMenu *createSceneMenu(QPointF const scenePos) override; + void updateConnectionGraphics(const std::unordered_set &connections, bool state) + { + for (auto const &c : connections) { + if (auto *cgo = connectionGraphicsObject(c)) { + // NodeEditor3 does not expose connection geometry + // directly. Update the graphics object to reflect + // the frozen state if available. + cgo->connectionState().setFrozen(state); + cgo->update(); + } + } + } public Q_SLOTS: bool save() const; diff --git a/src/DefaultConnectionPainter.cpp b/src/DefaultConnectionPainter.cpp index a7079ebd3..d9c113335 100644 --- a/src/DefaultConnectionPainter.cpp +++ b/src/DefaultConnectionPainter.cpp @@ -9,7 +9,6 @@ #include - namespace QtNodes { QPainterPath DefaultConnectionPainter::cubicPath(ConnectionGraphicsObject const &connection) const @@ -27,11 +26,12 @@ QPainterPath DefaultConnectionPainter::cubicPath(ConnectionGraphicsObject const return cubic; } -void DefaultConnectionPainter::drawSketchLine(QPainter *painter, ConnectionGraphicsObject const &cgo) const +void DefaultConnectionPainter::drawSketchLine(QPainter *painter, + ConnectionGraphicsObject const &cgo) const { ConnectionState const &state = cgo.connectionState(); - if (state.requiresPort()) { + if (state.requiresPort() || state.frozen()) { auto const &connectionStyle = QtNodes::StyleCollection::connectionStyle(); QPen pen; @@ -49,7 +49,8 @@ void DefaultConnectionPainter::drawSketchLine(QPainter *painter, ConnectionGraph } } -void DefaultConnectionPainter::drawHoveredOrSelected(QPainter *painter, ConnectionGraphicsObject const &cgo) const +void DefaultConnectionPainter::drawHoveredOrSelected(QPainter *painter, + ConnectionGraphicsObject const &cgo) const { bool const hovered = cgo.connectionState().hovered(); bool const selected = cgo.isSelected(); @@ -74,11 +75,12 @@ void DefaultConnectionPainter::drawHoveredOrSelected(QPainter *painter, Connecti } } -void DefaultConnectionPainter::drawNormalLine(QPainter *painter, ConnectionGraphicsObject const &cgo) const +void DefaultConnectionPainter::drawNormalLine(QPainter *painter, + ConnectionGraphicsObject const &cgo) const { ConnectionState const &state = cgo.connectionState(); - if (state.requiresPort()) + if (state.requiresPort() || state.frozen()) return; // colors @@ -132,6 +134,7 @@ void DefaultConnectionPainter::drawNormalLine(QPainter *painter, ConnectionGraph painter->setBrush(Qt::NoBrush); QColor cOut = normalColorOut; + if (selected) cOut = cOut.darker(200); p.setColor(cOut); @@ -200,7 +203,8 @@ void DefaultConnectionPainter::paint(QPainter *painter, ConnectionGraphicsObject painter->drawEllipse(cgo.in(), pointRadius, pointRadius); } -QPainterPath DefaultConnectionPainter::getPainterStroke(ConnectionGraphicsObject const &connection) const +QPainterPath DefaultConnectionPainter::getPainterStroke( + ConnectionGraphicsObject const &connection) const { auto cubic = cubicPath(connection); From 4935991cdf4f854d3fe0a8af8b45e2cb15cbeb2f Mon Sep 17 00:00:00 2001 From: g-abilio Date: Thu, 7 Aug 2025 15:48:34 -0300 Subject: [PATCH 02/11] add frozen dynamic to node delegate model --- .../QtNodes/internal/NodeDelegateModel.hpp | 11 +++++-- src/DataFlowGraphModel.cpp | 33 ++++++++----------- 2 files changed, 23 insertions(+), 21 deletions(-) diff --git a/include/QtNodes/internal/NodeDelegateModel.hpp b/include/QtNodes/internal/NodeDelegateModel.hpp index a7ae23bd5..dd6769fbd 100644 --- a/include/QtNodes/internal/NodeDelegateModel.hpp +++ b/include/QtNodes/internal/NodeDelegateModel.hpp @@ -10,7 +10,6 @@ #include - namespace QtNodes { class StyleCollection; @@ -21,7 +20,9 @@ class StyleCollection; * AbstractGraphModel. * This class is the same what has been called NodeDataModel before v3. */ -class NODE_EDITOR_PUBLIC NodeDelegateModel : public QObject, public Serializable +class NODE_EDITOR_PUBLIC NodeDelegateModel + : public QObject + , public Serializable { Q_OBJECT @@ -75,6 +76,10 @@ class NODE_EDITOR_PUBLIC NodeDelegateModel : public QObject, public Serializable virtual bool resizable() const { return false; } + bool frozen() const { return _frozen; } + + void setFrozenState(bool state) { _frozen = state; } + public Q_SLOTS: virtual void inputConnectionCreated(ConnectionId const &) {} virtual void inputConnectionDeleted(ConnectionId const &) {} @@ -117,6 +122,8 @@ public Q_SLOTS: private: NodeStyle _nodeStyle; + + bool _frozen{false}; }; } // namespace QtNodes diff --git a/src/DataFlowGraphModel.cpp b/src/DataFlowGraphModel.cpp index f7892a02a..17207bb89 100644 --- a/src/DataFlowGraphModel.cpp +++ b/src/DataFlowGraphModel.cpp @@ -8,7 +8,6 @@ #include #include - namespace QtNodes { DataFlowGraphModel::DataFlowGraphModel(std::shared_ptr registry) @@ -117,12 +116,10 @@ bool DataFlowGraphModel::connectionPossible(ConnectionId const connectionId) con // Check port bounds, i.e. that we do not connect non-existing port numbers auto checkPortBounds = [&](PortType const portType) { NodeId const nodeId = getNodeId(portType, connectionId); - auto portCountRole = (portType == PortType::Out) ? - NodeRole::OutPortCount : - NodeRole::InPortCount; + auto portCountRole = (portType == PortType::Out) ? NodeRole::OutPortCount + : NodeRole::InPortCount; - std::size_t const portCount = - nodeData(nodeId, portCountRole).toUInt(); + std::size_t const portCount = nodeData(nodeId, portCountRole).toUInt(); return getPortIndex(portType, connectionId) < portCount; }; @@ -146,12 +143,9 @@ bool DataFlowGraphModel::connectionPossible(ConnectionId const connectionId) con return connected.empty() || (policy == ConnectionPolicy::Many); }; - bool const basicChecks = - getDataType(PortType::Out).id == getDataType(PortType::In).id - && portVacant(PortType::Out) - && portVacant(PortType::In) - && checkPortBounds(PortType::Out) - && checkPortBounds(PortType::In); + bool const basicChecks = getDataType(PortType::Out).id == getDataType(PortType::In).id + && portVacant(PortType::Out) && portVacant(PortType::In) + && checkPortBounds(PortType::Out) && checkPortBounds(PortType::In); // In data-flow mode (this class) it's important to forbid graph loops. // We perform depth-first graph traversal starting from the "Input" port of @@ -161,17 +155,16 @@ bool DataFlowGraphModel::connectionPossible(ConnectionId const connectionId) con std::stack filo; filo.push(connectionId.inNodeId); - while (!filo.empty()) - { - auto id = filo.top(); filo.pop(); + while (!filo.empty()) { + auto id = filo.top(); + filo.pop(); if (id == connectionId.outNodeId) { // LOOP! - return true; + return true; } // Add out-connections to continue interations - std::size_t const nOutPorts = - nodeData(id, NodeRole::OutPortCount).toUInt(); + std::size_t const nOutPorts = nodeData(id, NodeRole::OutPortCount).toUInt(); for (PortIndex index = 0; index < nOutPorts; ++index) { auto const &outConnectionIds = connections(id, PortType::Out, index); @@ -188,7 +181,6 @@ bool DataFlowGraphModel::connectionPossible(ConnectionId const connectionId) con return basicChecks && (loopsEnabled() || !hasLoops()); } - void DataFlowGraphModel::addConnection(ConnectionId const connectionId) { _connectivity.insert(connectionId); @@ -418,6 +410,9 @@ bool DataFlowGraphModel::setPortData( switch (role) { case PortRole::Data: if (portType == PortType::In) { + if (model->frozen()) + return false; + model->setInData(value.value>(), portIndex); // Triggers repainting on the scene. From d058334c39865196cad70ddf6efef8edbb712d4b Mon Sep 17 00:00:00 2001 From: g-abilio Date: Fri, 8 Aug 2025 22:01:31 -0300 Subject: [PATCH 03/11] create freeze and unfreeze menu --- .../QtNodes/internal/BasicGraphicsScene.hpp | 2 + src/BasicGraphicsScene.cpp | 62 +++++++++++++++++++ src/DataFlowGraphicsScene.cpp | 1 - src/GraphicsView.cpp | 14 +++-- 4 files changed, 72 insertions(+), 7 deletions(-) diff --git a/include/QtNodes/internal/BasicGraphicsScene.hpp b/include/QtNodes/internal/BasicGraphicsScene.hpp index 72a308578..0d8b6dbfa 100644 --- a/include/QtNodes/internal/BasicGraphicsScene.hpp +++ b/include/QtNodes/internal/BasicGraphicsScene.hpp @@ -109,6 +109,8 @@ class NODE_EDITOR_PUBLIC BasicGraphicsScene : public QGraphicsScene */ virtual QMenu *createSceneMenu(QPointF const scenePos); + QMenu *createFreezeMenu(QPointF const scenePos); + Q_SIGNALS: void modified(BasicGraphicsScene *); void nodeMoved(NodeId const nodeId, QPointF const &newLocation); diff --git a/src/BasicGraphicsScene.cpp b/src/BasicGraphicsScene.cpp index 7cec5ec45..3d73c512f 100644 --- a/src/BasicGraphicsScene.cpp +++ b/src/BasicGraphicsScene.cpp @@ -11,6 +11,10 @@ #include +#include +#include +#include +#include #include #include @@ -198,6 +202,64 @@ QMenu *BasicGraphicsScene::createSceneMenu(QPointF const scenePos) return nullptr; } +QMenu *BasicGraphicsScene::createFreezeMenu(QPointF const scenePos) +{ + QMenu *menu = new QMenu(); + + auto *txtBox = new QLineEdit(menu); + txtBox->setPlaceholderText(QStringLiteral("Filter")); + txtBox->setClearButtonEnabled(true); + + auto *txtBoxAction = new QWidgetAction(menu); + txtBoxAction->setDefaultWidget(txtBox); + menu->addAction(txtBoxAction); + + QTreeWidget *treeView = new QTreeWidget(menu); + treeView->header()->close(); + + treeView->setMaximumHeight(100); + treeView->setMaximumWidth(150); + + auto *treeViewAction = new QWidgetAction(menu); + treeViewAction->setDefaultWidget(treeView); + menu->addAction(treeViewAction); + + auto freezeItem = new QTreeWidgetItem(treeView); + freezeItem->setText(0, "Freeze"); + + auto unfreezeItem = new QTreeWidgetItem(treeView); + unfreezeItem->setText(0, "Unfreeze"); + + treeView->expandAll(); + + connect(treeView, &QTreeWidget::itemClicked, [this, menu, scenePos](QTreeWidgetItem *item, int) { + if (item->text(0) == "Freeze") { + menu->close(); + return; + } + if (item->text(0) == "Unfreeze") { + menu->close(); + return; + } + }); + + // Filtro + connect(txtBox, &QLineEdit::textChanged, [treeView](const QString &text) { + QTreeWidgetItemIterator it(treeView); + while (*it) { + auto modelName = (*it)->text(0); + const bool match = (modelName.contains(text, Qt::CaseInsensitive)); + (*it)->setHidden(!match); + ++it; + } + }); + + txtBox->setFocus(); + menu->setAttribute(Qt::WA_DeleteOnClose); + + return menu; +} + void BasicGraphicsScene::traverseGraphAndPopulateGraphicsObjects() { auto allNodeIds = _graphModel.allNodeIds(); diff --git a/src/DataFlowGraphicsScene.cpp b/src/DataFlowGraphicsScene.cpp index 32970608e..e300ba86e 100644 --- a/src/DataFlowGraphicsScene.cpp +++ b/src/DataFlowGraphicsScene.cpp @@ -26,7 +26,6 @@ #include #include - namespace QtNodes { DataFlowGraphicsScene::DataFlowGraphicsScene(DataFlowGraphModel &graphModel, QObject *parent) diff --git a/src/GraphicsView.cpp b/src/GraphicsView.cpp index cbd87cc87..b431afb42 100644 --- a/src/GraphicsView.cpp +++ b/src/GraphicsView.cpp @@ -166,18 +166,20 @@ void GraphicsView::centerScene() void GraphicsView::contextMenuEvent(QContextMenuEvent *event) { + QGraphicsView::contextMenuEvent(event); + QMenu *menu; + if (itemAt(event->pos())) { - QGraphicsView::contextMenuEvent(event); - return; + menu = nodeScene()->createFreezeMenu(mapToScene(event->pos())); + } else { + menu = nodeScene()->createSceneMenu(mapToScene(event->pos())); } - auto const scenePos = mapToScene(event->pos()); - - QMenu *menu = nodeScene()->createSceneMenu(scenePos); - if (menu) { menu->exec(event->globalPos()); } + + return; } void GraphicsView::wheelEvent(QWheelEvent *event) From c31c8fc16b32528e0ab77ba8847253d85c22d987 Mon Sep 17 00:00:00 2001 From: g-abilio Date: Fri, 8 Aug 2025 23:01:20 -0300 Subject: [PATCH 04/11] add freeze and unfreeze logic to freeze menu --- src/BasicGraphicsScene.cpp | 44 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/src/BasicGraphicsScene.cpp b/src/BasicGraphicsScene.cpp index 3d73c512f..b9a8baf59 100644 --- a/src/BasicGraphicsScene.cpp +++ b/src/BasicGraphicsScene.cpp @@ -234,10 +234,54 @@ QMenu *BasicGraphicsScene::createFreezeMenu(QPointF const scenePos) connect(treeView, &QTreeWidget::itemClicked, [this, menu, scenePos](QTreeWidgetItem *item, int) { if (item->text(0) == "Freeze") { + for (QGraphicsItem *item : selectedItems()) { + if (auto n = qgraphicsitem_cast(item)) { + unsigned int portCount + = graphModel().nodeData(n->nodeId(), NodeRole::OutPortCount).toUInt(); + for (int i = 0; i < portCount; i++) { + auto graphConnections = graphModel().connections(n->nodeId(), + QtNodes::PortType::Out, + QtNodes::PortIndex(i)); + + for (auto const &c : graphConnections) { + if (auto *cgo = connectionGraphicsObject(c)) { + // NodeEditor3 does not expose connection geometry + // directly. Update the graphics object to reflect + // the frozen state if available. + cgo->connectionState().setFrozen(true); + cgo->update(); + } + } + } + } + } + menu->close(); return; } if (item->text(0) == "Unfreeze") { + for (QGraphicsItem *item : selectedItems()) { + if (auto n = qgraphicsitem_cast(item)) { + unsigned int portCount + = graphModel().nodeData(n->nodeId(), NodeRole::OutPortCount).toUInt(); + for (int i = 0; i < portCount; i++) { + auto graphConnections = graphModel().connections(n->nodeId(), + QtNodes::PortType::Out, + QtNodes::PortIndex(i)); + + for (auto const &c : graphConnections) { + if (auto *cgo = connectionGraphicsObject(c)) { + // NodeEditor3 does not expose connection geometry + // directly. Update the graphics object to reflect + // the frozen state if available. + cgo->connectionState().setFrozen(false); + cgo->update(); + } + } + } + } + } + menu->close(); return; } From febd94635a8762ee7499ac6023535949008a82d1 Mon Sep 17 00:00:00 2001 From: g-abilio Date: Tue, 12 Aug 2025 10:29:46 -0300 Subject: [PATCH 05/11] structure to sync delegatemodel and connectionstate --- examples/calculator/MathOperationDataModel.cpp | 8 ++++++-- examples/calculator/MathOperationDataModel.hpp | 1 - include/QtNodes/internal/BasicGraphicsScene.hpp | 3 +-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/examples/calculator/MathOperationDataModel.cpp b/examples/calculator/MathOperationDataModel.cpp index f6a3a26d0..67bcab931 100644 --- a/examples/calculator/MathOperationDataModel.cpp +++ b/examples/calculator/MathOperationDataModel.cpp @@ -1,5 +1,4 @@ #include "MathOperationDataModel.hpp" - #include "DecimalData.hpp" unsigned int MathOperationDataModel::nPorts(PortType portType) const @@ -21,7 +20,12 @@ NodeDataType MathOperationDataModel::dataType(PortType, PortIndex) const std::shared_ptr MathOperationDataModel::outData(PortIndex) { - return std::static_pointer_cast(_result); + auto output = std::static_pointer_cast(_result); + if (output) { + //setFrozenState(); + } + + return output; } void MathOperationDataModel::setInData(std::shared_ptr data, PortIndex portIndex) diff --git a/examples/calculator/MathOperationDataModel.hpp b/examples/calculator/MathOperationDataModel.hpp index 6d279642b..8d56f2637 100644 --- a/examples/calculator/MathOperationDataModel.hpp +++ b/examples/calculator/MathOperationDataModel.hpp @@ -1,7 +1,6 @@ #pragma once #include - #include #include #include diff --git a/include/QtNodes/internal/BasicGraphicsScene.hpp b/include/QtNodes/internal/BasicGraphicsScene.hpp index 0d8b6dbfa..ce7dfcadf 100644 --- a/include/QtNodes/internal/BasicGraphicsScene.hpp +++ b/include/QtNodes/internal/BasicGraphicsScene.hpp @@ -5,7 +5,6 @@ #include "ConnectionIdHash.hpp" #include "Definitions.hpp" #include "Export.hpp" - #include "QUuidStdHash.hpp" #include @@ -28,7 +27,7 @@ class ConnectionGraphicsObject; class NodeGraphicsObject; class NodeStyle; -/// An instance of QGraphicsScene, holds connections and nodes. +/// An instance of QGraphicsScene , holds connections and nodes. class NODE_EDITOR_PUBLIC BasicGraphicsScene : public QGraphicsScene { Q_OBJECT From fa34b00517e43a62a4e63610ba48bb0de808079d Mon Sep 17 00:00:00 2001 From: g-abilio Date: Tue, 12 Aug 2025 14:26:32 -0300 Subject: [PATCH 06/11] sync delegatemodel and connectionstate, as well as freeze example creation --- .../calculator/MathOperationDataModel.cpp | 3 -- examples/calculator/RandomNumber.hpp | 51 +++++++++++++++++++ examples/calculator/headless_main.cpp | 3 ++ examples/calculator/main.cpp | 3 ++ src/BasicGraphicsScene.cpp | 16 ++++++ 5 files changed, 73 insertions(+), 3 deletions(-) create mode 100644 examples/calculator/RandomNumber.hpp diff --git a/examples/calculator/MathOperationDataModel.cpp b/examples/calculator/MathOperationDataModel.cpp index 67bcab931..5cf03e269 100644 --- a/examples/calculator/MathOperationDataModel.cpp +++ b/examples/calculator/MathOperationDataModel.cpp @@ -21,9 +21,6 @@ NodeDataType MathOperationDataModel::dataType(PortType, PortIndex) const std::shared_ptr MathOperationDataModel::outData(PortIndex) { auto output = std::static_pointer_cast(_result); - if (output) { - //setFrozenState(); - } return output; } diff --git a/examples/calculator/RandomNumber.hpp b/examples/calculator/RandomNumber.hpp new file mode 100644 index 000000000..c9c385858 --- /dev/null +++ b/examples/calculator/RandomNumber.hpp @@ -0,0 +1,51 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "MathOperationDataModel.hpp" +#include "DecimalData.hpp" + +/// The model generates a random value in a schema produced to +/// demonstrate the usage of the freeze/unfreeze mechanism. +/// The random number is generate in the [n1, n2] interval. +class RandomNumberModel : public MathOperationDataModel +{ +public: + virtual ~RandomNumberModel() {} + +public: + QString caption() const override { return QStringLiteral("Random Number"); } + + QString name() const override { return QStringLiteral("Random Number"); } + +private: + void compute() override + { + if (frozen()) + return; + + PortIndex const outPortIndex = 0; + + auto n1 = _number1.lock(); + auto n2 = _number2.lock(); + + if (n1 && n2) { + double a = n1->number(); + double b = n2->number(); + + if (a > b) + std::swap(a, b); + + double randomValue = QRandomGenerator::global()->generateDouble() * (b - a) + a; + _result = std::make_shared(randomValue); + } else { + _result.reset(); + } + + Q_EMIT dataUpdated(outPortIndex); + } +}; diff --git a/examples/calculator/headless_main.cpp b/examples/calculator/headless_main.cpp index e4dafa022..5e9330c0f 100644 --- a/examples/calculator/headless_main.cpp +++ b/examples/calculator/headless_main.cpp @@ -3,6 +3,7 @@ #include "MultiplicationModel.hpp" #include "NumberDisplayDataModel.hpp" #include "NumberSourceDataModel.hpp" +#include "RandomNumber.hpp" #include "SubtractionModel.hpp" #include @@ -27,6 +28,8 @@ static std::shared_ptr registerDataModels() ret->registerModel("Operators"); + ret->registerModel("Operators"); + return ret; } diff --git a/examples/calculator/main.cpp b/examples/calculator/main.cpp index 980c0f23a..95f4d6227 100644 --- a/examples/calculator/main.cpp +++ b/examples/calculator/main.cpp @@ -17,6 +17,7 @@ #include "MultiplicationModel.hpp" #include "NumberDisplayDataModel.hpp" #include "NumberSourceDataModel.hpp" +#include "RandomNumber.hpp" #include "SubtractionModel.hpp" using QtNodes::ConnectionStyle; @@ -40,6 +41,8 @@ static std::shared_ptr registerDataModels() ret->registerModel("Operators"); + ret->registerModel("Operators"); + return ret; } diff --git a/src/BasicGraphicsScene.cpp b/src/BasicGraphicsScene.cpp index b9a8baf59..37a0701e1 100644 --- a/src/BasicGraphicsScene.cpp +++ b/src/BasicGraphicsScene.cpp @@ -3,10 +3,12 @@ #include "AbstractNodeGeometry.hpp" #include "ConnectionGraphicsObject.hpp" #include "ConnectionIdUtils.hpp" +#include "DataFlowGraphModel.hpp" #include "DefaultConnectionPainter.hpp" #include "DefaultHorizontalNodeGeometry.hpp" #include "DefaultNodePainter.hpp" #include "DefaultVerticalNodeGeometry.hpp" +#include "NodeDelegateModel.hpp" #include "NodeGraphicsObject.hpp" #include @@ -253,6 +255,13 @@ QMenu *BasicGraphicsScene::createFreezeMenu(QPointF const scenePos) } } } + + if (auto *dfModel = dynamic_cast(&graphModel())) { + if (auto *delegate = dfModel->delegateModel( + n->nodeId())) { + delegate->setFrozenState(true); + } + } } } @@ -279,6 +288,13 @@ QMenu *BasicGraphicsScene::createFreezeMenu(QPointF const scenePos) } } } + + if (auto *dfModel = dynamic_cast(&graphModel())) { + if (auto *delegate = dfModel->delegateModel( + n->nodeId())) { + delegate->setFrozenState(false); + } + } } } From 0491c390daa85faa872e9527616adc3ebcacdf70 Mon Sep 17 00:00:00 2001 From: g-abilio Date: Tue, 12 Aug 2025 14:56:23 -0300 Subject: [PATCH 07/11] refactor freeze code and remove comments --- .../QtNodes/internal/BasicGraphicsScene.hpp | 2 + .../internal/DataFlowGraphicsScene.hpp | 3 - src/BasicGraphicsScene.cpp | 86 +++++++------------ 3 files changed, 32 insertions(+), 59 deletions(-) diff --git a/include/QtNodes/internal/BasicGraphicsScene.hpp b/include/QtNodes/internal/BasicGraphicsScene.hpp index ce7dfcadf..da3b9536b 100644 --- a/include/QtNodes/internal/BasicGraphicsScene.hpp +++ b/include/QtNodes/internal/BasicGraphicsScene.hpp @@ -110,6 +110,8 @@ class NODE_EDITOR_PUBLIC BasicGraphicsScene : public QGraphicsScene QMenu *createFreezeMenu(QPointF const scenePos); + void freezeModelAndConnections(bool isFreeze); + Q_SIGNALS: void modified(BasicGraphicsScene *); void nodeMoved(NodeId const nodeId, QPointF const &newLocation); diff --git a/include/QtNodes/internal/DataFlowGraphicsScene.hpp b/include/QtNodes/internal/DataFlowGraphicsScene.hpp index aeeae4065..1f23ab26d 100644 --- a/include/QtNodes/internal/DataFlowGraphicsScene.hpp +++ b/include/QtNodes/internal/DataFlowGraphicsScene.hpp @@ -28,9 +28,6 @@ class NODE_EDITOR_PUBLIC DataFlowGraphicsScene : public BasicGraphicsScene { for (auto const &c : connections) { if (auto *cgo = connectionGraphicsObject(c)) { - // NodeEditor3 does not expose connection geometry - // directly. Update the graphics object to reflect - // the frozen state if available. cgo->connectionState().setFrozen(state); cgo->update(); } diff --git a/src/BasicGraphicsScene.cpp b/src/BasicGraphicsScene.cpp index 37a0701e1..11964eaff 100644 --- a/src/BasicGraphicsScene.cpp +++ b/src/BasicGraphicsScene.cpp @@ -236,67 +236,13 @@ QMenu *BasicGraphicsScene::createFreezeMenu(QPointF const scenePos) connect(treeView, &QTreeWidget::itemClicked, [this, menu, scenePos](QTreeWidgetItem *item, int) { if (item->text(0) == "Freeze") { - for (QGraphicsItem *item : selectedItems()) { - if (auto n = qgraphicsitem_cast(item)) { - unsigned int portCount - = graphModel().nodeData(n->nodeId(), NodeRole::OutPortCount).toUInt(); - for (int i = 0; i < portCount; i++) { - auto graphConnections = graphModel().connections(n->nodeId(), - QtNodes::PortType::Out, - QtNodes::PortIndex(i)); - - for (auto const &c : graphConnections) { - if (auto *cgo = connectionGraphicsObject(c)) { - // NodeEditor3 does not expose connection geometry - // directly. Update the graphics object to reflect - // the frozen state if available. - cgo->connectionState().setFrozen(true); - cgo->update(); - } - } - } - - if (auto *dfModel = dynamic_cast(&graphModel())) { - if (auto *delegate = dfModel->delegateModel( - n->nodeId())) { - delegate->setFrozenState(true); - } - } - } - } + freezeModelAndConnections(true); menu->close(); return; } if (item->text(0) == "Unfreeze") { - for (QGraphicsItem *item : selectedItems()) { - if (auto n = qgraphicsitem_cast(item)) { - unsigned int portCount - = graphModel().nodeData(n->nodeId(), NodeRole::OutPortCount).toUInt(); - for (int i = 0; i < portCount; i++) { - auto graphConnections = graphModel().connections(n->nodeId(), - QtNodes::PortType::Out, - QtNodes::PortIndex(i)); - - for (auto const &c : graphConnections) { - if (auto *cgo = connectionGraphicsObject(c)) { - // NodeEditor3 does not expose connection geometry - // directly. Update the graphics object to reflect - // the frozen state if available. - cgo->connectionState().setFrozen(false); - cgo->update(); - } - } - } - - if (auto *dfModel = dynamic_cast(&graphModel())) { - if (auto *delegate = dfModel->delegateModel( - n->nodeId())) { - delegate->setFrozenState(false); - } - } - } - } + freezeModelAndConnections(false); menu->close(); return; @@ -444,4 +390,32 @@ void BasicGraphicsScene::onModelReset() traverseGraphAndPopulateGraphicsObjects(); } +void BasicGraphicsScene::freezeModelAndConnections(bool isFreeze) +{ + for (QGraphicsItem *item : selectedItems()) { + if (auto n = qgraphicsitem_cast(item)) { + unsigned int portCount + = graphModel().nodeData(n->nodeId(), NodeRole::OutPortCount).toUInt(); + for (int i = 0; i < portCount; i++) { + auto graphConnections = graphModel().connections(n->nodeId(), + QtNodes::PortType::Out, + QtNodes::PortIndex(i)); + + for (auto const &c : graphConnections) { + if (auto *cgo = connectionGraphicsObject(c)) { + cgo->connectionState().setFrozen(isFreeze); + cgo->update(); + } + } + } + + if (auto *dfModel = dynamic_cast(&graphModel())) { + if (auto *delegate = dfModel->delegateModel(n->nodeId())) { + delegate->setFrozenState(isFreeze); + } + } + } + } +} + } // namespace QtNodes From 92cf2068f4fc71337683e482c37f995255611d7c Mon Sep 17 00:00:00 2001 From: g-abilio Date: Thu, 4 Dec 2025 11:57:03 -0300 Subject: [PATCH 08/11] fix build errors in mac-os --- src/BasicGraphicsScene.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/BasicGraphicsScene.cpp b/src/BasicGraphicsScene.cpp index 11964eaff..4eb334f0f 100644 --- a/src/BasicGraphicsScene.cpp +++ b/src/BasicGraphicsScene.cpp @@ -234,7 +234,7 @@ QMenu *BasicGraphicsScene::createFreezeMenu(QPointF const scenePos) treeView->expandAll(); - connect(treeView, &QTreeWidget::itemClicked, [this, menu, scenePos](QTreeWidgetItem *item, int) { + connect(treeView, &QTreeWidget::itemClicked, [this, menu](QTreeWidgetItem *item, int) { if (item->text(0) == "Freeze") { freezeModelAndConnections(true); @@ -394,8 +394,7 @@ void BasicGraphicsScene::freezeModelAndConnections(bool isFreeze) { for (QGraphicsItem *item : selectedItems()) { if (auto n = qgraphicsitem_cast(item)) { - unsigned int portCount - = graphModel().nodeData(n->nodeId(), NodeRole::OutPortCount).toUInt(); + int portCount = graphModel().nodeData(n->nodeId(), NodeRole::OutPortCount).toInt(); for (int i = 0; i < portCount; i++) { auto graphConnections = graphModel().connections(n->nodeId(), QtNodes::PortType::Out, From 8d868ba53ceb36233be4c9941625b7c5aa88c091 Mon Sep 17 00:00:00 2001 From: g-abilio Date: Thu, 4 Dec 2025 12:09:36 -0300 Subject: [PATCH 09/11] fix build errors in mac-os --- include/QtNodes/internal/BasicGraphicsScene.hpp | 2 +- src/BasicGraphicsScene.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/QtNodes/internal/BasicGraphicsScene.hpp b/include/QtNodes/internal/BasicGraphicsScene.hpp index da3b9536b..a6ee90ad4 100644 --- a/include/QtNodes/internal/BasicGraphicsScene.hpp +++ b/include/QtNodes/internal/BasicGraphicsScene.hpp @@ -108,7 +108,7 @@ class NODE_EDITOR_PUBLIC BasicGraphicsScene : public QGraphicsScene */ virtual QMenu *createSceneMenu(QPointF const scenePos); - QMenu *createFreezeMenu(QPointF const scenePos); + QMenu *createFreezeMenu(); void freezeModelAndConnections(bool isFreeze); diff --git a/src/BasicGraphicsScene.cpp b/src/BasicGraphicsScene.cpp index 4eb334f0f..756c16732 100644 --- a/src/BasicGraphicsScene.cpp +++ b/src/BasicGraphicsScene.cpp @@ -204,7 +204,7 @@ QMenu *BasicGraphicsScene::createSceneMenu(QPointF const scenePos) return nullptr; } -QMenu *BasicGraphicsScene::createFreezeMenu(QPointF const scenePos) +QMenu *BasicGraphicsScene::createFreezeMenu() { QMenu *menu = new QMenu(); From 92b467682803f83579f0cec3dda0d62ba04c0ee1 Mon Sep 17 00:00:00 2001 From: g-abilio Date: Thu, 4 Dec 2025 12:16:22 -0300 Subject: [PATCH 10/11] fix cross os build error --- src/GraphicsView.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/GraphicsView.cpp b/src/GraphicsView.cpp index b431afb42..dca1c0116 100644 --- a/src/GraphicsView.cpp +++ b/src/GraphicsView.cpp @@ -170,7 +170,7 @@ void GraphicsView::contextMenuEvent(QContextMenuEvent *event) QMenu *menu; if (itemAt(event->pos())) { - menu = nodeScene()->createFreezeMenu(mapToScene(event->pos())); + menu = nodeScene()->createFreezeMenu(); } else { menu = nodeScene()->createSceneMenu(mapToScene(event->pos())); } From 845d342d76d7a7540307274ab18e62a8c323b78c Mon Sep 17 00:00:00 2001 From: g-abilio Date: Thu, 11 Dec 2025 16:09:32 -0300 Subject: [PATCH 11/11] add frozen menu flag --- examples/calculator/RandomNumber.hpp | 4 ++++ .../internal/DataFlowGraphicsScene.hpp | 10 +-------- .../QtNodes/internal/NodeDelegateModel.hpp | 7 +++++++ src/DataFlowGraphicsScene.cpp | 11 ++++++++++ src/GraphicsView.cpp | 21 ++++++++++++++++--- 5 files changed, 41 insertions(+), 12 deletions(-) diff --git a/examples/calculator/RandomNumber.hpp b/examples/calculator/RandomNumber.hpp index c9c385858..b895dee44 100644 --- a/examples/calculator/RandomNumber.hpp +++ b/examples/calculator/RandomNumber.hpp @@ -15,6 +15,10 @@ class RandomNumberModel : public MathOperationDataModel { public: + RandomNumberModel() { + setFrozenMenu(true); + } + virtual ~RandomNumberModel() {} public: diff --git a/include/QtNodes/internal/DataFlowGraphicsScene.hpp b/include/QtNodes/internal/DataFlowGraphicsScene.hpp index 1f23ab26d..30bcc9491 100644 --- a/include/QtNodes/internal/DataFlowGraphicsScene.hpp +++ b/include/QtNodes/internal/DataFlowGraphicsScene.hpp @@ -24,15 +24,7 @@ class NODE_EDITOR_PUBLIC DataFlowGraphicsScene : public BasicGraphicsScene public: std::vector selectedNodes() const; QMenu *createSceneMenu(QPointF const scenePos) override; - void updateConnectionGraphics(const std::unordered_set &connections, bool state) - { - for (auto const &c : connections) { - if (auto *cgo = connectionGraphicsObject(c)) { - cgo->connectionState().setFrozen(state); - cgo->update(); - } - } - } + void updateConnectionGraphics(const std::unordered_set &connections, bool state); public Q_SLOTS: bool save() const; diff --git a/include/QtNodes/internal/NodeDelegateModel.hpp b/include/QtNodes/internal/NodeDelegateModel.hpp index dd6769fbd..39c050d37 100644 --- a/include/QtNodes/internal/NodeDelegateModel.hpp +++ b/include/QtNodes/internal/NodeDelegateModel.hpp @@ -5,6 +5,7 @@ #include "NodeData.hpp" #include "NodeStyle.hpp" #include "Serializable.hpp" +#include #include @@ -80,6 +81,10 @@ class NODE_EDITOR_PUBLIC NodeDelegateModel void setFrozenState(bool state) { _frozen = state; } + bool frozenMenu() const { return _frozenMenu; } + + void setFrozenMenu(bool state) { _frozenMenu = state; } + public Q_SLOTS: virtual void inputConnectionCreated(ConnectionId const &) {} virtual void inputConnectionDeleted(ConnectionId const &) {} @@ -124,6 +129,8 @@ public Q_SLOTS: NodeStyle _nodeStyle; bool _frozen{false}; + + bool _frozenMenu{false}; }; } // namespace QtNodes diff --git a/src/DataFlowGraphicsScene.cpp b/src/DataFlowGraphicsScene.cpp index e300ba86e..b232164ab 100644 --- a/src/DataFlowGraphicsScene.cpp +++ b/src/DataFlowGraphicsScene.cpp @@ -190,4 +190,15 @@ bool DataFlowGraphicsScene::load() return true; } +void DataFlowGraphicsScene::updateConnectionGraphics( + const std::unordered_set &connections, bool state) +{ + for (auto const &c : connections) { + if (auto *cgo = connectionGraphicsObject(c)) { + cgo->connectionState().setFrozen(state); + cgo->update(); + } + } +} + } // namespace QtNodes diff --git a/src/GraphicsView.cpp b/src/GraphicsView.cpp index dca1c0116..9e6094281 100644 --- a/src/GraphicsView.cpp +++ b/src/GraphicsView.cpp @@ -2,6 +2,8 @@ #include "BasicGraphicsScene.hpp" #include "ConnectionGraphicsObject.hpp" +#include "DataFlowGraphModel.hpp" +#include "NodeDelegateModel.hpp" #include "NodeGraphicsObject.hpp" #include "StyleCollection.hpp" #include "UndoCommands.hpp" @@ -23,7 +25,10 @@ #include using QtNodes::BasicGraphicsScene; +using QtNodes::DataFlowGraphModel; using QtNodes::GraphicsView; +using QtNodes::NodeDelegateModel; +using QtNodes::NodeGraphicsObject; GraphicsView::GraphicsView(QWidget *parent) : QGraphicsView(parent) @@ -167,11 +172,21 @@ void GraphicsView::centerScene() void GraphicsView::contextMenuEvent(QContextMenuEvent *event) { QGraphicsView::contextMenuEvent(event); - QMenu *menu; + QMenu *menu = nullptr; - if (itemAt(event->pos())) { + bool isFrozenMenu; + + if (auto *dfModel = dynamic_cast(&nodeScene()->graphModel())) { + if (auto n = qgraphicsitem_cast(itemAt(event->pos()))) { + if (auto *delegate = dfModel->delegateModel(n->nodeId())) { + isFrozenMenu = delegate->frozenMenu(); + } + } + } + + if (itemAt(event->pos()) && isFrozenMenu) { menu = nodeScene()->createFreezeMenu(); - } else { + } else if (!itemAt(event->pos())) { menu = nodeScene()->createSceneMenu(mapToScene(event->pos())); }