From 988ce45da02502ee9d5986d535a5d4f87bfad964 Mon Sep 17 00:00:00 2001 From: Taiguara Tupinambas Date: Sun, 22 Jun 2025 14:08:06 -0300 Subject: [PATCH 01/31] wip: adds NodeValidationState info to NodeDelegateModel --- examples/calculator/DivisionModel.hpp | 10 ++-- .../QtNodes/internal/DefaultNodePainter.hpp | 2 + include/QtNodes/internal/Definitions.hpp | 27 +++++----- .../QtNodes/internal/NodeDelegateModel.hpp | 19 +++++++ src/DataFlowGraphModel.cpp | 22 ++++++-- src/DefaultNodePainter.cpp | 54 +++++++++++++++++++ src/NodeDelegateModel.cpp | 5 ++ 7 files changed, 119 insertions(+), 20 deletions(-) diff --git a/examples/calculator/DivisionModel.hpp b/examples/calculator/DivisionModel.hpp index 9018450c8..ab0753b6d 100644 --- a/examples/calculator/DivisionModel.hpp +++ b/examples/calculator/DivisionModel.hpp @@ -56,12 +56,14 @@ class DivisionModel : public MathOperationDataModel auto n2 = _number2.lock(); if (n2 && (n2->number() == 0.0)) { - //modelValidationState = NodeValidationState::Error; - //modelValidationError = QStringLiteral("Division by zero error"); + QtNodes::NodeValidationState state; + state._isValid = false; + state._errorMessage = QStringLiteral("Division by zero error"); + setValidatonState(state); _result.reset(); } else if (n1 && n2) { - //modelValidationState = NodeValidationState::Valid; - //modelValidationError = QString(); + QtNodes::NodeValidationState state; + setValidatonState(state); _result = std::make_shared(n1->number() / n2->number()); } else { //modelValidationState = NodeValidationState::Warning; diff --git a/include/QtNodes/internal/DefaultNodePainter.hpp b/include/QtNodes/internal/DefaultNodePainter.hpp index 484969f9a..d9d2be817 100644 --- a/include/QtNodes/internal/DefaultNodePainter.hpp +++ b/include/QtNodes/internal/DefaultNodePainter.hpp @@ -30,5 +30,7 @@ class NODE_EDITOR_PUBLIC DefaultNodePainter : public AbstractNodePainter void drawEntryLabels(QPainter *painter, NodeGraphicsObject &ngo) const; void drawResizeRect(QPainter *painter, NodeGraphicsObject &ngo) const; + + void drawValidationRect(QPainter *painter, NodeGraphicsObject &ngo) const; }; } // namespace QtNodes diff --git a/include/QtNodes/internal/Definitions.hpp b/include/QtNodes/internal/Definitions.hpp index 863fa40b4..efd9bbd3e 100644 --- a/include/QtNodes/internal/Definitions.hpp +++ b/include/QtNodes/internal/Definitions.hpp @@ -18,21 +18,22 @@ NODE_EDITOR_PUBLIC Q_NAMESPACE Q_NAMESPACE_EXPORT(NODE_EDITOR_PUBLIC) #endif - /** +/** * Constants used for fetching QVariant data from GraphModel. */ - enum class NodeRole { - Type = 0, ///< Type of the current node, usually a string. - Position = 1, ///< `QPointF` positon of the node on the scene. - Size = 2, ///< `QSize` for resizable nodes. - CaptionVisible = 3, ///< `bool` for caption visibility. - Caption = 4, ///< `QString` for node caption. - Style = 5, ///< Custom NodeStyle as QJsonDocument - InternalData = 6, ///< Node-stecific user data as QJsonObject - InPortCount = 7, ///< `unsigned int` - OutPortCount = 9, ///< `unsigned int` - Widget = 10, ///< Optional `QWidget*` or `nullptr` - }; +enum class NodeRole { + Type = 0, ///< Type of the current node, usually a string. + Position = 1, ///< `QPointF` positon of the node on the scene. + Size = 2, ///< `QSize` for resizable nodes. + CaptionVisible = 3, ///< `bool` for caption visibility. + Caption = 4, ///< `QString` for node caption. + Style = 5, ///< Custom NodeStyle as QJsonDocument + InternalData = 6, ///< Node-stecific user data as QJsonObject + InPortCount = 7, ///< `unsigned int` + OutPortCount = 9, ///< `unsigned int` + Widget = 10, ///< Optional `QWidget*` or `nullptr` + ValidationState = 11, ///< Enum NodeValidationState of the node +}; Q_ENUM_NS(NodeRole) /** diff --git a/include/QtNodes/internal/NodeDelegateModel.hpp b/include/QtNodes/internal/NodeDelegateModel.hpp index 6301164db..9ee4a8bfb 100644 --- a/include/QtNodes/internal/NodeDelegateModel.hpp +++ b/include/QtNodes/internal/NodeDelegateModel.hpp @@ -2,6 +2,7 @@ #include +#include #include #include "Definitions.hpp" @@ -12,6 +13,15 @@ namespace QtNodes { +/** + * Describes whether a node configuration is usable. + */ +struct NodeValidationState +{ + bool _isValid{true}; + QString _errorMessage{""}; +}; + class StyleCollection; /** @@ -44,11 +54,16 @@ class NODE_EDITOR_PUBLIC NodeDelegateModel : public QObject, public Serializable /// Name makes this model unique virtual QString name() const = 0; + /// Validation State will default to Valid, but you can manipulate it by overriding in an inherited class + virtual NodeValidationState validationState() const { return _nodeValidationState; } + public: QJsonObject save() const override; void load(QJsonObject const &) override; + void setValidatonState(const NodeValidationState &validationState); + public: virtual unsigned int nPorts(PortType portType) const = 0; @@ -128,6 +143,10 @@ public Q_SLOTS: private: NodeStyle _nodeStyle; + + NodeValidationState _nodeValidationState; }; } // namespace QtNodes + +Q_DECLARE_METATYPE(QtNodes::NodeValidationState) diff --git a/src/DataFlowGraphModel.cpp b/src/DataFlowGraphModel.cpp index 7256e3aeb..fdfdc0f09 100644 --- a/src/DataFlowGraphModel.cpp +++ b/src/DataFlowGraphModel.cpp @@ -233,9 +233,14 @@ QVariant DataFlowGraphModel::nodeData(NodeId nodeId, NodeRole role) const break; case NodeRole::Widget: { - auto w = model->embeddedWidget(); + auto *w = model->embeddedWidget(); result = QVariant::fromValue(w); } break; + + case NodeRole::ValidationState: { + auto validationState = model->validationState(); + result = QVariant::fromValue(validationState); + } break; } return result; @@ -295,6 +300,15 @@ bool DataFlowGraphModel::setNodeData(NodeId nodeId, NodeRole role, QVariant valu case NodeRole::Widget: break; + + case NodeRole::ValidationState: { + if (value.canConvert()) { + auto state = value.value(); + if (auto node = delegateModel(nodeId); node != nullptr) { + node->setValidatonState(state); + } + } + } break; } return result; @@ -476,7 +490,8 @@ void DataFlowGraphModel::loadNode(QJsonObject const &nodeJson) connect(model.get(), &NodeDelegateModel::portsAboutToBeDeleted, this, - [restoredNodeId, this](PortType const portType, PortIndex const first, PortIndex const last) { + [restoredNodeId, + this](PortType const portType, PortIndex const first, PortIndex const last) { portsAboutToBeDeleted(restoredNodeId, portType, first, last); }); @@ -488,7 +503,8 @@ void DataFlowGraphModel::loadNode(QJsonObject const &nodeJson) connect(model.get(), &NodeDelegateModel::portsAboutToBeInserted, this, - [restoredNodeId, this](PortType const portType, PortIndex const first, PortIndex const last) { + [restoredNodeId, + this](PortType const portType, PortIndex const first, PortIndex const last) { portsAboutToBeInserted(restoredNodeId, portType, first, last); }); diff --git a/src/DefaultNodePainter.cpp b/src/DefaultNodePainter.cpp index 8febe4cb1..04d85b548 100644 --- a/src/DefaultNodePainter.cpp +++ b/src/DefaultNodePainter.cpp @@ -9,6 +9,7 @@ #include "BasicGraphicsScene.hpp" #include "ConnectionGraphicsObject.hpp" #include "ConnectionIdUtils.hpp" +#include "NodeDelegateModel.hpp" #include "NodeGraphicsObject.hpp" #include "NodeState.hpp" #include "StyleCollection.hpp" @@ -32,6 +33,8 @@ void DefaultNodePainter::paint(QPainter *painter, NodeGraphicsObject &ngo) const drawEntryLabels(painter, ngo); drawResizeRect(painter, ngo); + + drawValidationRect(painter, ngo); } void DefaultNodePainter::drawNodeRect(QPainter *painter, NodeGraphicsObject &ngo) const @@ -270,4 +273,55 @@ void DefaultNodePainter::drawResizeRect(QPainter *painter, NodeGraphicsObject &n } } +void DefaultNodePainter::drawValidationRect(QPainter *painter, NodeGraphicsObject &ngo) const +{ + AbstractGraphModel const &model = ngo.graphModel(); + + NodeId const nodeId = ngo.nodeId(); + + QVariant var = model.nodeData(nodeId, NodeRole::ValidationState); + if (var.canConvert()) { + auto state = var.value(); + if (state._isValid) { + return; + } + QString const errorMsg = state._errorMessage; + + QtNodes::AbstractNodeGeometry const &geometry = ngo.nodeScene()->nodeGeometry(); + QSize const size = geometry.size(nodeId); + + QJsonDocument json = QJsonDocument::fromVariant(model.nodeData(nodeId, NodeRole::Style)); + NodeStyle nodeStyle(json.object()); + + QtNodes::NodeStyle style(json.object()); + + auto color = ngo.isSelected() ? nodeStyle.SelectedBoundaryColor + : nodeStyle.NormalBoundaryColor; + QPen pen(color, ngo.nodeState().hovered() ? nodeStyle.HoveredPenWidth : nodeStyle.PenWidth); + painter->setPen(pen); + + painter->setBrush(nodeStyle.ErrorColor); + + float diam = style.ConnectionPointDiameter; + + QFontMetrics metrics(painter->font()); + QRect textRect = metrics.boundingRect(errorMsg); + unsigned int validationHeight = textRect.height() + diam; + + QRectF boundary(-diam, + -diam + size.height() - validationHeight, + size.width() + 2.0 * diam, + validationHeight + 2.0 * diam); + + double const radius = 3.0; + painter->drawRoundedRect(boundary, radius, radius); + + QPointF position((size.width() - textRect.width()) / 2.0, + size.height() - (validationHeight - textRect.height())); + + painter->setPen(style.FontColor); + painter->drawText(position, errorMsg); + } +} + } // namespace QtNodes diff --git a/src/NodeDelegateModel.cpp b/src/NodeDelegateModel.cpp index 94e47ad68..34cf2cb22 100644 --- a/src/NodeDelegateModel.cpp +++ b/src/NodeDelegateModel.cpp @@ -24,6 +24,11 @@ void NodeDelegateModel::load(QJsonObject const &) // } +void NodeDelegateModel::setValidatonState(const NodeValidationState &validationState) +{ + _nodeValidationState = validationState; +} + ConnectionPolicy NodeDelegateModel::portConnectionPolicy(PortType portType, PortIndex) const { auto result = ConnectionPolicy::One; From 1c847507183d64efe54743662360b63efed3a06f Mon Sep 17 00:00:00 2001 From: Taiguara Tupinambas Date: Sun, 22 Jun 2025 15:20:18 -0300 Subject: [PATCH 02/31] makes the nodeObject red in case of invalid state and adds a tooltip for error msg --- examples/calculator/DivisionModel.hpp | 4 +- .../QtNodes/internal/DefaultNodePainter.hpp | 2 - resources/DefaultStyle.json | 2 +- src/DataFlowGraphModel.cpp | 1 + src/DefaultNodePainter.cpp | 84 +++++-------------- src/NodeGraphicsObject.cpp | 22 +++-- 6 files changed, 42 insertions(+), 73 deletions(-) diff --git a/examples/calculator/DivisionModel.hpp b/examples/calculator/DivisionModel.hpp index ab0753b6d..4b5893614 100644 --- a/examples/calculator/DivisionModel.hpp +++ b/examples/calculator/DivisionModel.hpp @@ -66,8 +66,8 @@ class DivisionModel : public MathOperationDataModel setValidatonState(state); _result = std::make_shared(n1->number() / n2->number()); } else { - //modelValidationState = NodeValidationState::Warning; - //modelValidationError = QStringLiteral("Missing or incorrect inputs"); + QtNodes::NodeValidationState state; + setValidatonState(state); _result.reset(); } diff --git a/include/QtNodes/internal/DefaultNodePainter.hpp b/include/QtNodes/internal/DefaultNodePainter.hpp index d9d2be817..484969f9a 100644 --- a/include/QtNodes/internal/DefaultNodePainter.hpp +++ b/include/QtNodes/internal/DefaultNodePainter.hpp @@ -30,7 +30,5 @@ class NODE_EDITOR_PUBLIC DefaultNodePainter : public AbstractNodePainter void drawEntryLabels(QPainter *painter, NodeGraphicsObject &ngo) const; void drawResizeRect(QPainter *painter, NodeGraphicsObject &ngo) const; - - void drawValidationRect(QPainter *painter, NodeGraphicsObject &ngo) const; }; } // namespace QtNodes diff --git a/resources/DefaultStyle.json b/resources/DefaultStyle.json index 4fe334063..2335ce6c2 100644 --- a/resources/DefaultStyle.json +++ b/resources/DefaultStyle.json @@ -17,7 +17,7 @@ "FontColorFaded" : "gray", "ConnectionPointColor": [169, 169, 169], "FilledConnectionPointColor": "cyan", - "ErrorColor": "red", + "ErrorColor": [255, 105, 97], "WarningColor": [128, 128, 0], "PenWidth": 1.0, diff --git a/src/DataFlowGraphModel.cpp b/src/DataFlowGraphModel.cpp index fdfdc0f09..f3710deef 100644 --- a/src/DataFlowGraphModel.cpp +++ b/src/DataFlowGraphModel.cpp @@ -308,6 +308,7 @@ bool DataFlowGraphModel::setNodeData(NodeId nodeId, NodeRole role, QVariant valu node->setValidatonState(state); } } + Q_EMIT nodeUpdated(nodeId); } break; } diff --git a/src/DefaultNodePainter.cpp b/src/DefaultNodePainter.cpp index 04d85b548..350f249e0 100644 --- a/src/DefaultNodePainter.cpp +++ b/src/DefaultNodePainter.cpp @@ -33,8 +33,6 @@ void DefaultNodePainter::paint(QPainter *painter, NodeGraphicsObject &ngo) const drawEntryLabels(painter, ngo); drawResizeRect(painter, ngo); - - drawValidationRect(painter, ngo); } void DefaultNodePainter::drawNodeRect(QPainter *painter, NodeGraphicsObject &ngo) const @@ -51,7 +49,18 @@ void DefaultNodePainter::drawNodeRect(QPainter *painter, NodeGraphicsObject &ngo NodeStyle nodeStyle(json.object()); - auto color = ngo.isSelected() ? nodeStyle.SelectedBoundaryColor : nodeStyle.NormalBoundaryColor; + QVariant var = model.nodeData(nodeId, NodeRole::ValidationState); + bool invalid = false; + if (var.canConvert()) { + auto state = var.value(); + invalid = !state._isValid; + } + + QColor color = ngo.isSelected() ? nodeStyle.SelectedBoundaryColor + : nodeStyle.NormalBoundaryColor; + if (invalid) { + color = nodeStyle.ErrorColor; + } if (ngo.nodeState().hovered()) { QPen p(color, nodeStyle.HoveredPenWidth); @@ -61,15 +70,17 @@ void DefaultNodePainter::drawNodeRect(QPainter *painter, NodeGraphicsObject &ngo painter->setPen(p); } - QLinearGradient gradient(QPointF(0.0, 0.0), QPointF(2.0, size.height())); - - gradient.setColorAt(0.0, nodeStyle.GradientColor0); - gradient.setColorAt(0.10, nodeStyle.GradientColor1); - gradient.setColorAt(0.90, nodeStyle.GradientColor2); - gradient.setColorAt(1.0, nodeStyle.GradientColor3); - - painter->setBrush(gradient); + if (invalid) { + painter->setBrush(nodeStyle.ErrorColor); + } else { + QLinearGradient gradient(QPointF(0.0, 0.0), QPointF(2.0, size.height())); + gradient.setColorAt(0.0, nodeStyle.GradientColor0); + gradient.setColorAt(0.10, nodeStyle.GradientColor1); + gradient.setColorAt(0.90, nodeStyle.GradientColor2); + gradient.setColorAt(1.0, nodeStyle.GradientColor3); + painter->setBrush(gradient); + } QRectF boundary(0, 0, size.width(), size.height()); double const radius = 3.0; @@ -273,55 +284,4 @@ void DefaultNodePainter::drawResizeRect(QPainter *painter, NodeGraphicsObject &n } } -void DefaultNodePainter::drawValidationRect(QPainter *painter, NodeGraphicsObject &ngo) const -{ - AbstractGraphModel const &model = ngo.graphModel(); - - NodeId const nodeId = ngo.nodeId(); - - QVariant var = model.nodeData(nodeId, NodeRole::ValidationState); - if (var.canConvert()) { - auto state = var.value(); - if (state._isValid) { - return; - } - QString const errorMsg = state._errorMessage; - - QtNodes::AbstractNodeGeometry const &geometry = ngo.nodeScene()->nodeGeometry(); - QSize const size = geometry.size(nodeId); - - QJsonDocument json = QJsonDocument::fromVariant(model.nodeData(nodeId, NodeRole::Style)); - NodeStyle nodeStyle(json.object()); - - QtNodes::NodeStyle style(json.object()); - - auto color = ngo.isSelected() ? nodeStyle.SelectedBoundaryColor - : nodeStyle.NormalBoundaryColor; - QPen pen(color, ngo.nodeState().hovered() ? nodeStyle.HoveredPenWidth : nodeStyle.PenWidth); - painter->setPen(pen); - - painter->setBrush(nodeStyle.ErrorColor); - - float diam = style.ConnectionPointDiameter; - - QFontMetrics metrics(painter->font()); - QRect textRect = metrics.boundingRect(errorMsg); - unsigned int validationHeight = textRect.height() + diam; - - QRectF boundary(-diam, - -diam + size.height() - validationHeight, - size.width() + 2.0 * diam, - validationHeight + 2.0 * diam); - - double const radius = 3.0; - painter->drawRoundedRect(boundary, radius, radius); - - QPointF position((size.width() - textRect.width()) / 2.0, - size.height() - (validationHeight - textRect.height())); - - painter->setPen(style.FontColor); - painter->drawText(position, errorMsg); - } -} - } // namespace QtNodes diff --git a/src/NodeGraphicsObject.cpp b/src/NodeGraphicsObject.cpp index cf4236baf..36c886376 100644 --- a/src/NodeGraphicsObject.cpp +++ b/src/NodeGraphicsObject.cpp @@ -13,6 +13,7 @@ #include "ConnectionGraphicsObject.hpp" #include "ConnectionIdUtils.hpp" #include "NodeConnectionInteraction.hpp" +#include "NodeDelegateModel.hpp" #include "StyleCollection.hpp" #include "UndoCommands.hpp" @@ -37,8 +38,7 @@ NodeGraphicsObject::NodeGraphicsObject(BasicGraphicsScene &scene, NodeId nodeId) NodeStyle nodeStyle(nodeStyleJson); - if(nodeStyle.ShadowEnabled) - { + if (nodeStyle.ShadowEnabled) { auto effect = new QGraphicsDropShadowEffect; effect->setOffset(4, 4); effect->setBlurRadius(20); @@ -79,10 +79,10 @@ BasicGraphicsScene *NodeGraphicsObject::nodeScene() const void NodeGraphicsObject::updateQWidgetEmbedPos() { - if (_proxyWidget) { - AbstractNodeGeometry &geometry = nodeScene()->nodeGeometry(); - _proxyWidget->setPos(geometry.widgetPosition(_nodeId)); - } + if (_proxyWidget) { + AbstractNodeGeometry &geometry = nodeScene()->nodeGeometry(); + _proxyWidget->setPos(geometry.widgetPosition(_nodeId)); + } } void NodeGraphicsObject::embedQWidget() @@ -161,6 +161,16 @@ void NodeGraphicsObject::reactToConnection(ConnectionGraphicsObject const *cgo) void NodeGraphicsObject::paint(QPainter *painter, QStyleOptionGraphicsItem const *option, QWidget *) { + QString tooltip; + QVariant var = _graphModel.nodeData(_nodeId, NodeRole::ValidationState); + if (var.canConvert()) { + auto state = var.value(); + if (!state._isValid) { + tooltip = state._errorMessage; + } + } + setToolTip(tooltip); + painter->setClipRect(option->exposedRect); nodeScene()->nodePainter().paint(painter, *this); From 3007c23e1c35d8d256d1aec5b7107e94fb01aa11 Mon Sep 17 00:00:00 2001 From: Taiguara Tupinambas Date: Sun, 22 Jun 2025 15:34:37 -0300 Subject: [PATCH 03/31] adds warning state and adapts calculator example --- examples/calculator/DivisionModel.hpp | 9 +++++-- .../QtNodes/internal/NodeDelegateModel.hpp | 11 ++++++--- src/DefaultNodePainter.cpp | 24 +++++++++++++------ src/NodeGraphicsObject.cpp | 4 ++-- 4 files changed, 34 insertions(+), 14 deletions(-) diff --git a/examples/calculator/DivisionModel.hpp b/examples/calculator/DivisionModel.hpp index 4b5893614..3643433a0 100644 --- a/examples/calculator/DivisionModel.hpp +++ b/examples/calculator/DivisionModel.hpp @@ -57,12 +57,17 @@ class DivisionModel : public MathOperationDataModel if (n2 && (n2->number() == 0.0)) { QtNodes::NodeValidationState state; - state._isValid = false; - state._errorMessage = QStringLiteral("Division by zero error"); + state._state = QtNodes::NodeValidationState::State::Error; + state._stateMessage = QStringLiteral("Division by zero error"); setValidatonState(state); _result.reset(); } else if (n1 && n2) { QtNodes::NodeValidationState state; + + if (n2->number() < 1e-5) { + state._state = QtNodes::NodeValidationState::State::Warning; + state._stateMessage = QStringLiteral("Dividend very small. Calculation might overflow"); + } setValidatonState(state); _result = std::make_shared(n1->number() / n2->number()); } else { diff --git a/include/QtNodes/internal/NodeDelegateModel.hpp b/include/QtNodes/internal/NodeDelegateModel.hpp index 9ee4a8bfb..1917e3599 100644 --- a/include/QtNodes/internal/NodeDelegateModel.hpp +++ b/include/QtNodes/internal/NodeDelegateModel.hpp @@ -14,12 +14,17 @@ namespace QtNodes { /** - * Describes whether a node configuration is usable. + * Describes whether a node configuration is usable and defines a description message */ struct NodeValidationState { - bool _isValid{true}; - QString _errorMessage{""}; + enum class State : int { + Valid, /// All required inputs are present and correct. + Warning, /// Some inputs are missing or questionable, processing may be unreliable. + Error /// Inputs or settings are invalid, preventing successful computation. + }; + State _state{State::Valid}; + QString _stateMessage{""}; }; class StyleCollection; diff --git a/src/DefaultNodePainter.cpp b/src/DefaultNodePainter.cpp index 350f249e0..5142e161b 100644 --- a/src/DefaultNodePainter.cpp +++ b/src/DefaultNodePainter.cpp @@ -51,15 +51,25 @@ void DefaultNodePainter::drawNodeRect(QPainter *painter, NodeGraphicsObject &ngo QVariant var = model.nodeData(nodeId, NodeRole::ValidationState); bool invalid = false; - if (var.canConvert()) { - auto state = var.value(); - invalid = !state._isValid; - } QColor color = ngo.isSelected() ? nodeStyle.SelectedBoundaryColor : nodeStyle.NormalBoundaryColor; - if (invalid) { - color = nodeStyle.ErrorColor; + + if (var.canConvert()) { + auto state = var.value(); + switch (state._state) { + case NodeValidationState::State::Error: { + invalid = true; + color = nodeStyle.ErrorColor; + } break; + case NodeValidationState::State::Warning: { + invalid = true; + color = nodeStyle.WarningColor; + break; + default: + break; + } + } } if (ngo.nodeState().hovered()) { @@ -71,7 +81,7 @@ void DefaultNodePainter::drawNodeRect(QPainter *painter, NodeGraphicsObject &ngo } if (invalid) { - painter->setBrush(nodeStyle.ErrorColor); + painter->setBrush(color); } else { QLinearGradient gradient(QPointF(0.0, 0.0), QPointF(2.0, size.height())); gradient.setColorAt(0.0, nodeStyle.GradientColor0); diff --git a/src/NodeGraphicsObject.cpp b/src/NodeGraphicsObject.cpp index 36c886376..8b886f21e 100644 --- a/src/NodeGraphicsObject.cpp +++ b/src/NodeGraphicsObject.cpp @@ -165,8 +165,8 @@ void NodeGraphicsObject::paint(QPainter *painter, QStyleOptionGraphicsItem const QVariant var = _graphModel.nodeData(_nodeId, NodeRole::ValidationState); if (var.canConvert()) { auto state = var.value(); - if (!state._isValid) { - tooltip = state._errorMessage; + if (state._state != NodeValidationState::State::Valid) { + tooltip = state._stateMessage; } } setToolTip(tooltip); From 129d414689b915922a1faa893d8aafbf5cd18968 Mon Sep 17 00:00:00 2001 From: Taiguara Tupinambas Date: Sun, 22 Jun 2025 16:22:49 -0300 Subject: [PATCH 04/31] adds validation icon and adapts calculation example --- examples/calculator/DivisionModel.hpp | 17 ++++---- .../QtNodes/internal/DefaultNodePainter.hpp | 6 +++ include/QtNodes/internal/NodeStyle.hpp | 1 + resources/DefaultStyle.json | 5 ++- resources/info-tooltip.svg | 6 +++ resources/resources.qrc | 5 ++- src/DefaultNodePainter.cpp | 40 +++++++++++++++++++ 7 files changed, 69 insertions(+), 11 deletions(-) create mode 100644 resources/info-tooltip.svg diff --git a/examples/calculator/DivisionModel.hpp b/examples/calculator/DivisionModel.hpp index 3643433a0..ea5e6b09a 100644 --- a/examples/calculator/DivisionModel.hpp +++ b/examples/calculator/DivisionModel.hpp @@ -55,19 +55,22 @@ class DivisionModel : public MathOperationDataModel auto n1 = _number1.lock(); auto n2 = _number2.lock(); + QtNodes::NodeValidationState state; if (n2 && (n2->number() == 0.0)) { - QtNodes::NodeValidationState state; state._state = QtNodes::NodeValidationState::State::Error; state._stateMessage = QStringLiteral("Division by zero error"); setValidatonState(state); _result.reset(); - } else if (n1 && n2) { - QtNodes::NodeValidationState state; - - if (n2->number() < 1e-5) { - state._state = QtNodes::NodeValidationState::State::Warning; - state._stateMessage = QStringLiteral("Dividend very small. Calculation might overflow"); + } else if ( n2 && (n2->number() < 1e-5)) { + state._state = QtNodes::NodeValidationState::State::Warning; + state._stateMessage = QStringLiteral("Very small divident. Result might overflow"); + setValidatonState(state); + if (n1) { + _result = std::make_shared(n1->number() / n2->number()); + } else { + _result.reset(); } + } else if (n1 && n2) { setValidatonState(state); _result = std::make_shared(n1->number() / n2->number()); } else { diff --git a/include/QtNodes/internal/DefaultNodePainter.hpp b/include/QtNodes/internal/DefaultNodePainter.hpp index 484969f9a..589800ddd 100644 --- a/include/QtNodes/internal/DefaultNodePainter.hpp +++ b/include/QtNodes/internal/DefaultNodePainter.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include "AbstractNodePainter.hpp" @@ -30,5 +31,10 @@ class NODE_EDITOR_PUBLIC DefaultNodePainter : public AbstractNodePainter void drawEntryLabels(QPainter *painter, NodeGraphicsObject &ngo) const; void drawResizeRect(QPainter *painter, NodeGraphicsObject &ngo) const; + + void drawValidationIcon(QPainter *painter, NodeGraphicsObject &ngo) const; + +private: + QIcon _toolTipIcon{"://info-tooltip.svg"}; }; } // namespace QtNodes diff --git a/include/QtNodes/internal/NodeStyle.hpp b/include/QtNodes/internal/NodeStyle.hpp index 85abc5612..9cb4a250c 100644 --- a/include/QtNodes/internal/NodeStyle.hpp +++ b/include/QtNodes/internal/NodeStyle.hpp @@ -43,6 +43,7 @@ class NODE_EDITOR_PUBLIC NodeStyle : public Style QColor WarningColor; QColor ErrorColor; + QColor ToolTipIconColor; float PenWidth; float HoveredPenWidth; diff --git a/resources/DefaultStyle.json b/resources/DefaultStyle.json index 2335ce6c2..2df69bfd2 100644 --- a/resources/DefaultStyle.json +++ b/resources/DefaultStyle.json @@ -17,8 +17,9 @@ "FontColorFaded" : "gray", "ConnectionPointColor": [169, 169, 169], "FilledConnectionPointColor": "cyan", - "ErrorColor": [255, 105, 97], - "WarningColor": [128, 128, 0], + "ErrorColor": [211, 47, 47], + "WarningColor": [255, 179, 0], + "ToolTipIconColor": "white", "PenWidth": 1.0, "HoveredPenWidth": 1.5, diff --git a/resources/info-tooltip.svg b/resources/info-tooltip.svg new file mode 100644 index 000000000..7ffca6ca6 --- /dev/null +++ b/resources/info-tooltip.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/resources/resources.qrc b/resources/resources.qrc index a0b5ef8ba..08aec37e6 100644 --- a/resources/resources.qrc +++ b/resources/resources.qrc @@ -1,5 +1,6 @@ - - + + DefaultStyle.json + info-tooltip.svg diff --git a/src/DefaultNodePainter.cpp b/src/DefaultNodePainter.cpp index 5142e161b..b7a5a8e73 100644 --- a/src/DefaultNodePainter.cpp +++ b/src/DefaultNodePainter.cpp @@ -33,6 +33,8 @@ void DefaultNodePainter::paint(QPainter *painter, NodeGraphicsObject &ngo) const drawEntryLabels(painter, ngo); drawResizeRect(painter, ngo); + + drawValidationIcon(painter, ngo); } void DefaultNodePainter::drawNodeRect(QPainter *painter, NodeGraphicsObject &ngo) const @@ -294,4 +296,42 @@ void DefaultNodePainter::drawResizeRect(QPainter *painter, NodeGraphicsObject &n } } +void DefaultNodePainter::drawValidationIcon(QPainter *painter, NodeGraphicsObject &ngo) const +{ + AbstractGraphModel &model = ngo.graphModel(); + NodeId const nodeId = ngo.nodeId(); + AbstractNodeGeometry &geometry = ngo.nodeScene()->nodeGeometry(); + + QVariant var = model.nodeData(nodeId, NodeRole::ValidationState); + if (!var.canConvert()) + return; + + auto state = var.value(); + if (state._state == NodeValidationState::State::Valid) + return; + + QJsonDocument json = QJsonDocument::fromVariant(model.nodeData(nodeId, NodeRole::Style)); + NodeStyle nodeStyle(json.object()); + + QSize size = geometry.size(nodeId); + + QIcon icon(":/info-tooltip.svg"); + QSize iconSize(16, 16); + QPixmap pixmap = icon.pixmap(iconSize); + + QColor color = (state._state == NodeValidationState::State::Error) ? nodeStyle.ErrorColor + : nodeStyle.WarningColor; + + QPainter imgPainter(&pixmap); + imgPainter.setCompositionMode(QPainter::CompositionMode_SourceIn); + imgPainter.fillRect(pixmap.rect(), color); + imgPainter.end(); + + QPointF center(size.width(), 0.0); + center += QPointF(iconSize.width() / 2.0, -iconSize.height() / 2.0); + + painter->drawPixmap(center.toPoint() - QPoint(iconSize.width() / 2, iconSize.height() / 2), + pixmap); +} + } // namespace QtNodes From e0b0c4a51565ac5eca75067d18db24e77c54187d Mon Sep 17 00:00:00 2001 From: g-abilio Date: Mon, 14 Jul 2025 14:03:05 -0300 Subject: [PATCH 05/31] core improvements to develop node processing status --- .../QtNodes/internal/DefaultNodePainter.hpp | 2 + include/QtNodes/internal/Definitions.hpp | 29 +++---- .../QtNodes/internal/NodeDelegateModel.hpp | 32 ++++++- .../QtNodes/internal/NodeGraphicsObject.hpp | 25 ++++++ resources/resources.qrc | 6 ++ resources/status_icons/empty.svg | 22 +++++ resources/status_icons/failed.svg | 78 +++++++++++++++++ resources/status_icons/partial.svg | 26 ++++++ resources/status_icons/pending.svg | 84 +++++++++++++++++++ resources/status_icons/processing.svg | 80 ++++++++++++++++++ resources/status_icons/updated.svg | 15 ++++ src/BasicGraphicsScene.cpp | 2 + src/DataFlowGraphModel.cpp | 15 ++++ src/DefaultNodePainter.cpp | 36 ++++++++ src/NodeDelegateModel.cpp | 5 ++ src/NodeGraphicsObject.cpp | 75 +++++++++++++++++ 16 files changed, 517 insertions(+), 15 deletions(-) create mode 100644 resources/status_icons/empty.svg create mode 100644 resources/status_icons/failed.svg create mode 100644 resources/status_icons/partial.svg create mode 100644 resources/status_icons/pending.svg create mode 100644 resources/status_icons/processing.svg create mode 100644 resources/status_icons/updated.svg diff --git a/include/QtNodes/internal/DefaultNodePainter.hpp b/include/QtNodes/internal/DefaultNodePainter.hpp index 589800ddd..953faa065 100644 --- a/include/QtNodes/internal/DefaultNodePainter.hpp +++ b/include/QtNodes/internal/DefaultNodePainter.hpp @@ -32,6 +32,8 @@ class NODE_EDITOR_PUBLIC DefaultNodePainter : public AbstractNodePainter void drawResizeRect(QPainter *painter, NodeGraphicsObject &ngo) const; + void drawProcessingIndicator(QPainter *painter, NodeGraphicsObject &ngo) const; + void drawValidationIcon(QPainter *painter, NodeGraphicsObject &ngo) const; private: diff --git a/include/QtNodes/internal/Definitions.hpp b/include/QtNodes/internal/Definitions.hpp index efd9bbd3e..592e874e1 100644 --- a/include/QtNodes/internal/Definitions.hpp +++ b/include/QtNodes/internal/Definitions.hpp @@ -18,22 +18,23 @@ NODE_EDITOR_PUBLIC Q_NAMESPACE Q_NAMESPACE_EXPORT(NODE_EDITOR_PUBLIC) #endif -/** + /** * Constants used for fetching QVariant data from GraphModel. */ -enum class NodeRole { - Type = 0, ///< Type of the current node, usually a string. - Position = 1, ///< `QPointF` positon of the node on the scene. - Size = 2, ///< `QSize` for resizable nodes. - CaptionVisible = 3, ///< `bool` for caption visibility. - Caption = 4, ///< `QString` for node caption. - Style = 5, ///< Custom NodeStyle as QJsonDocument - InternalData = 6, ///< Node-stecific user data as QJsonObject - InPortCount = 7, ///< `unsigned int` - OutPortCount = 9, ///< `unsigned int` - Widget = 10, ///< Optional `QWidget*` or `nullptr` - ValidationState = 11, ///< Enum NodeValidationState of the node -}; + enum class NodeRole { + Type = 0, ///< Type of the current node, usually a string. + Position = 1, ///< `QPointF` positon of the node on the scene. + Size = 2, ///< `QSize` for resizable nodes. + CaptionVisible = 3, ///< `bool` for caption visibility. + Caption = 4, ///< `QString` for node caption. + Style = 5, ///< Custom NodeStyle as QJsonDocument + InternalData = 6, ///< Node-stecific user data as QJsonObject + InPortCount = 7, ///< `unsigned int` + OutPortCount = 9, ///< `unsigned int` + Widget = 10, ///< Optional `QWidget*` or `nullptr` + ValidationState = 11, ///< Enum NodeValidationState of the node + ProcessingStatus = 12 ///< Enum NodeProcessingStatus of the node + }; Q_ENUM_NS(NodeRole) /** diff --git a/include/QtNodes/internal/NodeDelegateModel.hpp b/include/QtNodes/internal/NodeDelegateModel.hpp index 1917e3599..e4b13fe13 100644 --- a/include/QtNodes/internal/NodeDelegateModel.hpp +++ b/include/QtNodes/internal/NodeDelegateModel.hpp @@ -35,7 +35,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 @@ -44,6 +46,24 @@ class NODE_EDITOR_PUBLIC NodeDelegateModel : public QObject, public Serializable virtual ~NodeDelegateModel() = default; + /** + * Describes the node status, depending on its current situation + */ + struct NodeProcessingStatus + { + enum class Status : int { + NoStatus = 0, /// + Updated = 1, /// + Processing = 2, /// + Pending = 3, /// + Empty = 4, /// + Failed = 5, /// + Partial = 6, /// + }; + + Status _status{Status::NoStatus}; + }; + /// It is possible to hide caption in GUI virtual bool captionVisible() const { return true; } @@ -62,6 +82,12 @@ class NODE_EDITOR_PUBLIC NodeDelegateModel : public QObject, public Serializable /// Validation State will default to Valid, but you can manipulate it by overriding in an inherited class virtual NodeValidationState validationState() const { return _nodeValidationState; } + /// Returns the curent processing status + virtual NodeProcessingStatus::Status processingStatus() const + { + return _processingStatus._status; + } + public: QJsonObject save() const override; @@ -81,6 +107,8 @@ class NODE_EDITOR_PUBLIC NodeDelegateModel : public QObject, public Serializable void setNodeStyle(NodeStyle const &style); + void setNodeProcessingStatus(NodeProcessingStatus status); + public: virtual void setInData(std::shared_ptr nodeData, PortIndex const portIndex) = 0; @@ -150,6 +178,8 @@ public Q_SLOTS: NodeStyle _nodeStyle; NodeValidationState _nodeValidationState; + + NodeProcessingStatus _processingStatus; }; } // namespace QtNodes diff --git a/include/QtNodes/internal/NodeGraphicsObject.hpp b/include/QtNodes/internal/NodeGraphicsObject.hpp index 50ce7be50..f24f81d4e 100644 --- a/include/QtNodes/internal/NodeGraphicsObject.hpp +++ b/include/QtNodes/internal/NodeGraphicsObject.hpp @@ -1,5 +1,7 @@ #pragma once +#include +#include #include #include @@ -52,6 +54,16 @@ class NodeGraphicsObject : public QGraphicsObject void updateQWidgetEmbedPos(); + void updateStatusIconSize() const; + + const QIcon processingStatusIcon() const; + + QRect statusIconRect() const; + + QSize statusIconSize() const; + + QColor getStatusColor() { return _status_color; } + protected: void paint(QPainter *painter, QStyleOptionGraphicsItem const *option, @@ -89,5 +101,18 @@ class NodeGraphicsObject : public QGraphicsObject // either nullptr or owned by parent QGraphicsItem QGraphicsProxyWidget *_proxyWidget; + + mutable bool _statusIconActive; + + mutable QSize _statusIconSize; + + mutable QColor _status_color; + + const QIcon _statusUpdated{"://status_icons/updated.svg"}; + const QIcon _statusProcessing{"://status_icons/processing.svg"}; + const QIcon _statusPending{"://status_icons/pending.svg"}; + const QIcon _statusInvalid{"://status_icons/failed.svg"}; + const QIcon _statusEmpty{"://status_icons/empty.svg"}; + const QIcon _statusPartial{"://status_icons/partial.svg"}; }; } // namespace QtNodes diff --git a/resources/resources.qrc b/resources/resources.qrc index 08aec37e6..f9da26fcd 100644 --- a/resources/resources.qrc +++ b/resources/resources.qrc @@ -2,5 +2,11 @@ DefaultStyle.json info-tooltip.svg + status_icons/empty.svg + status_icons/failed.svg + status_icons/partial.svg + status_icons/pending.svg + status_icons/processing.svg + status_icons/updated.svg diff --git a/resources/status_icons/empty.svg b/resources/status_icons/empty.svg new file mode 100644 index 000000000..e38cd34ae --- /dev/null +++ b/resources/status_icons/empty.svg @@ -0,0 +1,22 @@ + + + + + + + + + diff --git a/resources/status_icons/failed.svg b/resources/status_icons/failed.svg new file mode 100644 index 000000000..0b94f245c --- /dev/null +++ b/resources/status_icons/failed.svg @@ -0,0 +1,78 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/resources/status_icons/partial.svg b/resources/status_icons/partial.svg new file mode 100644 index 000000000..ec991dc5d --- /dev/null +++ b/resources/status_icons/partial.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/resources/status_icons/pending.svg b/resources/status_icons/pending.svg new file mode 100644 index 000000000..c850c9132 --- /dev/null +++ b/resources/status_icons/pending.svg @@ -0,0 +1,84 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/resources/status_icons/processing.svg b/resources/status_icons/processing.svg new file mode 100644 index 000000000..74b82bc3a --- /dev/null +++ b/resources/status_icons/processing.svg @@ -0,0 +1,80 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/resources/status_icons/updated.svg b/resources/status_icons/updated.svg new file mode 100644 index 000000000..c2485802e --- /dev/null +++ b/resources/status_icons/updated.svg @@ -0,0 +1,15 @@ + + + + + + + + + + diff --git a/src/BasicGraphicsScene.cpp b/src/BasicGraphicsScene.cpp index 10e7b7527..9009071a0 100644 --- a/src/BasicGraphicsScene.cpp +++ b/src/BasicGraphicsScene.cpp @@ -3,11 +3,13 @@ #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 "GraphicsView.hpp" +#include "NodeDelegateModel.hpp" #include "NodeGraphicsObject.hpp" #include diff --git a/src/DataFlowGraphModel.cpp b/src/DataFlowGraphModel.cpp index f3710deef..0f173cfdc 100644 --- a/src/DataFlowGraphModel.cpp +++ b/src/DataFlowGraphModel.cpp @@ -241,6 +241,11 @@ QVariant DataFlowGraphModel::nodeData(NodeId nodeId, NodeRole role) const auto validationState = model->validationState(); result = QVariant::fromValue(validationState); } break; + + case NodeRole::ProcessingStatus: { + auto processingStatus = model->processingStatus(); + result = QVariant::fromValue(processingStatus); + } break; } return result; @@ -310,6 +315,16 @@ bool DataFlowGraphModel::setNodeData(NodeId nodeId, NodeRole role, QVariant valu } Q_EMIT nodeUpdated(nodeId); } break; + + case NodeRole::ProcessingStatus: { + if (value.canConvert()) { + auto status = value.value(); + if (auto node = delegateModel(nodeId); node != nullptr) { + node->setNodeProcessingStatus(status); + } + } + Q_EMIT nodeUpdated(nodeId); + } break; } return result; diff --git a/src/DefaultNodePainter.cpp b/src/DefaultNodePainter.cpp index b7a5a8e73..60d2115f6 100644 --- a/src/DefaultNodePainter.cpp +++ b/src/DefaultNodePainter.cpp @@ -9,6 +9,7 @@ #include "BasicGraphicsScene.hpp" #include "ConnectionGraphicsObject.hpp" #include "ConnectionIdUtils.hpp" +#include "DataFlowGraphModel.hpp" #include "NodeDelegateModel.hpp" #include "NodeGraphicsObject.hpp" #include "NodeState.hpp" @@ -32,6 +33,8 @@ void DefaultNodePainter::paint(QPainter *painter, NodeGraphicsObject &ngo) const drawEntryLabels(painter, ngo); + drawProcessingIndicator(painter, ngo); + drawResizeRect(painter, ngo); drawValidationIcon(painter, ngo); @@ -296,6 +299,39 @@ void DefaultNodePainter::drawResizeRect(QPainter *painter, NodeGraphicsObject &n } } +void DefaultNodePainter::drawProcessingIndicator(QPainter *painter, NodeGraphicsObject &ngo) const +{ + AbstractGraphModel &model = ngo.graphModel(); + NodeId const nodeId = ngo.nodeId(); + + auto *dfModel = dynamic_cast(&model); + if (!dfModel) + return; + + auto *delegate = dfModel->delegateModel(nodeId); + if (!delegate) + return; + + AbstractNodeGeometry &geometry = ngo.nodeScene()->nodeGeometry(); + + ngo.updateStatusIconSize(); + QSize size = geometry.size(nodeId); + + QIcon icon = ngo.processingStatusIcon(); + QSize iconSize(16, 16); + QPixmap pixmap = icon.pixmap(iconSize); + + QColor color = ngo.getStatusColor(); + + QPainter imgPainter(&pixmap); + imgPainter.setCompositionMode(QPainter::CompositionMode_SourceIn); + imgPainter.fillRect(pixmap.rect(), color); + imgPainter.end(); + + QRect r(size.width() - 12.0, size.height() - 12.0, 8.0, 8.0); + painter->drawPixmap(r, pixmap); +} + void DefaultNodePainter::drawValidationIcon(QPainter *painter, NodeGraphicsObject &ngo) const { AbstractGraphModel &model = ngo.graphModel(); diff --git a/src/NodeDelegateModel.cpp b/src/NodeDelegateModel.cpp index 34cf2cb22..834976a34 100644 --- a/src/NodeDelegateModel.cpp +++ b/src/NodeDelegateModel.cpp @@ -56,4 +56,9 @@ void NodeDelegateModel::setNodeStyle(NodeStyle const &style) _nodeStyle = style; } +void NodeDelegateModel::setNodeProcessingStatus(NodeProcessingStatus status) +{ + _processingStatus = status; +} + } // namespace QtNodes diff --git a/src/NodeGraphicsObject.cpp b/src/NodeGraphicsObject.cpp index 8b886f21e..a558cc3b9 100644 --- a/src/NodeGraphicsObject.cpp +++ b/src/NodeGraphicsObject.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -65,6 +66,15 @@ NodeGraphicsObject::NodeGraphicsObject(BasicGraphicsScene &scene, NodeId nodeId) if (_nodeId == nodeId) setLockedState(); }); + + QVariant var = _graphModel.nodeData(_nodeId, NodeRole::ProcessingStatus); + auto processingStatusValue = var.value() + ._status; + + _statusIconActive = processingStatusValue + != QtNodes::NodeDelegateModel::NodeProcessingStatus::Status::NoStatus; + _statusIconSize.setWidth(_statusIconActive ? 32 : 0); + _statusIconSize.setHeight(_statusIconActive ? 32 : 0); } AbstractGraphModel &NodeGraphicsObject::graphModel() const @@ -379,4 +389,69 @@ void NodeGraphicsObject::contextMenuEvent(QGraphicsSceneContextMenuEvent *event) Q_EMIT nodeScene()->nodeContextMenu(_nodeId, mapToScene(event->pos())); } +void NodeGraphicsObject::updateStatusIconSize() const +{ + QVariant var = _graphModel.nodeData(_nodeId, NodeRole::ProcessingStatus); + auto processingStatusValue = var.value() + ._status; + + bool oldStatus = _statusIconActive; + _statusIconActive = processingStatusValue + != QtNodes::NodeDelegateModel::NodeProcessingStatus::Status::NoStatus; + + if (oldStatus != _statusIconActive) { + _statusIconSize.setWidth(_statusIconActive ? 32 : 0); + _statusIconSize.setHeight(_statusIconActive ? 32 : 0); + } +} + +QRect NodeGraphicsObject::statusIconRect() const +{ + QVariant var = _graphModel.nodeData(_nodeId, NodeRole::ProcessingStatus); + + // auto spacing = static_cast(_spacing); + auto iconPos = + // = portScenePosition(std::max(var.value().nPorts(PortType::Out), + // var.value().nPorts(PortType::In)), + // PortType::Out) + // .toPoint() + // + + QPoint{-statusIconSize().width() / 2, 0}; + + return QRect{iconPos, statusIconSize()}; +} + +const QIcon NodeGraphicsObject::processingStatusIcon() const +{ + QVariant var = _graphModel.nodeData(_nodeId, NodeRole::ProcessingStatus); + + switch (var.value()._status) { + case QtNodes::NodeDelegateModel::NodeProcessingStatus::Status::NoStatus: + case QtNodes::NodeDelegateModel::NodeProcessingStatus::Status::Updated: + _status_color = QColor("green"); + return _statusUpdated; + case QtNodes::NodeDelegateModel::NodeProcessingStatus::Status::Processing: + _status_color = QColor("blue"); + return _statusProcessing; + case QtNodes::NodeDelegateModel::NodeProcessingStatus::Status::Pending: + _status_color = QColor("yellow"); + return _statusPending; + case QtNodes::NodeDelegateModel::NodeProcessingStatus::Status::Empty: + _status_color = QColor("gray"); + return _statusEmpty; + case QtNodes::NodeDelegateModel::NodeProcessingStatus::Status::Failed: + _status_color = QColor("red"); + return _statusInvalid; + case QtNodes::NodeDelegateModel::NodeProcessingStatus::Status::Partial: + _status_color = QColor("white"); + return _statusPartial; + } + return _statusInvalid; +} + +QSize NodeGraphicsObject::statusIconSize() const +{ + return _statusIconSize; +} + } // namespace QtNodes From e002f5bbce9da0f9f105e63b137feb85501a2620 Mon Sep 17 00:00:00 2001 From: g-abilio Date: Tue, 15 Jul 2025 16:20:00 -0300 Subject: [PATCH 06/31] first commit on the creation of a processing status example --- .../calculator/MathOperationDataModel.cpp | 15 ++++++-- examples/calculator/MultiplicationModel.hpp | 14 +++++++- .../QtNodes/internal/NodeDelegateModel.hpp | 2 +- src/BasicGraphicsScene.cpp | 34 +++++++++++++++++++ src/DataFlowGraphModel.cpp | 2 +- src/NodeDelegateModel.cpp | 4 +-- 6 files changed, 64 insertions(+), 7 deletions(-) diff --git a/examples/calculator/MathOperationDataModel.cpp b/examples/calculator/MathOperationDataModel.cpp index f6a3a26d0..0151d0476 100644 --- a/examples/calculator/MathOperationDataModel.cpp +++ b/examples/calculator/MathOperationDataModel.cpp @@ -1,6 +1,6 @@ #include "MathOperationDataModel.hpp" - #include "DecimalData.hpp" +#include unsigned int MathOperationDataModel::nPorts(PortType portType) const { @@ -38,5 +38,16 @@ void MathOperationDataModel::setInData(std::shared_ptr data, PortIndex _number2 = numberData; } - compute(); + Q_EMIT computingStarted(); + + QTimer *timer = new QTimer(this); + timer->start(1000); + int secondsRemaining = 10; + connect(timer, &QTimer::timeout, this, [=]() mutable { + if (--secondsRemaining <= 0) { + timer->stop(); + compute(); + Q_EMIT computingFinished(); + } + }); } diff --git a/examples/calculator/MultiplicationModel.hpp b/examples/calculator/MultiplicationModel.hpp index 127ae4011..40f9ffc62 100644 --- a/examples/calculator/MultiplicationModel.hpp +++ b/examples/calculator/MultiplicationModel.hpp @@ -1,7 +1,7 @@ #pragma once #include - +#include #include #include @@ -29,6 +29,18 @@ class MultiplicationModel : public MathOperationDataModel auto n1 = _number1.lock(); auto n2 = _number2.lock(); + // Dentro da sua classe: + QTimer *timer = new QTimer(this); + timer->start(1000); // 1000ms = 1 segundo + int secondsRemaining = 10; + connect(timer, &QTimer::timeout, this, [=]() mutable { + if (--secondsRemaining <= 0) { + timer->stop(); + // Faça algo quando o timer acabar + } + // Atualize sua UI aqui, ex: label->setText(QString::number(secondsRemaining)); + }); + if (n1 && n2) { //modelValidationState = NodeValidationState::Valid; //modelValidationError = QString(); diff --git a/include/QtNodes/internal/NodeDelegateModel.hpp b/include/QtNodes/internal/NodeDelegateModel.hpp index e4b13fe13..0637451c7 100644 --- a/include/QtNodes/internal/NodeDelegateModel.hpp +++ b/include/QtNodes/internal/NodeDelegateModel.hpp @@ -107,7 +107,7 @@ class NODE_EDITOR_PUBLIC NodeDelegateModel void setNodeStyle(NodeStyle const &style); - void setNodeProcessingStatus(NodeProcessingStatus status); + void setNodeProcessingStatus(NodeProcessingStatus::Status status); public: virtual void setInData(std::shared_ptr nodeData, PortIndex const portIndex) = 0; diff --git a/src/BasicGraphicsScene.cpp b/src/BasicGraphicsScene.cpp index 9009071a0..3571cdcce 100644 --- a/src/BasicGraphicsScene.cpp +++ b/src/BasicGraphicsScene.cpp @@ -278,6 +278,40 @@ void BasicGraphicsScene::onNodeCreated(NodeId const nodeId) { _nodeGraphicsObjects[nodeId] = std::make_unique(*this, nodeId); + //auto processing_status = NodeDelegateModel::NodeProcessingStatus() + + auto *dfModel = dynamic_cast(&_graphModel); + if (dfModel) { + if (auto *delegate = dfModel->delegateModel(nodeId)) { + connect(delegate, &NodeDelegateModel::computingStarted, this, [this, nodeId]() { + if (auto *df = dynamic_cast(&_graphModel)) + if (auto *d = df->delegateModel(nodeId)) + d->setNodeProcessingStatus( + NodeDelegateModel::NodeProcessingStatus::Status::Processing); + if (auto ngo = nodeGraphicsObject(nodeId)) + ngo->update(); + }); + connect(delegate, &NodeDelegateModel::computingFinished, this, [this, nodeId]() { + if (auto *df = dynamic_cast(&_graphModel)) + if (auto *d = df->delegateModel(nodeId)) + d->setNodeProcessingStatus( + NodeDelegateModel::NodeProcessingStatus::Status::NoStatus); + if (auto ngo = nodeGraphicsObject(nodeId)) + ngo->update(); + }); + /* + connect(delegate, &NodeDelegateModel::computingFinished, this, [this, nodeId]() { + if (auto *df = dynamic_cast(&_graphModel)) + if (auto *d = df->delegateModel(nodeId)) + d->setNodeProcessingStatus( + NodeDelegateModel::NodeProcessingStatus::Status::NoStatus); + if (auto ngo = nodeGraphicsObject(nodeId)) + ngo->update(); + }); +*/ + } + } + Q_EMIT modified(this); } diff --git a/src/DataFlowGraphModel.cpp b/src/DataFlowGraphModel.cpp index 0f173cfdc..5c2210be7 100644 --- a/src/DataFlowGraphModel.cpp +++ b/src/DataFlowGraphModel.cpp @@ -320,7 +320,7 @@ bool DataFlowGraphModel::setNodeData(NodeId nodeId, NodeRole role, QVariant valu if (value.canConvert()) { auto status = value.value(); if (auto node = delegateModel(nodeId); node != nullptr) { - node->setNodeProcessingStatus(status); + node->setNodeProcessingStatus(status._status); } } Q_EMIT nodeUpdated(nodeId); diff --git a/src/NodeDelegateModel.cpp b/src/NodeDelegateModel.cpp index 834976a34..0079d351b 100644 --- a/src/NodeDelegateModel.cpp +++ b/src/NodeDelegateModel.cpp @@ -56,9 +56,9 @@ void NodeDelegateModel::setNodeStyle(NodeStyle const &style) _nodeStyle = style; } -void NodeDelegateModel::setNodeProcessingStatus(NodeProcessingStatus status) +void NodeDelegateModel::setNodeProcessingStatus(NodeProcessingStatus::Status status) { - _processingStatus = status; + _processingStatus._status = status; } } // namespace QtNodes From 985b6388504fc49d823947f11cd0519f3067fddd Mon Sep 17 00:00:00 2001 From: Taiguara Tupinambas Date: Tue, 15 Jul 2025 19:40:10 -0300 Subject: [PATCH 07/31] fixes nodeprocessingstatus cast --- examples/calculator/MultiplicationModel.hpp | 25 ++++++++++----------- src/BasicGraphicsScene.cpp | 2 +- src/NodeGraphicsObject.cpp | 7 +++--- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/examples/calculator/MultiplicationModel.hpp b/examples/calculator/MultiplicationModel.hpp index 40f9ffc62..1fb13aa82 100644 --- a/examples/calculator/MultiplicationModel.hpp +++ b/examples/calculator/MultiplicationModel.hpp @@ -32,25 +32,24 @@ class MultiplicationModel : public MathOperationDataModel // Dentro da sua classe: QTimer *timer = new QTimer(this); timer->start(1000); // 1000ms = 1 segundo - int secondsRemaining = 10; + int secondsRemaining = 3; connect(timer, &QTimer::timeout, this, [=]() mutable { if (--secondsRemaining <= 0) { timer->stop(); + if (n1 && n2) { + //modelValidationState = NodeValidationState::Valid; + //modelValidationError = QString(); + _result = std::make_shared(n1->number() * n2->number()); + } else { + //modelValidationState = NodeValidationState::Warning; + //modelValidationError = QStringLiteral("Missing or incorrect inputs"); + _result.reset(); + } + + Q_EMIT dataUpdated(outPortIndex); // Faça algo quando o timer acabar } // Atualize sua UI aqui, ex: label->setText(QString::number(secondsRemaining)); }); - - if (n1 && n2) { - //modelValidationState = NodeValidationState::Valid; - //modelValidationError = QString(); - _result = std::make_shared(n1->number() * n2->number()); - } else { - //modelValidationState = NodeValidationState::Warning; - //modelValidationError = QStringLiteral("Missing or incorrect inputs"); - _result.reset(); - } - - Q_EMIT dataUpdated(outPortIndex); } }; diff --git a/src/BasicGraphicsScene.cpp b/src/BasicGraphicsScene.cpp index 3571cdcce..8a5a93fe1 100644 --- a/src/BasicGraphicsScene.cpp +++ b/src/BasicGraphicsScene.cpp @@ -295,7 +295,7 @@ void BasicGraphicsScene::onNodeCreated(NodeId const nodeId) if (auto *df = dynamic_cast(&_graphModel)) if (auto *d = df->delegateModel(nodeId)) d->setNodeProcessingStatus( - NodeDelegateModel::NodeProcessingStatus::Status::NoStatus); + NodeDelegateModel::NodeProcessingStatus::Status::Updated); if (auto ngo = nodeGraphicsObject(nodeId)) ngo->update(); }); diff --git a/src/NodeGraphicsObject.cpp b/src/NodeGraphicsObject.cpp index a558cc3b9..435957e58 100644 --- a/src/NodeGraphicsObject.cpp +++ b/src/NodeGraphicsObject.cpp @@ -392,8 +392,8 @@ void NodeGraphicsObject::contextMenuEvent(QGraphicsSceneContextMenuEvent *event) void NodeGraphicsObject::updateStatusIconSize() const { QVariant var = _graphModel.nodeData(_nodeId, NodeRole::ProcessingStatus); - auto processingStatusValue = var.value() - ._status; + auto processingStatusValue + = var.value(); bool oldStatus = _statusIconActive; _statusIconActive = processingStatusValue @@ -425,8 +425,9 @@ const QIcon NodeGraphicsObject::processingStatusIcon() const { QVariant var = _graphModel.nodeData(_nodeId, NodeRole::ProcessingStatus); - switch (var.value()._status) { + switch (var.value()) { case QtNodes::NodeDelegateModel::NodeProcessingStatus::Status::NoStatus: + return QIcon(); case QtNodes::NodeDelegateModel::NodeProcessingStatus::Status::Updated: _status_color = QColor("green"); return _statusUpdated; From 3f55e35bd856f388a09bd902bc807187d9422e44 Mon Sep 17 00:00:00 2001 From: g-abilio Date: Wed, 16 Jul 2025 11:51:17 -0300 Subject: [PATCH 08/31] creation of random gen example, and fix of icon color --- .../calculator/LongProcessingRandomNumber.hpp | 79 +++++++++++++++++++ .../calculator/MathOperationDataModel.cpp | 4 + .../calculator/MathOperationDataModel.hpp | 2 +- examples/calculator/MultiplicationModel.hpp | 14 +++- examples/calculator/headless_main.cpp | 3 + examples/calculator/main.cpp | 3 + .../QtNodes/internal/NodeGraphicsObject.hpp | 4 - src/BasicGraphicsScene.cpp | 34 -------- src/DefaultNodePainter.cpp | 7 -- src/NodeGraphicsObject.cpp | 6 -- 10 files changed, 103 insertions(+), 53 deletions(-) create mode 100644 examples/calculator/LongProcessingRandomNumber.hpp diff --git a/examples/calculator/LongProcessingRandomNumber.hpp b/examples/calculator/LongProcessingRandomNumber.hpp new file mode 100644 index 000000000..b10411804 --- /dev/null +++ b/examples/calculator/LongProcessingRandomNumber.hpp @@ -0,0 +1,79 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "MathOperationDataModel.hpp" +#include "DecimalData.hpp" + +/// The model generates a random value in a long processing schema, +/// as it should demonstrate the usage of the NodeProcessingStatus. +/// The random number is generate in the [n1, n2] interval. +class RandomNumberModel : public MathOperationDataModel +{ +public: + RandomNumberModel() { + QObject::connect(this, &NodeDelegateModel::computingStarted, this, [this]() { + //auto n1 = _number1.lock()->number(); + //auto n2 = _number2.lock()->number(); + + //if (n1 > n2) { + // this->setNodeProcessingStatus( + // NodeDelegateModel::NodeProcessingStatus::Status::Failed); + //} else { + this->setNodeProcessingStatus( + NodeDelegateModel::NodeProcessingStatus::Status::Processing); + //} + }); + QObject::connect(this, &NodeDelegateModel::computingFinished, this, [this]() { + this->setNodeProcessingStatus( + NodeDelegateModel::NodeProcessingStatus::Status::Updated); + }); + } + virtual ~RandomNumberModel() {} + +public: + QString caption() const override { return QStringLiteral("Random Number"); } + + QString name() const override { return QStringLiteral("Random Number"); } + +private: + void compute() override + { + Q_EMIT computingStarted(); + PortIndex const outPortIndex = 0; + + auto n1 = _number1.lock(); + auto n2 = _number2.lock(); + + QTimer *timer = new QTimer(this); + timer->start(1000); + int secondsRemaining = 3; + connect(timer, &QTimer::timeout, this, [=]() mutable { + if (--secondsRemaining <= 0) { + timer->stop(); + if (n1 && n2) { + double a = n1->number(); + double b = n2->number(); + + if (a > b) + std::swap(a, b); + + double upper = std::nextafter(b, std::numeric_limits::max()); + double randomValue = QRandomGenerator::global()->generateDouble() * (upper - a) + a; + + _result = std::make_shared(randomValue); + Q_EMIT computingFinished(); + } else { + _result.reset(); + } + + Q_EMIT dataUpdated(outPortIndex); + } + }); + } +}; diff --git a/examples/calculator/MathOperationDataModel.cpp b/examples/calculator/MathOperationDataModel.cpp index 0151d0476..dc166311b 100644 --- a/examples/calculator/MathOperationDataModel.cpp +++ b/examples/calculator/MathOperationDataModel.cpp @@ -38,6 +38,7 @@ void MathOperationDataModel::setInData(std::shared_ptr data, PortIndex _number2 = numberData; } + /* Q_EMIT computingStarted(); QTimer *timer = new QTimer(this); @@ -50,4 +51,7 @@ void MathOperationDataModel::setInData(std::shared_ptr data, PortIndex Q_EMIT computingFinished(); } }); + */ + + compute(); } diff --git a/examples/calculator/MathOperationDataModel.hpp b/examples/calculator/MathOperationDataModel.hpp index 6d279642b..16898a8c7 100644 --- a/examples/calculator/MathOperationDataModel.hpp +++ b/examples/calculator/MathOperationDataModel.hpp @@ -34,7 +34,7 @@ class MathOperationDataModel : public NodeDelegateModel void setInData(std::shared_ptr data, PortIndex portIndex) override; - QWidget *embeddedWidget() override { return nullptr; } + QWidget *embeddedWidget() override { return nullptr; } protected: virtual void compute() = 0; diff --git a/examples/calculator/MultiplicationModel.hpp b/examples/calculator/MultiplicationModel.hpp index 1fb13aa82..7349e2584 100644 --- a/examples/calculator/MultiplicationModel.hpp +++ b/examples/calculator/MultiplicationModel.hpp @@ -1,12 +1,12 @@ #pragma once #include +#include #include #include #include #include "MathOperationDataModel.hpp" - #include "DecimalData.hpp" /// The model dictates the number of inputs and outputs for the Node. @@ -14,6 +14,18 @@ class MultiplicationModel : public MathOperationDataModel { public: + MultiplicationModel() { + /* + QObject::connect(this, &NodeDelegateModel::computingStarted, this, [this]() { + this->setNodeProcessingStatus( + NodeDelegateModel::NodeProcessingStatus::Status::Processing); + }); + QObject::connect(this, &NodeDelegateModel::computingFinished, this, [this]() { + this->setNodeProcessingStatus( + NodeDelegateModel::NodeProcessingStatus::Status::Updated); + }); +*/ + } virtual ~MultiplicationModel() {} public: diff --git a/examples/calculator/headless_main.cpp b/examples/calculator/headless_main.cpp index e4dafa022..89103a4f2 100644 --- a/examples/calculator/headless_main.cpp +++ b/examples/calculator/headless_main.cpp @@ -1,5 +1,6 @@ #include "AdditionModel.hpp" #include "DivisionModel.hpp" +#include "LongProcessingRandomNumber.hpp" #include "MultiplicationModel.hpp" #include "NumberDisplayDataModel.hpp" #include "NumberSourceDataModel.hpp" @@ -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..4d2504f02 100644 --- a/examples/calculator/main.cpp +++ b/examples/calculator/main.cpp @@ -14,6 +14,7 @@ #include "AdditionModel.hpp" #include "DivisionModel.hpp" +#include "LongProcessingRandomNumber.hpp" #include "MultiplicationModel.hpp" #include "NumberDisplayDataModel.hpp" #include "NumberSourceDataModel.hpp" @@ -40,6 +41,8 @@ static std::shared_ptr registerDataModels() ret->registerModel("Operators"); + ret->registerModel("Operators"); + return ret; } diff --git a/include/QtNodes/internal/NodeGraphicsObject.hpp b/include/QtNodes/internal/NodeGraphicsObject.hpp index f24f81d4e..27de9e5a3 100644 --- a/include/QtNodes/internal/NodeGraphicsObject.hpp +++ b/include/QtNodes/internal/NodeGraphicsObject.hpp @@ -62,8 +62,6 @@ class NodeGraphicsObject : public QGraphicsObject QSize statusIconSize() const; - QColor getStatusColor() { return _status_color; } - protected: void paint(QPainter *painter, QStyleOptionGraphicsItem const *option, @@ -106,8 +104,6 @@ class NodeGraphicsObject : public QGraphicsObject mutable QSize _statusIconSize; - mutable QColor _status_color; - const QIcon _statusUpdated{"://status_icons/updated.svg"}; const QIcon _statusProcessing{"://status_icons/processing.svg"}; const QIcon _statusPending{"://status_icons/pending.svg"}; diff --git a/src/BasicGraphicsScene.cpp b/src/BasicGraphicsScene.cpp index 8a5a93fe1..9009071a0 100644 --- a/src/BasicGraphicsScene.cpp +++ b/src/BasicGraphicsScene.cpp @@ -278,40 +278,6 @@ void BasicGraphicsScene::onNodeCreated(NodeId const nodeId) { _nodeGraphicsObjects[nodeId] = std::make_unique(*this, nodeId); - //auto processing_status = NodeDelegateModel::NodeProcessingStatus() - - auto *dfModel = dynamic_cast(&_graphModel); - if (dfModel) { - if (auto *delegate = dfModel->delegateModel(nodeId)) { - connect(delegate, &NodeDelegateModel::computingStarted, this, [this, nodeId]() { - if (auto *df = dynamic_cast(&_graphModel)) - if (auto *d = df->delegateModel(nodeId)) - d->setNodeProcessingStatus( - NodeDelegateModel::NodeProcessingStatus::Status::Processing); - if (auto ngo = nodeGraphicsObject(nodeId)) - ngo->update(); - }); - connect(delegate, &NodeDelegateModel::computingFinished, this, [this, nodeId]() { - if (auto *df = dynamic_cast(&_graphModel)) - if (auto *d = df->delegateModel(nodeId)) - d->setNodeProcessingStatus( - NodeDelegateModel::NodeProcessingStatus::Status::Updated); - if (auto ngo = nodeGraphicsObject(nodeId)) - ngo->update(); - }); - /* - connect(delegate, &NodeDelegateModel::computingFinished, this, [this, nodeId]() { - if (auto *df = dynamic_cast(&_graphModel)) - if (auto *d = df->delegateModel(nodeId)) - d->setNodeProcessingStatus( - NodeDelegateModel::NodeProcessingStatus::Status::NoStatus); - if (auto ngo = nodeGraphicsObject(nodeId)) - ngo->update(); - }); -*/ - } - } - Q_EMIT modified(this); } diff --git a/src/DefaultNodePainter.cpp b/src/DefaultNodePainter.cpp index 60d2115f6..8848ac3f7 100644 --- a/src/DefaultNodePainter.cpp +++ b/src/DefaultNodePainter.cpp @@ -321,13 +321,6 @@ void DefaultNodePainter::drawProcessingIndicator(QPainter *painter, NodeGraphics QSize iconSize(16, 16); QPixmap pixmap = icon.pixmap(iconSize); - QColor color = ngo.getStatusColor(); - - QPainter imgPainter(&pixmap); - imgPainter.setCompositionMode(QPainter::CompositionMode_SourceIn); - imgPainter.fillRect(pixmap.rect(), color); - imgPainter.end(); - QRect r(size.width() - 12.0, size.height() - 12.0, 8.0, 8.0); painter->drawPixmap(r, pixmap); } diff --git a/src/NodeGraphicsObject.cpp b/src/NodeGraphicsObject.cpp index 435957e58..fe50dfddf 100644 --- a/src/NodeGraphicsObject.cpp +++ b/src/NodeGraphicsObject.cpp @@ -429,22 +429,16 @@ const QIcon NodeGraphicsObject::processingStatusIcon() const case QtNodes::NodeDelegateModel::NodeProcessingStatus::Status::NoStatus: return QIcon(); case QtNodes::NodeDelegateModel::NodeProcessingStatus::Status::Updated: - _status_color = QColor("green"); return _statusUpdated; case QtNodes::NodeDelegateModel::NodeProcessingStatus::Status::Processing: - _status_color = QColor("blue"); return _statusProcessing; case QtNodes::NodeDelegateModel::NodeProcessingStatus::Status::Pending: - _status_color = QColor("yellow"); return _statusPending; case QtNodes::NodeDelegateModel::NodeProcessingStatus::Status::Empty: - _status_color = QColor("gray"); return _statusEmpty; case QtNodes::NodeDelegateModel::NodeProcessingStatus::Status::Failed: - _status_color = QColor("red"); return _statusInvalid; case QtNodes::NodeDelegateModel::NodeProcessingStatus::Status::Partial: - _status_color = QColor("white"); return _statusPartial; } return _statusInvalid; From 50c6bec1df1eb463e4155bc87e0ffcd00b693e64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Taiguara=20Tupinamb=C3=A1s?= Date: Thu, 17 Jul 2025 11:46:09 -0300 Subject: [PATCH 09/31] Connect delegate UI update signal --- include/QtNodes/internal/NodeDelegateModel.hpp | 8 ++++++++ src/DataFlowGraphModel.cpp | 7 +++++++ 2 files changed, 15 insertions(+) diff --git a/include/QtNodes/internal/NodeDelegateModel.hpp b/include/QtNodes/internal/NodeDelegateModel.hpp index 0637451c7..296a8b642 100644 --- a/include/QtNodes/internal/NodeDelegateModel.hpp +++ b/include/QtNodes/internal/NodeDelegateModel.hpp @@ -152,6 +152,14 @@ public Q_SLOTS: void embeddedWidgetSizeUpdated(); + /// Request an update of the node's UI. + /** + * Emit this signal whenever some internal state change requires + * the node to be repainted. The containing graph model will + * propagate the update to the scene. + */ + void requestNodeUpdate(); + /// Call this function before deleting the data associated with ports. /** * The function notifies the Graph Model and makes it remove and recompute the diff --git a/src/DataFlowGraphModel.cpp b/src/DataFlowGraphModel.cpp index 5c2210be7..0387b4c40 100644 --- a/src/DataFlowGraphModel.cpp +++ b/src/DataFlowGraphModel.cpp @@ -93,6 +93,10 @@ NodeId DataFlowGraphModel::addNode(QString const nodeType) this, &DataFlowGraphModel::portsInserted); + connect(model.get(), &NodeDelegateModel::requestNodeUpdate, this, [newId, this]() { + Q_EMIT nodeUpdated(newId); + }); + _models[newId] = std::move(model); Q_EMIT nodeCreated(newId); @@ -528,6 +532,9 @@ void DataFlowGraphModel::loadNode(QJsonObject const &nodeJson) &NodeDelegateModel::portsInserted, this, &DataFlowGraphModel::portsInserted); + connect(model.get(), &NodeDelegateModel::requestNodeUpdate, this, [restoredNodeId, this]() { + Q_EMIT nodeUpdated(restoredNodeId); + }); _models[restoredNodeId] = std::move(model); From 1c09ecf35448013dbed37d60728337d5c1f707c3 Mon Sep 17 00:00:00 2001 From: g-abilio Date: Thu, 17 Jul 2025 15:24:50 -0300 Subject: [PATCH 10/31] fix random number node dynamic --- .../calculator/LongProcessingRandomNumber.hpp | 23 +++++++++---------- .../calculator/MathOperationDataModel.cpp | 15 ------------ 2 files changed, 11 insertions(+), 27 deletions(-) diff --git a/examples/calculator/LongProcessingRandomNumber.hpp b/examples/calculator/LongProcessingRandomNumber.hpp index b10411804..87ad3fab0 100644 --- a/examples/calculator/LongProcessingRandomNumber.hpp +++ b/examples/calculator/LongProcessingRandomNumber.hpp @@ -1,7 +1,6 @@ #pragma once #include -#include #include #include #include @@ -18,20 +17,17 @@ class RandomNumberModel : public MathOperationDataModel public: RandomNumberModel() { QObject::connect(this, &NodeDelegateModel::computingStarted, this, [this]() { - //auto n1 = _number1.lock()->number(); - //auto n2 = _number2.lock()->number(); - - //if (n1 > n2) { - // this->setNodeProcessingStatus( - // NodeDelegateModel::NodeProcessingStatus::Status::Failed); - //} else { - this->setNodeProcessingStatus( + this->setNodeProcessingStatus( NodeDelegateModel::NodeProcessingStatus::Status::Processing); - //} + + emit requestNodeUpdate(); + }); QObject::connect(this, &NodeDelegateModel::computingFinished, this, [this]() { this->setNodeProcessingStatus( NodeDelegateModel::NodeProcessingStatus::Status::Updated); + + emit requestNodeUpdate(); }); } virtual ~RandomNumberModel() {} @@ -60,8 +56,11 @@ class RandomNumberModel : public MathOperationDataModel double a = n1->number(); double b = n2->number(); - if (a > b) - std::swap(a, b); + if (a > b) { + this->setNodeProcessingStatus(NodeDelegateModel::NodeProcessingStatus::Status::Failed); + emit requestNodeUpdate(); + return; + } double upper = std::nextafter(b, std::numeric_limits::max()); double randomValue = QRandomGenerator::global()->generateDouble() * (upper - a) + a; diff --git a/examples/calculator/MathOperationDataModel.cpp b/examples/calculator/MathOperationDataModel.cpp index dc166311b..52528af0a 100644 --- a/examples/calculator/MathOperationDataModel.cpp +++ b/examples/calculator/MathOperationDataModel.cpp @@ -38,20 +38,5 @@ void MathOperationDataModel::setInData(std::shared_ptr data, PortIndex _number2 = numberData; } - /* - Q_EMIT computingStarted(); - - QTimer *timer = new QTimer(this); - timer->start(1000); - int secondsRemaining = 10; - connect(timer, &QTimer::timeout, this, [=]() mutable { - if (--secondsRemaining <= 0) { - timer->stop(); - compute(); - Q_EMIT computingFinished(); - } - }); - */ - compute(); } From f2c120f1a0a82fbb0b7da8028ce19007dfb57966 Mon Sep 17 00:00:00 2001 From: g-abilio Date: Thu, 17 Jul 2025 15:35:48 -0300 Subject: [PATCH 11/31] clean up test code in multiplication node --- examples/calculator/MultiplicationModel.hpp | 45 +++++---------------- 1 file changed, 11 insertions(+), 34 deletions(-) diff --git a/examples/calculator/MultiplicationModel.hpp b/examples/calculator/MultiplicationModel.hpp index 7349e2584..7d63534ba 100644 --- a/examples/calculator/MultiplicationModel.hpp +++ b/examples/calculator/MultiplicationModel.hpp @@ -14,18 +14,6 @@ class MultiplicationModel : public MathOperationDataModel { public: - MultiplicationModel() { - /* - QObject::connect(this, &NodeDelegateModel::computingStarted, this, [this]() { - this->setNodeProcessingStatus( - NodeDelegateModel::NodeProcessingStatus::Status::Processing); - }); - QObject::connect(this, &NodeDelegateModel::computingFinished, this, [this]() { - this->setNodeProcessingStatus( - NodeDelegateModel::NodeProcessingStatus::Status::Updated); - }); -*/ - } virtual ~MultiplicationModel() {} public: @@ -41,27 +29,16 @@ class MultiplicationModel : public MathOperationDataModel auto n1 = _number1.lock(); auto n2 = _number2.lock(); - // Dentro da sua classe: - QTimer *timer = new QTimer(this); - timer->start(1000); // 1000ms = 1 segundo - int secondsRemaining = 3; - connect(timer, &QTimer::timeout, this, [=]() mutable { - if (--secondsRemaining <= 0) { - timer->stop(); - if (n1 && n2) { - //modelValidationState = NodeValidationState::Valid; - //modelValidationError = QString(); - _result = std::make_shared(n1->number() * n2->number()); - } else { - //modelValidationState = NodeValidationState::Warning; - //modelValidationError = QStringLiteral("Missing or incorrect inputs"); - _result.reset(); - } - - Q_EMIT dataUpdated(outPortIndex); - // Faça algo quando o timer acabar - } - // Atualize sua UI aqui, ex: label->setText(QString::number(secondsRemaining)); - }); + if (n1 && n2) { + //modelValidationState = NodeValidationState::Valid; + //modelValidationError = QString(); + _result = std::make_shared(n1->number() * n2->number()); + } else { + //modelValidationState = NodeValidationState::Warning; + //modelValidationError = QStringLiteral("Missing or incorrect inputs"); + _result.reset(); + } + + Q_EMIT dataUpdated(outPortIndex); } }; From 965eb1598f6912a2eefcbd49ed67c7617839d54d Mon Sep 17 00:00:00 2001 From: Hudson Miranda Date: Tue, 22 Jul 2025 15:42:27 -0300 Subject: [PATCH 12/31] Add per-node background color API --- include/QtNodes/internal/NodeDelegateModel.hpp | 4 ++++ include/QtNodes/internal/NodeStyle.hpp | 6 ++++++ include/QtNodes/internal/UndoCommands.hpp | 2 +- src/NodeDelegateModel.cpp | 5 +++++ src/NodeStyle.cpp | 13 +++++++++++++ 5 files changed, 29 insertions(+), 1 deletion(-) diff --git a/include/QtNodes/internal/NodeDelegateModel.hpp b/include/QtNodes/internal/NodeDelegateModel.hpp index 6301164db..87669d12a 100644 --- a/include/QtNodes/internal/NodeDelegateModel.hpp +++ b/include/QtNodes/internal/NodeDelegateModel.hpp @@ -8,6 +8,7 @@ #include "Export.hpp" #include "NodeData.hpp" #include "NodeStyle.hpp" +#include #include "Serializable.hpp" namespace QtNodes { @@ -61,6 +62,9 @@ class NODE_EDITOR_PUBLIC NodeDelegateModel : public QObject, public Serializable void setNodeStyle(NodeStyle const &style); + /// Convenience helper to change the node background color. + void setBackgroundColor(QColor const &color); + public: virtual void setInData(std::shared_ptr nodeData, PortIndex const portIndex) = 0; diff --git a/include/QtNodes/internal/NodeStyle.hpp b/include/QtNodes/internal/NodeStyle.hpp index 85abc5612..1f25355d6 100644 --- a/include/QtNodes/internal/NodeStyle.hpp +++ b/include/QtNodes/internal/NodeStyle.hpp @@ -26,6 +26,12 @@ class NODE_EDITOR_PUBLIC NodeStyle : public Style QJsonObject toJson() const override; + /// Set uniform background color for the node. + void setBackgroundColor(QColor const &color); + + /// Current uniform background color. + QColor backgroundColor() const; + public: QColor NormalBoundaryColor; QColor SelectedBoundaryColor; diff --git a/include/QtNodes/internal/UndoCommands.hpp b/include/QtNodes/internal/UndoCommands.hpp index b29c36ab5..77e2a4f09 100644 --- a/include/QtNodes/internal/UndoCommands.hpp +++ b/include/QtNodes/internal/UndoCommands.hpp @@ -5,7 +5,7 @@ #include #include -#include +#include #include diff --git a/src/NodeDelegateModel.cpp b/src/NodeDelegateModel.cpp index 94e47ad68..b267b58c8 100644 --- a/src/NodeDelegateModel.cpp +++ b/src/NodeDelegateModel.cpp @@ -51,4 +51,9 @@ void NodeDelegateModel::setNodeStyle(NodeStyle const &style) _nodeStyle = style; } +void NodeDelegateModel::setBackgroundColor(QColor const &color) +{ + _nodeStyle.setBackgroundColor(color); +} + } // namespace QtNodes diff --git a/src/NodeStyle.cpp b/src/NodeStyle.cpp index 41b872bba..ddb25b0c1 100644 --- a/src/NodeStyle.cpp +++ b/src/NodeStyle.cpp @@ -158,3 +158,16 @@ QJsonObject NodeStyle::toJson() const return root; } + +void NodeStyle::setBackgroundColor(QColor const &color) +{ + GradientColor0 = color; + GradientColor1 = color; + GradientColor2 = color; + GradientColor3 = color; +} + +QColor NodeStyle::backgroundColor() const +{ + return GradientColor0; +} From c34e6e374b13da5d3301ca622dbea52ed986248d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Taiguara=20Tupinamb=C3=A1s?= Date: Wed, 6 Aug 2025 13:32:36 -0300 Subject: [PATCH 13/31] adds version to Catch2 due to compatibility issues --- external/CMakeLists.txt | 2 +- test/CMakeLists.txt | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt index d7fbfcca7..0d39af5c9 100644 --- a/external/CMakeLists.txt +++ b/external/CMakeLists.txt @@ -1,5 +1,5 @@ if(BUILD_TESTING) - find_package(Catch2 QUIET) + find_package(Catch2 2.13.7 QUIET) if(NOT Catch2_FOUND) add_subdirectory(Catch2) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 681251330..98a8c306a 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -4,6 +4,8 @@ else() find_package(Qt5 COMPONENTS Test) endif() +find_package(Catch2) + add_executable(test_nodes test_main.cpp src/TestAbstractGraphModel.cpp From b23ecc3baf1fca937ded5dd67a74e73e3292eb21 Mon Sep 17 00:00:00 2001 From: Taiguara Tupinambas Date: Wed, 10 Sep 2025 15:32:48 -0300 Subject: [PATCH 14/31] revert unnecessary changes in multiplication model --- examples/calculator/MultiplicationModel.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/calculator/MultiplicationModel.hpp b/examples/calculator/MultiplicationModel.hpp index 7d63534ba..127ae4011 100644 --- a/examples/calculator/MultiplicationModel.hpp +++ b/examples/calculator/MultiplicationModel.hpp @@ -1,12 +1,12 @@ #pragma once #include -#include -#include + #include #include #include "MathOperationDataModel.hpp" + #include "DecimalData.hpp" /// The model dictates the number of inputs and outputs for the Node. From 0a35f766778f75982af5a0b4441133c43765f8d9 Mon Sep 17 00:00:00 2001 From: Taiguara Tupinambas Date: Wed, 10 Sep 2025 15:36:11 -0300 Subject: [PATCH 15/31] revert unnecessary changes --- examples/calculator/MathOperationDataModel.cpp | 2 +- examples/calculator/MathOperationDataModel.hpp | 2 +- include/QtNodes/internal/Definitions.hpp | 1 - include/QtNodes/internal/NodeGraphicsObject.hpp | 1 - src/BasicGraphicsScene.cpp | 1 - 5 files changed, 2 insertions(+), 5 deletions(-) diff --git a/examples/calculator/MathOperationDataModel.cpp b/examples/calculator/MathOperationDataModel.cpp index 52528af0a..f6a3a26d0 100644 --- a/examples/calculator/MathOperationDataModel.cpp +++ b/examples/calculator/MathOperationDataModel.cpp @@ -1,6 +1,6 @@ #include "MathOperationDataModel.hpp" + #include "DecimalData.hpp" -#include unsigned int MathOperationDataModel::nPorts(PortType portType) const { diff --git a/examples/calculator/MathOperationDataModel.hpp b/examples/calculator/MathOperationDataModel.hpp index 16898a8c7..6d279642b 100644 --- a/examples/calculator/MathOperationDataModel.hpp +++ b/examples/calculator/MathOperationDataModel.hpp @@ -34,7 +34,7 @@ class MathOperationDataModel : public NodeDelegateModel void setInData(std::shared_ptr data, PortIndex portIndex) override; - QWidget *embeddedWidget() override { return nullptr; } + QWidget *embeddedWidget() override { return nullptr; } protected: virtual void compute() = 0; diff --git a/include/QtNodes/internal/Definitions.hpp b/include/QtNodes/internal/Definitions.hpp index 6f66a1e4c..8c01475f9 100644 --- a/include/QtNodes/internal/Definitions.hpp +++ b/include/QtNodes/internal/Definitions.hpp @@ -12,7 +12,6 @@ */ namespace QtNodes { - #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) NODE_EDITOR_PUBLIC Q_NAMESPACE #else diff --git a/include/QtNodes/internal/NodeGraphicsObject.hpp b/include/QtNodes/internal/NodeGraphicsObject.hpp index b122f97c4..69eb2ba6b 100644 --- a/include/QtNodes/internal/NodeGraphicsObject.hpp +++ b/include/QtNodes/internal/NodeGraphicsObject.hpp @@ -1,6 +1,5 @@ #pragma once -#include #include #include #include diff --git a/src/BasicGraphicsScene.cpp b/src/BasicGraphicsScene.cpp index edd3eddb4..7cec5ec45 100644 --- a/src/BasicGraphicsScene.cpp +++ b/src/BasicGraphicsScene.cpp @@ -3,7 +3,6 @@ #include "AbstractNodeGeometry.hpp" #include "ConnectionGraphicsObject.hpp" #include "ConnectionIdUtils.hpp" -#include "DataFlowGraphModel.hpp" #include "DefaultConnectionPainter.hpp" #include "DefaultHorizontalNodeGeometry.hpp" #include "DefaultNodePainter.hpp" From a9be092b5eb36830023a285241e8a90ac0715aa1 Mon Sep 17 00:00:00 2001 From: g-abilio Date: Thu, 11 Sep 2025 19:27:06 -0300 Subject: [PATCH 16/31] solve icon size and refactor NodeProcessingStatus code --- .../calculator/LongProcessingRandomNumber.hpp | 6 +-- .../QtNodes/internal/NodeDelegateModel.hpp | 40 ++++++++----------- src/DataFlowGraphModel.cpp | 6 +-- src/DefaultNodePainter.cpp | 2 +- src/NodeDelegateModel.cpp | 4 +- src/NodeGraphicsObject.cpp | 28 ++++++------- 6 files changed, 37 insertions(+), 49 deletions(-) diff --git a/examples/calculator/LongProcessingRandomNumber.hpp b/examples/calculator/LongProcessingRandomNumber.hpp index 87ad3fab0..d6399ca03 100644 --- a/examples/calculator/LongProcessingRandomNumber.hpp +++ b/examples/calculator/LongProcessingRandomNumber.hpp @@ -18,14 +18,14 @@ class RandomNumberModel : public MathOperationDataModel RandomNumberModel() { QObject::connect(this, &NodeDelegateModel::computingStarted, this, [this]() { this->setNodeProcessingStatus( - NodeDelegateModel::NodeProcessingStatus::Status::Processing); + QtNodes::NodeProcessingStatus::Processing); emit requestNodeUpdate(); }); QObject::connect(this, &NodeDelegateModel::computingFinished, this, [this]() { this->setNodeProcessingStatus( - NodeDelegateModel::NodeProcessingStatus::Status::Updated); + QtNodes::NodeProcessingStatus::Updated); emit requestNodeUpdate(); }); @@ -57,7 +57,7 @@ class RandomNumberModel : public MathOperationDataModel double b = n2->number(); if (a > b) { - this->setNodeProcessingStatus(NodeDelegateModel::NodeProcessingStatus::Status::Failed); + setNodeProcessingStatus(QtNodes::NodeProcessingStatus::Failed); emit requestNodeUpdate(); return; } diff --git a/include/QtNodes/internal/NodeDelegateModel.hpp b/include/QtNodes/internal/NodeDelegateModel.hpp index 296a8b642..28666c8a7 100644 --- a/include/QtNodes/internal/NodeDelegateModel.hpp +++ b/include/QtNodes/internal/NodeDelegateModel.hpp @@ -27,6 +27,19 @@ struct NodeValidationState QString _stateMessage{""}; }; +/** + * Describes the node status, depending on its current situation + */ +enum class NodeProcessingStatus : int { + NoStatus = 0, /// + Updated = 1, /// + Processing = 2, /// + Pending = 3, /// + Empty = 4, /// + Failed = 5, /// + Partial = 6, /// +}; + class StyleCollection; /** @@ -46,24 +59,6 @@ class NODE_EDITOR_PUBLIC NodeDelegateModel virtual ~NodeDelegateModel() = default; - /** - * Describes the node status, depending on its current situation - */ - struct NodeProcessingStatus - { - enum class Status : int { - NoStatus = 0, /// - Updated = 1, /// - Processing = 2, /// - Pending = 3, /// - Empty = 4, /// - Failed = 5, /// - Partial = 6, /// - }; - - Status _status{Status::NoStatus}; - }; - /// It is possible to hide caption in GUI virtual bool captionVisible() const { return true; } @@ -83,10 +78,7 @@ class NODE_EDITOR_PUBLIC NodeDelegateModel virtual NodeValidationState validationState() const { return _nodeValidationState; } /// Returns the curent processing status - virtual NodeProcessingStatus::Status processingStatus() const - { - return _processingStatus._status; - } + virtual NodeProcessingStatus processingStatus() const { return _processingStatus; } public: QJsonObject save() const override; @@ -107,7 +99,7 @@ class NODE_EDITOR_PUBLIC NodeDelegateModel void setNodeStyle(NodeStyle const &style); - void setNodeProcessingStatus(NodeProcessingStatus::Status status); + void setNodeProcessingStatus(NodeProcessingStatus status); public: virtual void setInData(std::shared_ptr nodeData, PortIndex const portIndex) = 0; @@ -187,7 +179,7 @@ public Q_SLOTS: NodeValidationState _nodeValidationState; - NodeProcessingStatus _processingStatus; + NodeProcessingStatus _processingStatus = NodeProcessingStatus::Updated; }; } // namespace QtNodes diff --git a/src/DataFlowGraphModel.cpp b/src/DataFlowGraphModel.cpp index 0387b4c40..74600dace 100644 --- a/src/DataFlowGraphModel.cpp +++ b/src/DataFlowGraphModel.cpp @@ -321,10 +321,10 @@ bool DataFlowGraphModel::setNodeData(NodeId nodeId, NodeRole role, QVariant valu } break; case NodeRole::ProcessingStatus: { - if (value.canConvert()) { - auto status = value.value(); + if (value.canConvert()) { + auto status = value.value(); if (auto node = delegateModel(nodeId); node != nullptr) { - node->setNodeProcessingStatus(status._status); + node->setNodeProcessingStatus(status); } } Q_EMIT nodeUpdated(nodeId); diff --git a/src/DefaultNodePainter.cpp b/src/DefaultNodePainter.cpp index 8848ac3f7..7dfee358a 100644 --- a/src/DefaultNodePainter.cpp +++ b/src/DefaultNodePainter.cpp @@ -321,7 +321,7 @@ void DefaultNodePainter::drawProcessingIndicator(QPainter *painter, NodeGraphics QSize iconSize(16, 16); QPixmap pixmap = icon.pixmap(iconSize); - QRect r(size.width() - 12.0, size.height() - 12.0, 8.0, 8.0); + QRect r(size.width() - 28.0, size.height() - 28.0, 20.0, 20.0); painter->drawPixmap(r, pixmap); } diff --git a/src/NodeDelegateModel.cpp b/src/NodeDelegateModel.cpp index 0079d351b..834976a34 100644 --- a/src/NodeDelegateModel.cpp +++ b/src/NodeDelegateModel.cpp @@ -56,9 +56,9 @@ void NodeDelegateModel::setNodeStyle(NodeStyle const &style) _nodeStyle = style; } -void NodeDelegateModel::setNodeProcessingStatus(NodeProcessingStatus::Status status) +void NodeDelegateModel::setNodeProcessingStatus(NodeProcessingStatus status) { - _processingStatus._status = status; + _processingStatus = status; } } // namespace QtNodes diff --git a/src/NodeGraphicsObject.cpp b/src/NodeGraphicsObject.cpp index fe50dfddf..2938c3764 100644 --- a/src/NodeGraphicsObject.cpp +++ b/src/NodeGraphicsObject.cpp @@ -68,11 +68,9 @@ NodeGraphicsObject::NodeGraphicsObject(BasicGraphicsScene &scene, NodeId nodeId) }); QVariant var = _graphModel.nodeData(_nodeId, NodeRole::ProcessingStatus); - auto processingStatusValue = var.value() - ._status; + auto processingStatusValue = var.value(); - _statusIconActive = processingStatusValue - != QtNodes::NodeDelegateModel::NodeProcessingStatus::Status::NoStatus; + _statusIconActive = processingStatusValue != QtNodes::NodeProcessingStatus::NoStatus; _statusIconSize.setWidth(_statusIconActive ? 32 : 0); _statusIconSize.setHeight(_statusIconActive ? 32 : 0); } @@ -392,12 +390,10 @@ void NodeGraphicsObject::contextMenuEvent(QGraphicsSceneContextMenuEvent *event) void NodeGraphicsObject::updateStatusIconSize() const { QVariant var = _graphModel.nodeData(_nodeId, NodeRole::ProcessingStatus); - auto processingStatusValue - = var.value(); + auto processingStatusValue = var.value(); bool oldStatus = _statusIconActive; - _statusIconActive = processingStatusValue - != QtNodes::NodeDelegateModel::NodeProcessingStatus::Status::NoStatus; + _statusIconActive = processingStatusValue != QtNodes::NodeProcessingStatus::NoStatus; if (oldStatus != _statusIconActive) { _statusIconSize.setWidth(_statusIconActive ? 32 : 0); @@ -425,20 +421,20 @@ const QIcon NodeGraphicsObject::processingStatusIcon() const { QVariant var = _graphModel.nodeData(_nodeId, NodeRole::ProcessingStatus); - switch (var.value()) { - case QtNodes::NodeDelegateModel::NodeProcessingStatus::Status::NoStatus: + switch (var.value()) { + case QtNodes::NodeProcessingStatus::NoStatus: return QIcon(); - case QtNodes::NodeDelegateModel::NodeProcessingStatus::Status::Updated: + case QtNodes::NodeProcessingStatus::Updated: return _statusUpdated; - case QtNodes::NodeDelegateModel::NodeProcessingStatus::Status::Processing: + case QtNodes::NodeProcessingStatus::Processing: return _statusProcessing; - case QtNodes::NodeDelegateModel::NodeProcessingStatus::Status::Pending: + case QtNodes::NodeProcessingStatus::Pending: return _statusPending; - case QtNodes::NodeDelegateModel::NodeProcessingStatus::Status::Empty: + case QtNodes::NodeProcessingStatus::Empty: return _statusEmpty; - case QtNodes::NodeDelegateModel::NodeProcessingStatus::Status::Failed: + case QtNodes::NodeProcessingStatus::Failed: return _statusInvalid; - case QtNodes::NodeDelegateModel::NodeProcessingStatus::Status::Partial: + case QtNodes::NodeProcessingStatus::Partial: return _statusPartial; } return _statusInvalid; From 03e311a0d2fdefae5b84d3484b3d7a5af6cd7b90 Mon Sep 17 00:00:00 2001 From: g-abilio Date: Thu, 11 Sep 2025 20:10:10 -0300 Subject: [PATCH 17/31] remove duplicate code --- include/QtNodes/internal/NodeDelegateModel.hpp | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/include/QtNodes/internal/NodeDelegateModel.hpp b/include/QtNodes/internal/NodeDelegateModel.hpp index 434410a51..ca841e250 100644 --- a/include/QtNodes/internal/NodeDelegateModel.hpp +++ b/include/QtNodes/internal/NodeDelegateModel.hpp @@ -72,9 +72,6 @@ class NODE_EDITOR_PUBLIC NodeDelegateModel /// Caption is used in GUI virtual QString caption() const = 0; - /// It is possible to hide caption in GUI - virtual bool captionVisible() const { return true; } - /// Port caption is used in GUI to label individual ports virtual QString portCaption(PortType, PortIndex) const { return QString(); } @@ -93,7 +90,7 @@ class NODE_EDITOR_PUBLIC NodeDelegateModel void setValidationState(const NodeValidationState &validationState); - void setNodeProcessingStatus(NodeProcessingStatus::Status status); + void setNodeProcessingStatus(NodeProcessingStatus status); virtual unsigned int nPorts(PortType portType) const = 0; @@ -105,8 +102,6 @@ class NODE_EDITOR_PUBLIC NodeDelegateModel void setNodeStyle(NodeStyle const &style); - void setNodeProcessingStatus(NodeProcessingStatus status); - public: virtual void setInData(std::shared_ptr nodeData, PortIndex const portIndex) = 0; From a87ceb2162bebfb07a7d68f1385913a05a291a18 Mon Sep 17 00:00:00 2001 From: g-abilio Date: Mon, 15 Sep 2025 14:49:37 -0300 Subject: [PATCH 18/31] add space to better organize processing status in node display --- src/DefaultHorizontalNodeGeometry.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/DefaultHorizontalNodeGeometry.cpp b/src/DefaultHorizontalNodeGeometry.cpp index 8f3e07aad..a2102c641 100644 --- a/src/DefaultHorizontalNodeGeometry.cpp +++ b/src/DefaultHorizontalNodeGeometry.cpp @@ -1,7 +1,7 @@ #include "DefaultHorizontalNodeGeometry.hpp" - #include "AbstractGraphModel.hpp" #include "NodeData.hpp" +#include "NodeDelegateModel.hpp" #include #include @@ -55,6 +55,12 @@ void DefaultHorizontalNodeGeometry::recomputeSize(NodeId const nodeId) const height += _portSpasing; // space above caption height += _portSpasing; // space below caption + QVariant var = _graphModel.nodeData(nodeId, NodeRole::ProcessingStatus); + auto processingStatusValue = var.value(); + + if (processingStatusValue != QtNodes::NodeProcessingStatus::NoStatus) + height += 20; + unsigned int inPortWidth = maxPortsTextAdvance(nodeId, PortType::In); unsigned int outPortWidth = maxPortsTextAdvance(nodeId, PortType::Out); From ae3715baaa2818a8e30c5651d7bc4461b2b45c1c Mon Sep 17 00:00:00 2001 From: g-abilio Date: Fri, 19 Sep 2025 10:54:39 -0300 Subject: [PATCH 19/31] remove processing value default value --- include/QtNodes/internal/NodeDelegateModel.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/QtNodes/internal/NodeDelegateModel.hpp b/include/QtNodes/internal/NodeDelegateModel.hpp index ca841e250..bb8a8ddb4 100644 --- a/include/QtNodes/internal/NodeDelegateModel.hpp +++ b/include/QtNodes/internal/NodeDelegateModel.hpp @@ -176,7 +176,7 @@ public Q_SLOTS: NodeValidationState _nodeValidationState; - NodeProcessingStatus _processingStatus = NodeProcessingStatus::Updated; + NodeProcessingStatus _processingStatus; }; } // namespace QtNodes From eedc666eabfcd46eb803a74b876acf777e360a27 Mon Sep 17 00:00:00 2001 From: g-abilio Date: Fri, 19 Sep 2025 11:37:27 -0300 Subject: [PATCH 20/31] fix bugs in node processing status --- examples/calculator/LongProcessingRandomNumber.hpp | 6 +++++- examples/calculator/NumberDisplayDataModel.cpp | 4 +++- examples/calculator/NumberSourceDataModel.cpp | 4 +++- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/examples/calculator/LongProcessingRandomNumber.hpp b/examples/calculator/LongProcessingRandomNumber.hpp index 431ecdd17..8be2882ca 100644 --- a/examples/calculator/LongProcessingRandomNumber.hpp +++ b/examples/calculator/LongProcessingRandomNumber.hpp @@ -16,9 +16,13 @@ class RandomNumberModel : public MathOperationDataModel { public: RandomNumberModel() { + this->setNodeProcessingStatus(QtNodes::NodeProcessingStatus::Empty); + QObject::connect(this, &NodeDelegateModel::computingStarted, this, [this]() { - this->setNodeProcessingStatus( + if (_number1.lock() && _number2.lock()) { + this->setNodeProcessingStatus( QtNodes::NodeProcessingStatus::Processing); + } emit requestNodeUpdate(); }); diff --git a/examples/calculator/NumberDisplayDataModel.cpp b/examples/calculator/NumberDisplayDataModel.cpp index 5086050c8..6f58f5854 100644 --- a/examples/calculator/NumberDisplayDataModel.cpp +++ b/examples/calculator/NumberDisplayDataModel.cpp @@ -4,7 +4,9 @@ NumberDisplayDataModel::NumberDisplayDataModel() : _label{nullptr} -{} +{ + this->setNodeProcessingStatus(QtNodes::NodeProcessingStatus::NoStatus); +} unsigned int NumberDisplayDataModel::nPorts(PortType portType) const { diff --git a/examples/calculator/NumberSourceDataModel.cpp b/examples/calculator/NumberSourceDataModel.cpp index f6a7ca55d..f2564886a 100644 --- a/examples/calculator/NumberSourceDataModel.cpp +++ b/examples/calculator/NumberSourceDataModel.cpp @@ -9,7 +9,9 @@ NumberSourceDataModel::NumberSourceDataModel() : _lineEdit{nullptr} , _number(std::make_shared(0.0)) -{} +{ + this->setNodeProcessingStatus(QtNodes::NodeProcessingStatus::NoStatus); +} QJsonObject NumberSourceDataModel::save() const { From 12986053a482c2f91dad912582503b94b27abefc Mon Sep 17 00:00:00 2001 From: g-abilio Date: Wed, 24 Sep 2025 09:59:09 -0300 Subject: [PATCH 21/31] add Q_DECLARE_METATYPE to solve linux build problems --- include/QtNodes/internal/NodeDelegateModel.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/include/QtNodes/internal/NodeDelegateModel.hpp b/include/QtNodes/internal/NodeDelegateModel.hpp index bb8a8ddb4..523c00493 100644 --- a/include/QtNodes/internal/NodeDelegateModel.hpp +++ b/include/QtNodes/internal/NodeDelegateModel.hpp @@ -43,6 +43,7 @@ enum class NodeProcessingStatus : int { Failed = 5, /// Partial = 6, /// }; +Q_DECLARE_METATYPE(QtNodes::NodeProcessingStatus); class StyleCollection; From 47d948f3f1641e8c5fd111990abc72609a29b101 Mon Sep 17 00:00:00 2001 From: g-abilio Date: Wed, 24 Sep 2025 10:10:09 -0300 Subject: [PATCH 22/31] declaring metatype in the correct place --- include/QtNodes/internal/NodeDelegateModel.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/QtNodes/internal/NodeDelegateModel.hpp b/include/QtNodes/internal/NodeDelegateModel.hpp index 523c00493..476cf6f65 100644 --- a/include/QtNodes/internal/NodeDelegateModel.hpp +++ b/include/QtNodes/internal/NodeDelegateModel.hpp @@ -43,7 +43,6 @@ enum class NodeProcessingStatus : int { Failed = 5, /// Partial = 6, /// }; -Q_DECLARE_METATYPE(QtNodes::NodeProcessingStatus); class StyleCollection; @@ -183,3 +182,4 @@ public Q_SLOTS: } // namespace QtNodes Q_DECLARE_METATYPE(QtNodes::NodeValidationState) +Q_DECLARE_METATYPE(QtNodes::NodeProcessingStatus) From 4f1b23c05975899b89c43bb4b0509f6b94dcdbbb Mon Sep 17 00:00:00 2001 From: g-abilio Date: Wed, 3 Dec 2025 11:20:34 -0300 Subject: [PATCH 23/31] fix undocommands module inclusion --- include/QtNodes/internal/UndoCommands.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/QtNodes/internal/UndoCommands.hpp b/include/QtNodes/internal/UndoCommands.hpp index 77e2a4f09..870478618 100644 --- a/include/QtNodes/internal/UndoCommands.hpp +++ b/include/QtNodes/internal/UndoCommands.hpp @@ -3,9 +3,9 @@ #include "Definitions.hpp" #include "Export.hpp" +#include #include #include -#include #include From cc81264ecd92014d5d62f6cf455af1633b0be4f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Ab=C3=ADlio?= <150153171+g-abilio@users.noreply.github.com> Date: Thu, 11 Dec 2025 10:53:55 -0300 Subject: [PATCH 24/31] Adds node nickname functionality (#11) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * improves NodeValidationState struct * qt6 for linux-gcc * Adds node nickname functionality * Adjust label layout * improve caption and nickname dynamic * Add editable nickname label * add correct caption dynamic, as well as labelEdit max size * fix alignment issues between nickname and node caption * revert workflows change * fix segfault detected in dataflowgraphmodel tests * produces optional nickname structure and adds example * fix typo in spacing method and attribute * uniformizes icon files attributes * removes commented code * improves processing status icon resolution * solves situations where icons should not appear * adds docstring to each nodeprocessingstatus * adds possibility to change the node processing status icon style * moves all status logic to NodeStyle * removes unnecessary code * adds declaration of QPixmap --------- Co-authored-by: Taiguara Tupinambás Co-authored-by: Gabrielnmds --- .../calculator/LongProcessingRandomNumber.hpp | 1 + .../calculator/NumberDisplayDataModel.cpp | 4 +- examples/calculator/NumberSourceDataModel.cpp | 4 +- examples/styles/models.hpp | 2 + .../QtNodes/internal/AbstractNodeGeometry.hpp | 17 ++- .../QtNodes/internal/DataFlowGraphModel.hpp | 5 + .../DefaultHorizontalNodeGeometry.hpp | 9 +- .../QtNodes/internal/DefaultNodePainter.hpp | 2 + .../internal/DefaultVerticalNodeGeometry.hpp | 8 +- include/QtNodes/internal/Definitions.hpp | 34 +++--- include/QtNodes/internal/GraphicsView.hpp | 6 + .../QtNodes/internal/NodeDelegateModel.hpp | 38 +++++-- .../QtNodes/internal/NodeGraphicsObject.hpp | 19 ---- include/QtNodes/internal/NodeStyle.hpp | 29 +++++ resources/status_icons/empty.svg | 34 ++---- resources/status_icons/failed.svg | 90 +++------------ resources/status_icons/partial.svg | 68 ++++++----- resources/status_icons/pending.svg | 102 ++++------------- resources/status_icons/processing.svg | 96 +++------------- resources/status_icons/updated.svg | 29 +++-- src/DataFlowGraphModel.cpp | 107 ++++++++++++------ src/DefaultHorizontalNodeGeometry.cpp | 96 ++++++++++++---- src/DefaultNodePainter.cpp | 73 ++++++++++-- src/DefaultVerticalNodeGeometry.cpp | 75 +++++++++--- src/GraphicsView.cpp | 70 ++++++++++++ src/NodeDelegateModel.cpp | 54 +++++++++ src/NodeGraphicsObject.cpp | 66 ----------- src/NodeStyle.cpp | 9 +- 28 files changed, 638 insertions(+), 509 deletions(-) diff --git a/examples/calculator/LongProcessingRandomNumber.hpp b/examples/calculator/LongProcessingRandomNumber.hpp index 8be2882ca..d64f4ba7b 100644 --- a/examples/calculator/LongProcessingRandomNumber.hpp +++ b/examples/calculator/LongProcessingRandomNumber.hpp @@ -18,6 +18,7 @@ class RandomNumberModel : public MathOperationDataModel RandomNumberModel() { this->setNodeProcessingStatus(QtNodes::NodeProcessingStatus::Empty); + QObject::connect(this, &NodeDelegateModel::computingStarted, this, [this]() { if (_number1.lock() && _number2.lock()) { this->setNodeProcessingStatus( diff --git a/examples/calculator/NumberDisplayDataModel.cpp b/examples/calculator/NumberDisplayDataModel.cpp index 6f58f5854..5086050c8 100644 --- a/examples/calculator/NumberDisplayDataModel.cpp +++ b/examples/calculator/NumberDisplayDataModel.cpp @@ -4,9 +4,7 @@ NumberDisplayDataModel::NumberDisplayDataModel() : _label{nullptr} -{ - this->setNodeProcessingStatus(QtNodes::NodeProcessingStatus::NoStatus); -} +{} unsigned int NumberDisplayDataModel::nPorts(PortType portType) const { diff --git a/examples/calculator/NumberSourceDataModel.cpp b/examples/calculator/NumberSourceDataModel.cpp index f2564886a..f6a7ca55d 100644 --- a/examples/calculator/NumberSourceDataModel.cpp +++ b/examples/calculator/NumberSourceDataModel.cpp @@ -9,9 +9,7 @@ NumberSourceDataModel::NumberSourceDataModel() : _lineEdit{nullptr} , _number(std::make_shared(0.0)) -{ - this->setNodeProcessingStatus(QtNodes::NodeProcessingStatus::NoStatus); -} +{} QJsonObject NumberSourceDataModel::save() const { diff --git a/examples/styles/models.hpp b/examples/styles/models.hpp index 1352e4bfc..199f80bee 100644 --- a/examples/styles/models.hpp +++ b/examples/styles/models.hpp @@ -36,6 +36,8 @@ class MyDataModel : public NodeDelegateModel QString name() const override { return QString("MyDataModel"); } + bool labelEditable() const override { return true; } + public: QJsonObject save() const override { diff --git a/include/QtNodes/internal/AbstractNodeGeometry.hpp b/include/QtNodes/internal/AbstractNodeGeometry.hpp index acc60def5..c3be96301 100644 --- a/include/QtNodes/internal/AbstractNodeGeometry.hpp +++ b/include/QtNodes/internal/AbstractNodeGeometry.hpp @@ -36,8 +36,7 @@ class NODE_EDITOR_PUBLIC AbstractNodeGeometry /// Port position in node's coordinate system. virtual QPointF portPosition(NodeId const nodeId, PortType const portType, - PortIndex const index) const - = 0; + PortIndex const index) const = 0; /// A convenience function using the `portPosition` and a given transformation. virtual QPointF portScenePosition(NodeId const nodeId, @@ -48,8 +47,7 @@ class NODE_EDITOR_PUBLIC AbstractNodeGeometry /// Defines where to draw port label. The point corresponds to a font baseline. virtual QPointF portTextPosition(NodeId const nodeId, PortType const portType, - PortIndex const portIndex) const - = 0; + PortIndex const portIndex) const = 0; /** * Defines where to start drawing the caption. The point corresponds to a font @@ -60,6 +58,15 @@ class NODE_EDITOR_PUBLIC AbstractNodeGeometry /// Caption rect is needed for estimating the total node size. virtual QRectF captionRect(NodeId const nodeId) const = 0; + /** + * Defines where to start drawing the label. The point corresponds to a font + * baseline. + */ + virtual QPointF labelPosition(NodeId const nodeId) const = 0; + + /// Caption rect is needed for estimating the total node size. + virtual QRectF labelRect(NodeId const nodeId) const = 0; + /// Position for an embedded widget. Return any value if you don't embed. virtual QPointF widgetPosition(NodeId const nodeId) const = 0; @@ -69,6 +76,8 @@ class NODE_EDITOR_PUBLIC AbstractNodeGeometry virtual QRect resizeHandleRect(NodeId const nodeId) const = 0; + virtual int getPortSpacing() = 0; + protected: AbstractGraphModel &_graphModel; }; diff --git a/include/QtNodes/internal/DataFlowGraphModel.hpp b/include/QtNodes/internal/DataFlowGraphModel.hpp index ff93c6eb4..fcde1482c 100644 --- a/include/QtNodes/internal/DataFlowGraphModel.hpp +++ b/include/QtNodes/internal/DataFlowGraphModel.hpp @@ -11,6 +11,8 @@ #include #include +#include +#include namespace QtNodes { @@ -137,6 +139,9 @@ private Q_SLOTS: std::unordered_set _connectivity; mutable std::unordered_map _nodeGeometryData; + + std::unordered_map _labels; + std::unordered_map _labelsVisible; }; } // namespace QtNodes diff --git a/include/QtNodes/internal/DefaultHorizontalNodeGeometry.hpp b/include/QtNodes/internal/DefaultHorizontalNodeGeometry.hpp index 33367e109..b26555282 100644 --- a/include/QtNodes/internal/DefaultHorizontalNodeGeometry.hpp +++ b/include/QtNodes/internal/DefaultHorizontalNodeGeometry.hpp @@ -28,14 +28,21 @@ class NODE_EDITOR_PUBLIC DefaultHorizontalNodeGeometry : public AbstractNodeGeom QPointF portTextPosition(NodeId const nodeId, PortType const portType, PortIndex const PortIndex) const override; + QPointF captionPosition(NodeId const nodeId) const override; QRectF captionRect(NodeId const nodeId) const override; + QPointF labelPosition(const NodeId nodeId) const override; + + QRectF labelRect(NodeId const nodeId) const override; + QPointF widgetPosition(NodeId const nodeId) const override; QRect resizeHandleRect(NodeId const nodeId) const override; + int getPortSpacing() override { return _portSpacing; } + private: QRectF portTextRect(NodeId const nodeId, PortType const portType, @@ -52,7 +59,7 @@ class NODE_EDITOR_PUBLIC DefaultHorizontalNodeGeometry : public AbstractNodeGeom // constness of the Node. mutable unsigned int _portSize; - unsigned int _portSpasing; + unsigned int _portSpacing; mutable QFontMetrics _fontMetrics; mutable QFontMetrics _boldFontMetrics; }; diff --git a/include/QtNodes/internal/DefaultNodePainter.hpp b/include/QtNodes/internal/DefaultNodePainter.hpp index 953faa065..7a4f21a2c 100644 --- a/include/QtNodes/internal/DefaultNodePainter.hpp +++ b/include/QtNodes/internal/DefaultNodePainter.hpp @@ -28,6 +28,8 @@ class NODE_EDITOR_PUBLIC DefaultNodePainter : public AbstractNodePainter void drawNodeCaption(QPainter *painter, NodeGraphicsObject &ngo) const; + void drawNodeLabel(QPainter *painter, NodeGraphicsObject &ngo) const; + void drawEntryLabels(QPainter *painter, NodeGraphicsObject &ngo) const; void drawResizeRect(QPainter *painter, NodeGraphicsObject &ngo) const; diff --git a/include/QtNodes/internal/DefaultVerticalNodeGeometry.hpp b/include/QtNodes/internal/DefaultVerticalNodeGeometry.hpp index ce4dd9f17..d20636567 100644 --- a/include/QtNodes/internal/DefaultVerticalNodeGeometry.hpp +++ b/include/QtNodes/internal/DefaultVerticalNodeGeometry.hpp @@ -33,10 +33,16 @@ class NODE_EDITOR_PUBLIC DefaultVerticalNodeGeometry : public AbstractNodeGeomet QRectF captionRect(NodeId const nodeId) const override; + QPointF labelPosition(const NodeId nodeId) const override; + + QRectF labelRect(NodeId const nodeId) const override; + QPointF widgetPosition(NodeId const nodeId) const override; QRect resizeHandleRect(NodeId const nodeId) const override; + int getPortSpacing() override { return _portSpacing; } + private: QRectF portTextRect(NodeId const nodeId, PortType const portType, @@ -54,7 +60,7 @@ class NODE_EDITOR_PUBLIC DefaultVerticalNodeGeometry : public AbstractNodeGeomet // constness of the Node. mutable unsigned int _portSize; - unsigned int _portSpasing; + unsigned int _portSpacing; mutable QFontMetrics _fontMetrics; mutable QFontMetrics _boldFontMetrics; }; diff --git a/include/QtNodes/internal/Definitions.hpp b/include/QtNodes/internal/Definitions.hpp index 8c01475f9..19a3b16e3 100644 --- a/include/QtNodes/internal/Definitions.hpp +++ b/include/QtNodes/internal/Definitions.hpp @@ -18,23 +18,27 @@ NODE_EDITOR_PUBLIC Q_NAMESPACE Q_NAMESPACE_EXPORT(NODE_EDITOR_PUBLIC) #endif -/** + /** * Constants used for fetching QVariant data from GraphModel. */ -enum class NodeRole { - Type = 0, ///< Type of the current node, usually a string. - Position = 1, ///< `QPointF` positon of the node on the scene. - Size = 2, ///< `QSize` for resizable nodes. - CaptionVisible = 3, ///< `bool` for caption visibility. - Caption = 4, ///< `QString` for node caption. - Style = 5, ///< Custom NodeStyle as QJsonDocument - InternalData = 6, ///< Node-stecific user data as QJsonObject - InPortCount = 7, ///< `unsigned int` - OutPortCount = 9, ///< `unsigned int` - Widget = 10, ///< Optional `QWidget*` or `nullptr` - ValidationState = 11, ///< Enum NodeValidationState of the node - ProcessingStatus = 12 ///< Enum NodeProcessingStatus of the node -}; + enum class NodeRole { + Type = 0, ///< Type of the current node, usually a string. + Position = 1, ///< `QPointF` positon of the node on the scene. + Size = 2, ///< `QSize` for resizable nodes. + CaptionVisible = 3, ///< `bool` for caption visibility. + Caption = 4, ///< `QString` for node caption. + Style = 5, ///< Custom NodeStyle as QJsonDocument + InternalData = 6, ///< Node-stecific user data as QJsonObject + InPortCount = 7, ///< `unsigned int` + OutPortCount = 9, ///< `unsigned int` + Widget = 10, ///< Optional `QWidget*` or `nullptr` + ValidationState = 11, ///< Enum NodeValidationState of the node + LabelVisible = 12, ///< `bool` for label visibility. + ProcessingStatus = 13, ///< Enum NodeProcessingStatus of the node + Label = 14, ///< `QString` for node label. + LabelEditable = 15, ///< `bool` to indicate label editing support. + }; + Q_ENUM_NS(NodeRole) /** diff --git a/include/QtNodes/internal/GraphicsView.hpp b/include/QtNodes/internal/GraphicsView.hpp index 52068129f..1fbe5a890 100644 --- a/include/QtNodes/internal/GraphicsView.hpp +++ b/include/QtNodes/internal/GraphicsView.hpp @@ -2,8 +2,11 @@ #include +#include "Definitions.hpp" #include "Export.hpp" +class QLineEdit; + namespace QtNodes { class BasicGraphicsScene; @@ -93,5 +96,8 @@ public Q_SLOTS: QPointF _clickPos; ScaleRange _scaleRange; + + QLineEdit *_labelEdit = nullptr; + NodeId _editingNodeId = InvalidNodeId; }; } // namespace QtNodes diff --git a/include/QtNodes/internal/NodeDelegateModel.hpp b/include/QtNodes/internal/NodeDelegateModel.hpp index 4c7001f9e..5215c3692 100644 --- a/include/QtNodes/internal/NodeDelegateModel.hpp +++ b/include/QtNodes/internal/NodeDelegateModel.hpp @@ -3,14 +3,15 @@ #include #include +#include #include #include "Definitions.hpp" #include "Export.hpp" #include "NodeData.hpp" #include "NodeStyle.hpp" -#include #include "Serializable.hpp" +#include namespace QtNodes { @@ -33,16 +34,16 @@ struct NodeValidationState }; /** - * Describes the node status, depending on its current situation - */ +* Describes the node status, depending on its current situation +*/ enum class NodeProcessingStatus : int { - NoStatus = 0, /// - Updated = 1, /// - Processing = 2, /// - Pending = 3, /// - Empty = 4, /// - Failed = 5, /// - Partial = 6, /// + NoStatus = 0, ///< No processing status is shown in the Node UI. + Updated = 1, ///< Node is up to date; its outputs reflect the current inputs and parameters. + Processing = 2, ///< Node is currently running a computation. + Pending = 3, ///< Node is out of date and waiting to be recomputed (e.g. manual/queued run). + Empty = 4, ///< Node has no valid input data; nothing to compute. + Failed = 5, ///< The last computation ended with an error. + Partial = 6, ///< Computation finished incompletely; only partial results are available. }; class StyleCollection; @@ -82,6 +83,15 @@ class NODE_EDITOR_PUBLIC NodeDelegateModel /// Validation State will default to Valid, but you can manipulate it by overriding in an inherited class virtual NodeValidationState validationState() const { return _nodeValidationState; } + /// Nicknames can be assigned to nodes and shown in GUI + virtual QString label() const { return QString(); } + + /// It is possible to hide the nickname in GUI + virtual bool labelVisible() const { return true; } + + /// Controls whether the label can be edited or not + virtual bool labelEditable() const { return false; } + /// Returns the curent processing status virtual NodeProcessingStatus processingStatus() const { return _processingStatus; } @@ -106,6 +116,12 @@ class NODE_EDITOR_PUBLIC NodeDelegateModel /// Convenience helper to change the node background color. void setBackgroundColor(QColor const &color); + QPixmap processingStatusIcon() const; + + void setStatusIcon(NodeProcessingStatus status, const QPixmap &pixmap); + + void setStatusIconStyle(ProcessingIconStyle const &style); + public: virtual void setInData(std::shared_ptr nodeData, PortIndex const portIndex) = 0; @@ -180,7 +196,7 @@ public Q_SLOTS: NodeValidationState _nodeValidationState; - NodeProcessingStatus _processingStatus; + NodeProcessingStatus _processingStatus{NodeProcessingStatus::NoStatus}; }; } // namespace QtNodes diff --git a/include/QtNodes/internal/NodeGraphicsObject.hpp b/include/QtNodes/internal/NodeGraphicsObject.hpp index 69eb2ba6b..b3c01b904 100644 --- a/include/QtNodes/internal/NodeGraphicsObject.hpp +++ b/include/QtNodes/internal/NodeGraphicsObject.hpp @@ -53,14 +53,6 @@ class NodeGraphicsObject : public QGraphicsObject void updateQWidgetEmbedPos(); - void updateStatusIconSize() const; - - const QIcon processingStatusIcon() const; - - QRect statusIconRect() const; - - QSize statusIconSize() const; - protected: void paint(QPainter *painter, QStyleOptionGraphicsItem const *option, @@ -90,16 +82,5 @@ class NodeGraphicsObject : public QGraphicsObject // either nullptr or owned by parent QGraphicsItem QGraphicsProxyWidget *_proxyWidget; - - mutable bool _statusIconActive; - - mutable QSize _statusIconSize; - - const QIcon _statusUpdated{"://status_icons/updated.svg"}; - const QIcon _statusProcessing{"://status_icons/processing.svg"}; - const QIcon _statusPending{"://status_icons/pending.svg"}; - const QIcon _statusInvalid{"://status_icons/failed.svg"}; - const QIcon _statusEmpty{"://status_icons/empty.svg"}; - const QIcon _statusPartial{"://status_icons/partial.svg"}; }; } // namespace QtNodes diff --git a/include/QtNodes/internal/NodeStyle.hpp b/include/QtNodes/internal/NodeStyle.hpp index 48dde7b0b..f3fb04bbc 100644 --- a/include/QtNodes/internal/NodeStyle.hpp +++ b/include/QtNodes/internal/NodeStyle.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include "Export.hpp" @@ -7,6 +8,25 @@ namespace QtNodes { +/** + * Describes the position of the processing icon on the node ui + */ +enum class ProcessingIconPos { + BottomLeft = 0, /// icon on the bottom left position + BottomRight = 1, /// icon on the bottom right position +}; + +/** + * Defines the processing icon style; + */ +struct ProcessingIconStyle +{ + ProcessingIconPos _pos{ProcessingIconPos::BottomRight}; + double _size{20.0}; + double _margin{8.0}; + int _resolution{64}; +}; + class NODE_EDITOR_PUBLIC NodeStyle : public Style { public: @@ -57,5 +77,14 @@ class NODE_EDITOR_PUBLIC NodeStyle : public Style float ConnectionPointDiameter; float Opacity; + + QIcon statusUpdated{QStringLiteral("://status_icons/updated.svg")}; + QIcon statusProcessing{QStringLiteral("://status_icons/processing.svg")}; + QIcon statusPending{QStringLiteral("://status_icons/pending.svg")}; + QIcon statusInvalid{QStringLiteral("://status_icons/failed.svg")}; + QIcon statusEmpty{QStringLiteral("://status_icons/empty.svg")}; + QIcon statusPartial{QStringLiteral("://status_icons/partial.svg")}; + + ProcessingIconStyle processingIconStyle{}; }; } // namespace QtNodes diff --git a/resources/status_icons/empty.svg b/resources/status_icons/empty.svg index e38cd34ae..21d5e820b 100644 --- a/resources/status_icons/empty.svg +++ b/resources/status_icons/empty.svg @@ -1,22 +1,12 @@ - - - - - - - - - + + + + + + + + \ No newline at end of file diff --git a/resources/status_icons/failed.svg b/resources/status_icons/failed.svg index 0b94f245c..90f53d66c 100644 --- a/resources/status_icons/failed.svg +++ b/resources/status_icons/failed.svg @@ -1,78 +1,14 @@ - - - - - - - - - - image/svg+xml - - - - - - - - - - + + + + + + + + - + \ No newline at end of file diff --git a/resources/status_icons/partial.svg b/resources/status_icons/partial.svg index ec991dc5d..78522eca3 100644 --- a/resources/status_icons/partial.svg +++ b/resources/status_icons/partial.svg @@ -1,26 +1,42 @@ - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/resources/status_icons/pending.svg b/resources/status_icons/pending.svg index c850c9132..7e74e3fb0 100644 --- a/resources/status_icons/pending.svg +++ b/resources/status_icons/pending.svg @@ -1,84 +1,22 @@ - - + + + + + + + + + + + + - - - - - + \ No newline at end of file diff --git a/resources/status_icons/processing.svg b/resources/status_icons/processing.svg index 74b82bc3a..03ac710ac 100644 --- a/resources/status_icons/processing.svg +++ b/resources/status_icons/processing.svg @@ -1,80 +1,20 @@ - - + + + + + + + + + + - + \ No newline at end of file diff --git a/resources/status_icons/updated.svg b/resources/status_icons/updated.svg index c2485802e..5c1075e2b 100644 --- a/resources/status_icons/updated.svg +++ b/resources/status_icons/updated.svg @@ -1,15 +1,14 @@ - - - - - - - - - - + + + + + + + + + + \ No newline at end of file diff --git a/src/DataFlowGraphModel.cpp b/src/DataFlowGraphModel.cpp index fa5655cde..187375b47 100644 --- a/src/DataFlowGraphModel.cpp +++ b/src/DataFlowGraphModel.cpp @@ -63,51 +63,48 @@ NodeId DataFlowGraphModel::addNode(QString const nodeType) { std::unique_ptr model = _registry->create(nodeType); - if (model) { - NodeId newId = newNodeId(); + if (!model) + return InvalidNodeId; - connect(model.get(), - &NodeDelegateModel::dataUpdated, - [newId, this](PortIndex const portIndex) { - onOutPortDataUpdated(newId, portIndex); - }); + NodeId newId = newNodeId(); - connect(model.get(), - &NodeDelegateModel::portsAboutToBeDeleted, - this, - [newId, this](PortType const portType, PortIndex const first, PortIndex const last) { - portsAboutToBeDeleted(newId, portType, first, last); - }); + connect(model.get(), &NodeDelegateModel::dataUpdated, [newId, this](PortIndex const portIndex) { + onOutPortDataUpdated(newId, portIndex); + }); - connect(model.get(), - &NodeDelegateModel::portsDeleted, - this, - &DataFlowGraphModel::portsDeleted); + connect(model.get(), + &NodeDelegateModel::portsAboutToBeDeleted, + this, + [newId, this](PortType const portType, PortIndex const first, PortIndex const last) { + portsAboutToBeDeleted(newId, portType, first, last); + }); - connect(model.get(), - &NodeDelegateModel::portsAboutToBeInserted, - this, - [newId, this](PortType const portType, PortIndex const first, PortIndex const last) { - portsAboutToBeInserted(newId, portType, first, last); - }); + connect(model.get(), &NodeDelegateModel::portsDeleted, this, &DataFlowGraphModel::portsDeleted); - connect(model.get(), - &NodeDelegateModel::portsInserted, - this, - &DataFlowGraphModel::portsInserted); + connect(model.get(), + &NodeDelegateModel::portsAboutToBeInserted, + this, + [newId, this](PortType const portType, PortIndex const first, PortIndex const last) { + portsAboutToBeInserted(newId, portType, first, last); + }); - connect(model.get(), &NodeDelegateModel::requestNodeUpdate, this, [newId, this]() { - Q_EMIT nodeUpdated(newId); - }); + connect(model.get(), + &NodeDelegateModel::portsInserted, + this, + &DataFlowGraphModel::portsInserted); - _models[newId] = std::move(model); + connect(model.get(), &NodeDelegateModel::requestNodeUpdate, this, [newId, this]() { + Q_EMIT nodeUpdated(newId); + }); - Q_EMIT nodeCreated(newId); + _models[newId] = std::move(model); - return newId; - } + _labels[newId] = _models[newId]->label(); + _labelsVisible[newId] = _models[newId]->labelVisible(); + + Q_EMIT nodeCreated(newId); - return InvalidNodeId; + return newId; } bool DataFlowGraphModel::connectionPossible(ConnectionId const connectionId) const @@ -299,6 +296,18 @@ QVariant DataFlowGraphModel::nodeData(NodeId nodeId, NodeRole role) const result = QVariant::fromValue(validationState); } break; + case NodeRole::LabelVisible: + result = _labelsVisible.at(nodeId); + break; + + case NodeRole::Label: + result = _labels.at(nodeId); + break; + + case NodeRole::LabelEditable: + result = model->labelEditable(); + break; + case NodeRole::ProcessingStatus: { auto processingStatus = model->processingStatus(); result = QVariant::fromValue(processingStatus); @@ -382,6 +391,21 @@ bool DataFlowGraphModel::setNodeData(NodeId nodeId, NodeRole role, QVariant valu } Q_EMIT nodeUpdated(nodeId); } break; + + case NodeRole::LabelVisible: { + _labelsVisible[nodeId] = value.toBool(); + Q_EMIT nodeUpdated(nodeId); + result = true; + } break; + + case NodeRole::Label: { + _labels[nodeId] = value.toString(); + Q_EMIT nodeUpdated(nodeId); + result = true; + } break; + + case NodeRole::LabelEditable: + break; } return result; @@ -489,6 +513,8 @@ bool DataFlowGraphModel::deleteNode(NodeId const nodeId) } _nodeGeometryData.erase(nodeId); + _labels.erase(nodeId); + _labelsVisible.erase(nodeId); _models.erase(nodeId); Q_EMIT nodeDeleted(nodeId); @@ -504,6 +530,9 @@ QJsonObject DataFlowGraphModel::saveNode(NodeId const nodeId) const nodeJson["internal-data"] = _models.at(nodeId)->save(); + nodeJson["label"] = _labels.at(nodeId); + nodeJson["labelVisible"] = _labelsVisible.at(nodeId); + { QPointF const pos = nodeData(nodeId, NodeRole::Position).value(); @@ -599,7 +628,13 @@ void DataFlowGraphModel::loadNode(QJsonObject const &nodeJson) setNodeData(restoredNodeId, NodeRole::Position, pos); - _models[restoredNodeId]->load(internalDataJson); + auto *restoredModel = _models[restoredNodeId].get(); + _labels[restoredNodeId] = nodeJson["label"].toString(restoredModel->label()); + _labelsVisible[restoredNodeId] = nodeJson.contains("labelVisible") + ? nodeJson["labelVisible"].toBool() + : restoredModel->labelVisible(); + + restoredModel->load(internalDataJson); } else { throw std::logic_error(std::string("No registered model with name ") + delegateModelName.toLocal8Bit().data()); diff --git a/src/DefaultHorizontalNodeGeometry.cpp b/src/DefaultHorizontalNodeGeometry.cpp index a2102c641..aa2f9c32f 100644 --- a/src/DefaultHorizontalNodeGeometry.cpp +++ b/src/DefaultHorizontalNodeGeometry.cpp @@ -1,7 +1,6 @@ #include "DefaultHorizontalNodeGeometry.hpp" #include "AbstractGraphModel.hpp" #include "NodeData.hpp" -#include "NodeDelegateModel.hpp" #include #include @@ -12,7 +11,7 @@ namespace QtNodes { DefaultHorizontalNodeGeometry::DefaultHorizontalNodeGeometry(AbstractGraphModel &graphModel) : AbstractNodeGeometry(graphModel) , _portSize(20) - , _portSpasing(10) + , _portSpacing(10) , _fontMetrics(QFont()) , _boldFontMetrics(QFont()) { @@ -27,7 +26,7 @@ QRectF DefaultHorizontalNodeGeometry::boundingRect(NodeId const nodeId) const { QSize s = size(nodeId); - qreal marginSize = 2.0 * _portSpasing; + qreal marginSize = 2.0 * _portSpacing; QMargins margins(marginSize, marginSize, marginSize, marginSize); QRectF r(QPointF(0, 0), s); @@ -49,28 +48,37 @@ void DefaultHorizontalNodeGeometry::recomputeSize(NodeId const nodeId) const } QRectF const capRect = captionRect(nodeId); + QRectF const lblRect = labelRect(nodeId); height += capRect.height(); + if (!lblRect.isNull()) { + height += lblRect.height(); + height += _portSpacing / 2; + } - height += _portSpasing; // space above caption - height += _portSpasing; // space below caption + height += _portSpacing; // space above caption + height += _portSpacing; // space below caption QVariant var = _graphModel.nodeData(nodeId, NodeRole::ProcessingStatus); - auto processingStatusValue = var.value(); + auto processingStatusValue = var.value(); - if (processingStatusValue != QtNodes::NodeProcessingStatus::NoStatus) + if (processingStatusValue != 0) height += 20; unsigned int inPortWidth = maxPortsTextAdvance(nodeId, PortType::In); unsigned int outPortWidth = maxPortsTextAdvance(nodeId, PortType::Out); - unsigned int width = inPortWidth + outPortWidth + 4 * _portSpasing; + unsigned int width = inPortWidth + outPortWidth + 4 * _portSpacing; if (auto w = _graphModel.nodeData(nodeId, NodeRole::Widget)) { width += w->width(); } - width = std::max(width, static_cast(capRect.width()) + 2 * _portSpasing); + unsigned int textWidth = static_cast(capRect.width()); + if (!lblRect.isNull()) + textWidth = std::max(textWidth, static_cast(lblRect.width())); + + width = std::max(width, textWidth + 2 * _portSpacing); QSize size(width, height); @@ -81,14 +89,20 @@ QPointF DefaultHorizontalNodeGeometry::portPosition(NodeId const nodeId, PortType const portType, PortIndex const portIndex) const { - unsigned int const step = _portSize + _portSpasing; + unsigned int const step = _portSize + _portSpacing; QPointF result; double totalHeight = 0.0; totalHeight += captionRect(nodeId).height(); - totalHeight += _portSpasing; + + if (_graphModel.nodeData(nodeId, NodeRole::LabelVisible)) { + totalHeight += labelRect(nodeId).height(); + totalHeight += _portSpacing / 2.0; + } + + totalHeight += _portSpacing; totalHeight += step * portIndex; totalHeight += step / 2.0; @@ -131,11 +145,11 @@ QPointF DefaultHorizontalNodeGeometry::portTextPosition(NodeId const nodeId, switch (portType) { case PortType::In: - p.setX(_portSpasing); + p.setX(_portSpacing); break; case PortType::Out: - p.setX(size.width() - _portSpasing - rect.width()); + p.setX(size.width() - _portSpacing - rect.width()); break; default: @@ -158,8 +172,48 @@ QRectF DefaultHorizontalNodeGeometry::captionRect(NodeId const nodeId) const QPointF DefaultHorizontalNodeGeometry::captionPosition(NodeId const nodeId) const { QSize size = _graphModel.nodeData(nodeId, NodeRole::Size); - return QPointF(0.5 * (size.width() - captionRect(nodeId).width()), - 0.5 * _portSpasing + captionRect(nodeId).height()); + + QRectF cap = captionRect(nodeId); + QRectF lbl = labelRect(nodeId); + + double y = 0.5 * _portSpacing + cap.height(); + y += _portSpacing / 2.0 + lbl.height(); + + return QPointF(0.5 * (size.width() - captionRect(nodeId).width()), y); +} + +QRectF DefaultHorizontalNodeGeometry::labelRect(NodeId const nodeId) const +{ + if (!_graphModel.nodeData(nodeId, NodeRole::LabelVisible)) + return QRect(); + + QString nickname = _graphModel.nodeData(nodeId, NodeRole::Label); + + QRectF nickRect = _boldFontMetrics.boundingRect(nickname); + + nickRect.setWidth(nickRect.width() * 0.5); + nickRect.setHeight(nickRect.height() * 0.5); + + return nickRect; +} + +QPointF DefaultHorizontalNodeGeometry::labelPosition(NodeId const nodeId) const +{ + QRectF cap = captionRect(nodeId); + QRectF lbl = labelRect(nodeId); + + double y = 0.5 * _portSpacing + cap.height(); + y += _portSpacing / 2.0 + lbl.height(); + + if (!_graphModel.nodeData(nodeId, NodeRole::CaptionVisible)) { + return QPointF(captionPosition(nodeId).x() + + 0.5 * (captionRect(nodeId).width() - 2 * labelRect(nodeId).width()), + y); + } + + return QPointF(captionPosition(nodeId).x() + + 0.5 * (captionRect(nodeId).width() - 2 * labelRect(nodeId).width()), + 0.5 * _portSpacing + captionRect(nodeId).height()); } QPointF DefaultHorizontalNodeGeometry::widgetPosition(NodeId const nodeId) const @@ -167,15 +221,17 @@ QPointF DefaultHorizontalNodeGeometry::widgetPosition(NodeId const nodeId) const QSize size = _graphModel.nodeData(nodeId, NodeRole::Size); unsigned int captionHeight = captionRect(nodeId).height(); + if (_graphModel.nodeData(nodeId, NodeRole::LabelVisible)) + captionHeight += labelRect(nodeId).height() + _portSpacing / 2; if (auto w = _graphModel.nodeData(nodeId, NodeRole::Widget)) { // If the widget wants to use as much vertical space as possible, // place it immediately after the caption. if (w->sizePolicy().verticalPolicy() & QSizePolicy::ExpandFlag) { - return QPointF(2.0 * _portSpasing + maxPortsTextAdvance(nodeId, PortType::In), - _portSpasing + captionHeight); + return QPointF(2.0 * _portSpacing + maxPortsTextAdvance(nodeId, PortType::In), + _portSpacing + captionHeight); } else { - return QPointF(2.0 * _portSpasing + maxPortsTextAdvance(nodeId, PortType::In), + return QPointF(2.0 * _portSpacing + maxPortsTextAdvance(nodeId, PortType::In), (captionHeight + size.height() - w->height()) / 2.0); } } @@ -188,7 +244,7 @@ QRect DefaultHorizontalNodeGeometry::resizeHandleRect(NodeId const nodeId) const unsigned int rectSize = 7; - return QRect(size.width() - _portSpasing, size.height() - _portSpasing, rectSize, rectSize); + return QRect(size.width() - _portSpacing, size.height() - _portSpacing, rectSize, rectSize); } QRectF DefaultHorizontalNodeGeometry::portTextRect(NodeId const nodeId, @@ -214,7 +270,7 @@ unsigned int DefaultHorizontalNodeGeometry::maxVerticalPortsExtent(NodeId const PortCount nOutPorts = _graphModel.nodeData(nodeId, NodeRole::OutPortCount); unsigned int maxNumOfEntries = std::max(nInPorts, nOutPorts); - unsigned int step = _portSize + _portSpasing; + unsigned int step = _portSize + _portSpacing; return step * maxNumOfEntries; } diff --git a/src/DefaultNodePainter.cpp b/src/DefaultNodePainter.cpp index 259eb670e..66180fe1c 100644 --- a/src/DefaultNodePainter.cpp +++ b/src/DefaultNodePainter.cpp @@ -38,6 +38,8 @@ void DefaultNodePainter::paint(QPainter *painter, NodeGraphicsObject &ngo) const drawResizeRect(painter, ngo); drawValidationIcon(painter, ngo); + + drawNodeLabel(painter, ngo); } void DefaultNodePainter::drawNodeRect(QPainter *painter, NodeGraphicsObject &ngo) const @@ -222,12 +224,21 @@ void DefaultNodePainter::drawNodeCaption(QPainter *painter, NodeGraphicsObject & if (!model.nodeData(nodeId, NodeRole::CaptionVisible).toBool()) return; + QString const nickname = model.nodeData(nodeId, NodeRole::Label).toString(); QString const name = model.nodeData(nodeId, NodeRole::Caption).toString(); QFont f = painter->font(); - f.setBold(true); + f.setBold(nickname.isEmpty()); + f.setItalic(!nickname.isEmpty()); + + QFontMetricsF metrics(f); - QPointF position = geometry.captionPosition(nodeId); + QRectF bounding = metrics.boundingRect(name); + QRectF capRect = geometry.captionRect(nodeId); + QPointF capPos = geometry.captionPosition(nodeId); + double centerX = capPos.x() + capRect.width() / 2.0; + + QPointF position(centerX - bounding.width() / 2.0, capPos.y()); QJsonDocument json = QJsonDocument::fromVariant(model.nodeData(nodeId, NodeRole::Style)); NodeStyle nodeStyle(json.object()); @@ -236,6 +247,45 @@ void DefaultNodePainter::drawNodeCaption(QPainter *painter, NodeGraphicsObject & painter->setPen(nodeStyle.FontColor); painter->drawText(position, name); + f.setBold(false); + f.setItalic(false); + painter->setFont(f); +} + +void DefaultNodePainter::drawNodeLabel(QPainter *painter, NodeGraphicsObject &ngo) const +{ + AbstractGraphModel &model = ngo.graphModel(); + NodeId const nodeId = ngo.nodeId(); + AbstractNodeGeometry &geometry = ngo.nodeScene()->nodeGeometry(); + + if (!model.nodeData(nodeId, NodeRole::LabelVisible).toBool()) + return; + + QString const nickname = model.nodeData(nodeId, NodeRole::Label).toString(); + + QFont f = painter->font(); + f.setBold(true); + f.setItalic(false); + + QFontMetricsF metrics(f); + + QRectF bounding = metrics.boundingRect(nickname); + QRectF capRect = geometry.captionRect(nodeId); + QPointF capPos = geometry.captionPosition(nodeId); + double centerX = capPos.x() + capRect.width() / 2.0; + + double textHeight = metrics.height(); + double y = capPos.y() - textHeight - 2.0; + + QPointF position(centerX - bounding.width() / 2.0, y); + + QJsonDocument json = QJsonDocument::fromVariant(model.nodeData(nodeId, NodeRole::Style)); + NodeStyle nodeStyle(json.object()); + + painter->setFont(f); + painter->setPen(nodeStyle.FontColor); + painter->drawText(position, nickname); + f.setBold(false); painter->setFont(f); } @@ -308,14 +358,23 @@ void DefaultNodePainter::drawProcessingIndicator(QPainter *painter, NodeGraphics AbstractNodeGeometry &geometry = ngo.nodeScene()->nodeGeometry(); - ngo.updateStatusIconSize(); QSize size = geometry.size(nodeId); - QIcon icon = ngo.processingStatusIcon(); - QSize iconSize(16, 16); - QPixmap pixmap = icon.pixmap(iconSize); + QPixmap pixmap = delegate->processingStatusIcon(); + NodeStyle nodeStyle = delegate->nodeStyle(); + + ProcessingIconStyle iconStyle = nodeStyle.processingIconStyle; + + qreal iconSize = iconStyle._size; + qreal margin = iconStyle._margin; + + qreal x = margin; + + if (iconStyle._pos == ProcessingIconPos::BottomRight) { + x = size.width() - iconSize - margin; + } - QRect r(size.width() - 28.0, size.height() - 28.0, 20.0, 20.0); + QRect r(x, size.height() - iconSize - margin, iconSize, iconSize); painter->drawPixmap(r, pixmap); } diff --git a/src/DefaultVerticalNodeGeometry.cpp b/src/DefaultVerticalNodeGeometry.cpp index f20617f25..589f5cdcb 100644 --- a/src/DefaultVerticalNodeGeometry.cpp +++ b/src/DefaultVerticalNodeGeometry.cpp @@ -12,7 +12,7 @@ namespace QtNodes { DefaultVerticalNodeGeometry::DefaultVerticalNodeGeometry(AbstractGraphModel &graphModel) : AbstractNodeGeometry(graphModel) , _portSize(20) - , _portSpasing(10) + , _portSpacing(10) , _fontMetrics(QFont()) , _boldFontMetrics(QFont()) { @@ -27,7 +27,7 @@ QRectF DefaultVerticalNodeGeometry::boundingRect(NodeId const nodeId) const { QSize s = size(nodeId); - qreal marginSize = 2.0 * _portSpasing; + qreal marginSize = 2.0 * _portSpacing; QMargins margins(marginSize, marginSize, marginSize, marginSize); QRectF r(QPointF(0, 0), s); @@ -42,18 +42,23 @@ QSize DefaultVerticalNodeGeometry::size(NodeId const nodeId) const void DefaultVerticalNodeGeometry::recomputeSize(NodeId const nodeId) const { - unsigned int height = _portSpasing; // maxHorizontalPortsExtent(nodeId); + unsigned int height = _portSpacing; // maxHorizontalPortsExtent(nodeId); if (auto w = _graphModel.nodeData(nodeId, NodeRole::Widget)) { height = std::max(height, static_cast(w->height())); } QRectF const capRect = captionRect(nodeId); + QRectF const lblRect = labelRect(nodeId); height += capRect.height(); + if (!lblRect.isNull()) { + height += lblRect.height(); + height += _portSpacing / 2; + } - height += _portSpasing; - height += _portSpasing; + height += _portSpacing; + height += _portSpacing; PortCount nInPorts = _graphModel.nodeData(nodeId, NodeRole::InPortCount); PortCount nOutPorts = _graphModel.nodeData(nodeId, NodeRole::OutPortCount); @@ -67,11 +72,11 @@ void DefaultVerticalNodeGeometry::recomputeSize(NodeId const nodeId) const unsigned int outPortWidth = maxPortsTextAdvance(nodeId, PortType::Out); unsigned int totalInPortsWidth = nInPorts > 0 - ? inPortWidth * nInPorts + _portSpasing * (nInPorts - 1) + ? inPortWidth * nInPorts + _portSpacing * (nInPorts - 1) : 0; unsigned int totalOutPortsWidth = nOutPorts > 0 ? outPortWidth * nOutPorts - + _portSpasing * (nOutPorts - 1) + + _portSpacing * (nOutPorts - 1) : 0; unsigned int width = std::max(totalInPortsWidth, totalOutPortsWidth); @@ -80,10 +85,14 @@ void DefaultVerticalNodeGeometry::recomputeSize(NodeId const nodeId) const width = std::max(width, static_cast(w->width())); } - width = std::max(width, static_cast(capRect.width())); + unsigned int textWidth = static_cast(capRect.width()); + if (!lblRect.isNull()) + textWidth = std::max(textWidth, static_cast(lblRect.width())); + + width = std::max(width, textWidth); - width += _portSpasing; - width += _portSpasing; + width += _portSpacing; + width += _portSpacing; QSize size(width, height); @@ -100,7 +109,7 @@ QPointF DefaultVerticalNodeGeometry::portPosition(NodeId const nodeId, switch (portType) { case PortType::In: { - unsigned int inPortWidth = maxPortsTextAdvance(nodeId, PortType::In) + _portSpasing; + unsigned int inPortWidth = maxPortsTextAdvance(nodeId, PortType::In) + _portSpacing; PortCount nInPorts = _graphModel.nodeData(nodeId, NodeRole::InPortCount); @@ -114,7 +123,7 @@ QPointF DefaultVerticalNodeGeometry::portPosition(NodeId const nodeId, } case PortType::Out: { - unsigned int outPortWidth = maxPortsTextAdvance(nodeId, PortType::Out) + _portSpasing; + unsigned int outPortWidth = maxPortsTextAdvance(nodeId, PortType::Out) + _portSpacing; PortCount nOutPorts = _graphModel.nodeData(nodeId, NodeRole::OutPortCount); double x = (size.width() - (nOutPorts - 1) * outPortWidth) / 2.0 + portIndex * outPortWidth; @@ -176,26 +185,56 @@ QPointF DefaultVerticalNodeGeometry::captionPosition(NodeId const nodeId) const QSize size = _graphModel.nodeData(nodeId, NodeRole::Size); unsigned int step = portCaptionsHeight(nodeId, PortType::In); - step += _portSpasing; + step += _portSpacing; auto rect = captionRect(nodeId); return QPointF(0.5 * (size.width() - rect.width()), step + rect.height()); } +QPointF DefaultVerticalNodeGeometry::labelPosition(const NodeId nodeId) const +{ + QSize size = _graphModel.nodeData(nodeId, NodeRole::Size); + + QRectF rect = labelRect(nodeId); + + unsigned int step = portCaptionsHeight(nodeId, PortType::In); + step += _portSpacing; + step += captionRect(nodeId).height(); + step += _portSpacing / 2; + + return QPointF(0.5 * (size.width() - rect.width()), step + rect.height()); +} + +QRectF DefaultVerticalNodeGeometry::labelRect(NodeId const nodeId) const +{ + if (!_graphModel.nodeData(nodeId, NodeRole::LabelVisible)) + return QRectF(); + + QString nickname = _graphModel.nodeData(nodeId, NodeRole::Label); + + QRectF rect = _boldFontMetrics.boundingRect(nickname); + rect.setWidth(rect.width() * 0.5); + rect.setHeight(rect.height() * 0.5); + + return rect; +} + QPointF DefaultVerticalNodeGeometry::widgetPosition(NodeId const nodeId) const { QSize size = _graphModel.nodeData(nodeId, NodeRole::Size); unsigned int captionHeight = captionRect(nodeId).height(); + if (_graphModel.nodeData(nodeId, NodeRole::LabelVisible)) + captionHeight += labelRect(nodeId).height() + _portSpacing / 2; if (auto w = _graphModel.nodeData(nodeId, NodeRole::Widget)) { // If the widget wants to use as much vertical space as possible, // place it immediately after the caption. if (w->sizePolicy().verticalPolicy() & QSizePolicy::ExpandFlag) { - return QPointF(_portSpasing + maxPortsTextAdvance(nodeId, PortType::In), captionHeight); + return QPointF(_portSpacing + maxPortsTextAdvance(nodeId, PortType::In), captionHeight); } else { - return QPointF(_portSpasing + maxPortsTextAdvance(nodeId, PortType::In), + return QPointF(_portSpacing + maxPortsTextAdvance(nodeId, PortType::In), (captionHeight + size.height() - w->height()) / 2.0); } } @@ -234,7 +273,7 @@ unsigned int DefaultVerticalNodeGeometry::maxHorizontalPortsExtent(NodeId const PortCount nOutPorts = _graphModel.nodeData(nodeId, NodeRole::OutPortCount); unsigned int maxNumOfEntries = std::max(nInPorts, nOutPorts); - unsigned int step = _portSize + _portSpasing; + unsigned int step = _portSize + _portSpacing; return step * maxNumOfEntries; } @@ -284,7 +323,7 @@ unsigned int DefaultVerticalNodeGeometry::portCaptionsHeight(NodeId const nodeId PortCount nInPorts = _graphModel.nodeData(nodeId, NodeRole::InPortCount); for (PortIndex i = 0; i < nInPorts; ++i) { if (_graphModel.portData(nodeId, PortType::In, i, PortRole::CaptionVisible)) { - h += _portSpasing; + h += _portSpacing; break; } } @@ -295,7 +334,7 @@ unsigned int DefaultVerticalNodeGeometry::portCaptionsHeight(NodeId const nodeId PortCount nOutPorts = _graphModel.nodeData(nodeId, NodeRole::OutPortCount); for (PortIndex i = 0; i < nOutPorts; ++i) { if (_graphModel.portData(nodeId, PortType::Out, i, PortRole::CaptionVisible)) { - h += _portSpasing; + h += _portSpacing; break; } } diff --git a/src/GraphicsView.cpp b/src/GraphicsView.cpp index cbd87cc87..cd19581eb 100644 --- a/src/GraphicsView.cpp +++ b/src/GraphicsView.cpp @@ -2,10 +2,13 @@ #include "BasicGraphicsScene.hpp" #include "ConnectionGraphicsObject.hpp" +#include "Definitions.hpp" #include "NodeGraphicsObject.hpp" #include "StyleCollection.hpp" #include "UndoCommands.hpp" +#include + #include #include @@ -300,6 +303,73 @@ void GraphicsView::onPasteObjects() void GraphicsView::keyPressEvent(QKeyEvent *event) { switch (event->key()) { + case Qt::Key_F2: { + BasicGraphicsScene *sc = nodeScene(); + + if (sc) { + QList items = sc->selectedItems(); + NodeGraphicsObject *ngo = nullptr; + for (QGraphicsItem *it : items) { + ngo = qgraphicsitem_cast(it); + + if (ngo) + break; + } + + if (ngo) { + bool const labelEditable + = sc->graphModel().nodeData(ngo->nodeId(), NodeRole::LabelEditable).toBool(); + + if (!labelEditable) + break; + + if (!_labelEdit) { + _labelEdit = new QLineEdit(this); + _labelEdit->setMaxLength(32); + + connect(_labelEdit, &QLineEdit::editingFinished, [this]() { + if (_editingNodeId != InvalidNodeId) { + nodeScene()->graphModel().setNodeData(_editingNodeId, + NodeRole::LabelVisible, + true); + nodeScene()->graphModel().setNodeData(_editingNodeId, + NodeRole::Label, + _labelEdit->text()); + } + + _labelEdit->hide(); + _editingNodeId = InvalidNodeId; + }); + } + + _editingNodeId = ngo->nodeId(); + + sc->graphModel().setNodeData(_editingNodeId, NodeRole::LabelVisible, true); + + AbstractNodeGeometry &geom = sc->nodeGeometry(); + QPointF labelPos = geom.labelPosition(_editingNodeId); + QPointF scenePos = ngo->mapToScene(labelPos); + QSize sz = _labelEdit->sizeHint(); + QPoint viewPos = mapFromScene(scenePos); + _labelEdit->move(viewPos.x() - sz.width() / 2, viewPos.y() - sz.height() / 2); + bool visible + = sc->graphModel().nodeData(_editingNodeId, NodeRole::LabelVisible).toBool(); + QString current + = sc->graphModel().nodeData(_editingNodeId, NodeRole::Label).toString(); + + if (!visible && current.isEmpty()) + _labelEdit->clear(); + else + _labelEdit->setText(current); + _labelEdit->resize(sz); + _labelEdit->show(); + _labelEdit->setFocus(); + return; + } + } + } + + break; case Qt::Key_Shift: setDragMode(QGraphicsView::RubberBandDrag); break; diff --git a/src/NodeDelegateModel.cpp b/src/NodeDelegateModel.cpp index c5d267602..fc8f3e8dd 100644 --- a/src/NodeDelegateModel.cpp +++ b/src/NodeDelegateModel.cpp @@ -56,6 +56,60 @@ void NodeDelegateModel::setNodeStyle(NodeStyle const &style) _nodeStyle = style; } +QPixmap NodeDelegateModel::processingStatusIcon() const +{ + int resolution = _nodeStyle.processingIconStyle._resolution; + switch (_processingStatus) { + case NodeProcessingStatus::NoStatus: + return {}; + case NodeProcessingStatus::Updated: + return _nodeStyle.statusUpdated.pixmap(resolution); + case NodeProcessingStatus::Processing: + return _nodeStyle.statusProcessing.pixmap(resolution); + case NodeProcessingStatus::Pending: + return _nodeStyle.statusPending.pixmap(resolution); + case NodeProcessingStatus::Empty: + return _nodeStyle.statusEmpty.pixmap(resolution); + case NodeProcessingStatus::Failed: + return _nodeStyle.statusInvalid.pixmap(resolution); + case NodeProcessingStatus::Partial: + return _nodeStyle.statusPartial.pixmap(resolution); + } + + return {}; +} + +void NodeDelegateModel::setStatusIcon(NodeProcessingStatus status, const QPixmap &pixmap) +{ + switch (status) { + case NodeProcessingStatus::NoStatus: + break; + case NodeProcessingStatus::Updated: + _nodeStyle.statusUpdated = QIcon(pixmap); + break; + case NodeProcessingStatus::Processing: + _nodeStyle.statusProcessing = QIcon(pixmap); + break; + case NodeProcessingStatus::Pending: + _nodeStyle.statusPending = QIcon(pixmap); + break; + case NodeProcessingStatus::Empty: + _nodeStyle.statusEmpty = QIcon(pixmap); + break; + case NodeProcessingStatus::Failed: + _nodeStyle.statusInvalid = QIcon(pixmap); + break; + case NodeProcessingStatus::Partial: + _nodeStyle.statusPartial = QIcon(pixmap); + break; + } +} + +void NodeDelegateModel::setStatusIconStyle(const ProcessingIconStyle &style) +{ + _nodeStyle.processingIconStyle = style; +} + void NodeDelegateModel::setNodeProcessingStatus(NodeProcessingStatus status) { _processingStatus = status; diff --git a/src/NodeGraphicsObject.cpp b/src/NodeGraphicsObject.cpp index 815ad4747..3eb94b359 100644 --- a/src/NodeGraphicsObject.cpp +++ b/src/NodeGraphicsObject.cpp @@ -66,13 +66,6 @@ NodeGraphicsObject::NodeGraphicsObject(BasicGraphicsScene &scene, NodeId nodeId) }); QVariant var = _graphModel.nodeData(_nodeId, NodeRole::ProcessingStatus); - - auto processingStatusValue = var.value(); - - _statusIconActive = processingStatusValue != QtNodes::NodeProcessingStatus::NoStatus; - - _statusIconSize.setWidth(_statusIconActive ? 32 : 0); - _statusIconSize.setHeight(_statusIconActive ? 32 : 0); } AbstractGraphModel &NodeGraphicsObject::graphModel() const @@ -390,63 +383,4 @@ void NodeGraphicsObject::contextMenuEvent(QGraphicsSceneContextMenuEvent *event) Q_EMIT nodeScene()->nodeContextMenu(_nodeId, mapToScene(event->pos())); } -void NodeGraphicsObject::updateStatusIconSize() const -{ - QVariant var = _graphModel.nodeData(_nodeId, NodeRole::ProcessingStatus); - - auto processingStatusValue = var.value(); - - bool oldStatus = _statusIconActive; - _statusIconActive = processingStatusValue != QtNodes::NodeProcessingStatus::NoStatus; - - if (oldStatus != _statusIconActive) { - _statusIconSize.setWidth(_statusIconActive ? 32 : 0); - _statusIconSize.setHeight(_statusIconActive ? 32 : 0); - } -} - -QRect NodeGraphicsObject::statusIconRect() const -{ - QVariant var = _graphModel.nodeData(_nodeId, NodeRole::ProcessingStatus); - - // auto spacing = static_cast(_spacing); - auto iconPos = - // = portScenePosition(std::max(var.value().nPorts(PortType::Out), - // var.value().nPorts(PortType::In)), - // PortType::Out) - // .toPoint() - // + - QPoint{-statusIconSize().width() / 2, 0}; - - return QRect{iconPos, statusIconSize()}; -} - -const QIcon NodeGraphicsObject::processingStatusIcon() const -{ - QVariant var = _graphModel.nodeData(_nodeId, NodeRole::ProcessingStatus); - - switch (var.value()) { - case QtNodes::NodeProcessingStatus::NoStatus: - return QIcon(); - case QtNodes::NodeProcessingStatus::Updated: - return _statusUpdated; - case QtNodes::NodeProcessingStatus::Processing: - return _statusProcessing; - case QtNodes::NodeProcessingStatus::Pending: - return _statusPending; - case QtNodes::NodeProcessingStatus::Empty: - return _statusEmpty; - case QtNodes::NodeProcessingStatus::Failed: - return _statusInvalid; - case QtNodes::NodeProcessingStatus::Partial: - return _statusPartial; - } - return _statusInvalid; -} - -QSize NodeGraphicsObject::statusIconSize() const -{ - return _statusIconSize; -} - } // namespace QtNodes diff --git a/src/NodeStyle.cpp b/src/NodeStyle.cpp index cb7b17090..4b469ea1c 100644 --- a/src/NodeStyle.cpp +++ b/src/NodeStyle.cpp @@ -8,7 +8,6 @@ #include - using QtNodes::NodeStyle; inline void initResources() @@ -89,14 +88,14 @@ void NodeStyle::setNodeStyle(QString jsonText) #define NODE_STYLE_READ_BOOL(values, variable) \ { \ - auto valueRef = values[#variable]; \ - NODE_STYLE_CHECK_UNDEFINED_VALUE(valueRef, variable) \ - variable = valueRef.toBool(); \ + auto valueRef = values[#variable]; \ + NODE_STYLE_CHECK_UNDEFINED_VALUE(valueRef, variable) \ + variable = valueRef.toBool(); \ } #define NODE_STYLE_WRITE_BOOL(values, variable) \ { \ - values[#variable] = variable; \ + values[#variable] = variable; \ } void NodeStyle::loadJson(QJsonObject const &json) From 422cdd52e28096976bb9600f3a0e481092f4bc41 Mon Sep 17 00:00:00 2001 From: Gabrielnmds Date: Tue, 5 Aug 2025 14:49:10 -0300 Subject: [PATCH 25/31] adds zoomFitAll and zoomFitSelected methods --- include/QtNodes/internal/GraphicsView.hpp | 4 ++++ src/GraphicsView.cpp | 22 ++++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/include/QtNodes/internal/GraphicsView.hpp b/include/QtNodes/internal/GraphicsView.hpp index f21c58799..9a657962d 100644 --- a/include/QtNodes/internal/GraphicsView.hpp +++ b/include/QtNodes/internal/GraphicsView.hpp @@ -58,6 +58,10 @@ public Q_SLOTS: virtual void onPasteObjects(); + void zoomFitAll(); + + void zoomFitSelected(); + Q_SIGNALS: void scaleChanged(double scale); diff --git a/src/GraphicsView.cpp b/src/GraphicsView.cpp index c587f081c..4b996b8ec 100644 --- a/src/GraphicsView.cpp +++ b/src/GraphicsView.cpp @@ -434,3 +434,25 @@ QPointF GraphicsView::scenePastePosition() return mapToScene(origin); } + +void GraphicsView::zoomFitAll() +{ + fitInView(scene()->itemsBoundingRect(), Qt::KeepAspectRatio); + //clipCurrentScale(); +} + +void GraphicsView::zoomFitSelected() +{ + if(scene()->selectedItems().count() > 0){ + + QRectF unitedBoundingRect{}; + + for(QGraphicsItem * item : scene()->selectedItems()) + { + unitedBoundingRect = unitedBoundingRect.united(item->mapRectToScene(item->boundingRect())); + } + + fitInView(unitedBoundingRect, Qt::KeepAspectRatio); + //clipCurrentScale(); + } +} From 9fdc3e81e8de20ff1a4235efdb1d8386b843fc59 Mon Sep 17 00:00:00 2001 From: Gabrielnmds Date: Mon, 15 Dec 2025 15:19:03 -0300 Subject: [PATCH 26/31] refactor(zoom fit): minor code fixes --- src/GraphicsView.cpp | 44 ++++++++++++++++++++++++++++---------------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/src/GraphicsView.cpp b/src/GraphicsView.cpp index 4b996b8ec..0ba9f21a0 100644 --- a/src/GraphicsView.cpp +++ b/src/GraphicsView.cpp @@ -75,8 +75,7 @@ QAction *GraphicsView::deleteSelectionAction() const void GraphicsView::setScene(BasicGraphicsScene *scene) { QGraphicsView::setScene(scene); - if (!scene) - { + if (!scene) { // Clear actions. delete _clearSelectionAction; delete _deleteSelectionAction; @@ -181,12 +180,21 @@ void GraphicsView::centerScene() void GraphicsView::contextMenuEvent(QContextMenuEvent *event) { - if (itemAt(event->pos())) { - QGraphicsView::contextMenuEvent(event); - return; + QGraphicsView::contextMenuEvent(event); + QMenu *menu = nullptr; + + bool isZoomFitMenu; + + if (auto *dfModel = dynamic_cast(&nodeScene()->graphModel())) { + if (auto n = qgraphicsitem_cast(itemAt(event->pos()))) { + if (auto *delegate = dfModel->delegateModel(n->nodeId())) { + isZoomFitMenu = delegate->zoomFitMenu(); + } + } } - if (!nodeScene()) return; + if (!nodeScene()) + return; auto const scenePos = mapToScene(event->pos()); @@ -291,14 +299,16 @@ void GraphicsView::setupScale(double scale) void GraphicsView::onDeleteSelectedObjects() { - if (!nodeScene()) return; + if (!nodeScene()) + return; nodeScene()->undoStack().push(new DeleteCommand(nodeScene())); } void GraphicsView::onDuplicateSelectedObjects() { - if (!nodeScene()) return; + if (!nodeScene()) + return; QPointF const pastePosition = scenePastePosition(); @@ -308,14 +318,16 @@ void GraphicsView::onDuplicateSelectedObjects() void GraphicsView::onCopySelectedObjects() { - if (!nodeScene()) return; + if (!nodeScene()) + return; nodeScene()->undoStack().push(new CopyCommand(nodeScene())); } void GraphicsView::onPasteObjects() { - if (!nodeScene()) return; + if (!nodeScene()) + return; QPointF const pastePosition = scenePastePosition(); nodeScene()->undoStack().push(new PasteCommand(nodeScene(), pastePosition)); @@ -360,7 +372,8 @@ void GraphicsView::mouseMoveEvent(QMouseEvent *event) { QGraphicsView::mouseMoveEvent(event); - if (!scene()) return; + if (!scene()) + return; if (scene()->mouseGrabberItem() == nullptr && event->buttons() == Qt::LeftButton) { // Make sure shift is not being pressed @@ -443,13 +456,12 @@ void GraphicsView::zoomFitAll() void GraphicsView::zoomFitSelected() { - if(scene()->selectedItems().count() > 0){ - + if (scene()->selectedItems().count() > 0) { QRectF unitedBoundingRect{}; - for(QGraphicsItem * item : scene()->selectedItems()) - { - unitedBoundingRect = unitedBoundingRect.united(item->mapRectToScene(item->boundingRect())); + for (QGraphicsItem *item : scene()->selectedItems()) { + unitedBoundingRect = unitedBoundingRect.united( + item->mapRectToScene(item->boundingRect())); } fitInView(unitedBoundingRect, Qt::KeepAspectRatio); From f2473e7fd234627297d43f0bc5e8f2395f009ea2 Mon Sep 17 00:00:00 2001 From: Gabrielnmds Date: Wed, 17 Dec 2025 11:56:56 -0300 Subject: [PATCH 27/31] refactor(zoom fit): fixes code conflicts --- .../QtNodes/internal/BasicGraphicsScene.hpp | 6 +- .../QtNodes/internal/NodeDelegateModel.hpp | 6 ++ src/BasicGraphicsScene.cpp | 68 +++++++++++++++++++ src/GraphicsView.cpp | 24 ++++--- 4 files changed, 95 insertions(+), 9 deletions(-) diff --git a/include/QtNodes/internal/BasicGraphicsScene.hpp b/include/QtNodes/internal/BasicGraphicsScene.hpp index 568835ff7..21fea8389 100644 --- a/include/QtNodes/internal/BasicGraphicsScene.hpp +++ b/include/QtNodes/internal/BasicGraphicsScene.hpp @@ -17,7 +17,6 @@ #include #include - class QUndoStack; namespace QtNodes { @@ -112,6 +111,8 @@ class NODE_EDITOR_PUBLIC BasicGraphicsScene : public QGraphicsScene */ virtual QMenu *createSceneMenu(QPointF const scenePos); + QMenu *createZoomMenu(QPointF const scenePos); + Q_SIGNALS: void modified(BasicGraphicsScene *); void nodeMoved(NodeId const nodeId, QPointF const &newLocation); @@ -125,6 +126,9 @@ class NODE_EDITOR_PUBLIC BasicGraphicsScene : public QGraphicsScene /// Signal allows showing custom context menu upon clicking a node. void nodeContextMenu(NodeId const nodeId, QPointF const pos); + /// Signals to call Graphics View's zoomFit methods + void zoomFitAllClicked(); + void zoomFitSelectedClicked(); private: /** diff --git a/include/QtNodes/internal/NodeDelegateModel.hpp b/include/QtNodes/internal/NodeDelegateModel.hpp index 04cc78a12..46dc94cc8 100644 --- a/include/QtNodes/internal/NodeDelegateModel.hpp +++ b/include/QtNodes/internal/NodeDelegateModel.hpp @@ -132,6 +132,10 @@ class NODE_EDITOR_PUBLIC NodeDelegateModel virtual bool resizable() const { return false; } + bool zoomFitMenu() const { return _zoomFitMenu; } + + void setZoomFitMenu(bool state) { _zoomFitMenu = state; } + public Q_SLOTS: virtual void inputConnectionCreated(ConnectionId const &) {} virtual void inputConnectionDeleted(ConnectionId const &) {} @@ -188,6 +192,8 @@ public Q_SLOTS: NodeValidationState _nodeValidationState; NodeProcessingStatus _processingStatus{NodeProcessingStatus::NoStatus}; + + bool _zoomFitMenu{false}; }; } // namespace QtNodes diff --git a/src/BasicGraphicsScene.cpp b/src/BasicGraphicsScene.cpp index 84bf9a314..2fbed3369 100644 --- a/src/BasicGraphicsScene.cpp +++ b/src/BasicGraphicsScene.cpp @@ -11,6 +11,10 @@ #include +#include +#include +#include +#include #include #include @@ -203,6 +207,70 @@ QMenu *BasicGraphicsScene::createSceneMenu(QPointF const scenePos) return nullptr; } +QMenu *BasicGraphicsScene::createZoomMenu(QPointF const scenePos) +{ + Q_UNUSED(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, "Zoom Fit All"); + + auto unfreezeItem = new QTreeWidgetItem(treeView); + unfreezeItem->setText(0, "Zoom Fit Selected"); + + treeView->expandAll(); + + connect(treeView, &QTreeWidget::itemClicked, [this, menu](QTreeWidgetItem *item, int) { + if (item->text(0) == "Zoom Fit All") { + Q_EMIT zoomFitAllClicked(); + + menu->close(); + return; + } + if (item->text(0) == "Zoom Fit Selected") { + Q_EMIT zoomFitSelectedClicked(); + + menu->close(); + return; + } + }); + + // Filter + 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/GraphicsView.cpp b/src/GraphicsView.cpp index 0ba9f21a0..57905d002 100644 --- a/src/GraphicsView.cpp +++ b/src/GraphicsView.cpp @@ -2,6 +2,7 @@ #include "BasicGraphicsScene.hpp" #include "ConnectionGraphicsObject.hpp" +#include "DataFlowGraphModel.hpp" #include "NodeGraphicsObject.hpp" #include "StyleCollection.hpp" #include "UndoCommands.hpp" @@ -23,6 +24,7 @@ #include using QtNodes::BasicGraphicsScene; +using QtNodes::DataFlowGraphModel; using QtNodes::GraphicsView; GraphicsView::GraphicsView(QWidget *parent) @@ -161,6 +163,13 @@ void GraphicsView::setScene(BasicGraphicsScene *scene) auto redoAction = scene->undoStack().createRedoAction(this, tr("&Redo")); redoAction->setShortcuts(QKeySequence::Redo); addAction(redoAction); + + /// Connections to context menu funcionality + connect(scene, &BasicGraphicsScene::zoomFitAllClicked, this, &GraphicsView::zoomFitAll); + connect(scene, + &BasicGraphicsScene::zoomFitSelectedClicked, + this, + &GraphicsView::zoomFitSelected); } void GraphicsView::centerScene() @@ -193,16 +202,17 @@ void GraphicsView::contextMenuEvent(QContextMenuEvent *event) } } - if (!nodeScene()) - return; - - auto const scenePos = mapToScene(event->pos()); - - QMenu *menu = nodeScene()->createSceneMenu(scenePos); + if (itemAt(event->pos()) && isZoomFitMenu) { + menu = nodeScene()->createZoomMenu(mapToScene(event->pos())); + } else if (!itemAt(event->pos())) { + menu = nodeScene()->createSceneMenu(mapToScene(event->pos())); + } if (menu) { menu->exec(event->globalPos()); } + + return; } void GraphicsView::wheelEvent(QWheelEvent *event) @@ -451,7 +461,6 @@ QPointF GraphicsView::scenePastePosition() void GraphicsView::zoomFitAll() { fitInView(scene()->itemsBoundingRect(), Qt::KeepAspectRatio); - //clipCurrentScale(); } void GraphicsView::zoomFitSelected() @@ -465,6 +474,5 @@ void GraphicsView::zoomFitSelected() } fitInView(unitedBoundingRect, Qt::KeepAspectRatio); - //clipCurrentScale(); } } From bc1b6cb62a5ff02289c672b38bd8b5f0836aad59 Mon Sep 17 00:00:00 2001 From: Gabrielnmds Date: Wed, 17 Dec 2025 15:37:11 -0300 Subject: [PATCH 28/31] Refactor(node editor): solve conflicts --- external/CMakeLists.txt | 2 +- test/CMakeLists.txt | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt index 0d39af5c9..d7fbfcca7 100644 --- a/external/CMakeLists.txt +++ b/external/CMakeLists.txt @@ -1,5 +1,5 @@ if(BUILD_TESTING) - find_package(Catch2 2.13.7 QUIET) + find_package(Catch2 QUIET) if(NOT Catch2_FOUND) add_subdirectory(Catch2) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 98a8c306a..681251330 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -4,8 +4,6 @@ else() find_package(Qt5 COMPONENTS Test) endif() -find_package(Catch2) - add_executable(test_nodes test_main.cpp src/TestAbstractGraphModel.cpp From 2b3ac01c8cfbfb747ad4e2bffd5cf2b705d969e1 Mon Sep 17 00:00:00 2001 From: g-abilio Date: Wed, 7 Jan 2026 15:05:34 -0300 Subject: [PATCH 29/31] fix uninitialized variable and remove return --- src/GraphicsView.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/GraphicsView.cpp b/src/GraphicsView.cpp index 57905d002..e331d2957 100644 --- a/src/GraphicsView.cpp +++ b/src/GraphicsView.cpp @@ -192,7 +192,7 @@ void GraphicsView::contextMenuEvent(QContextMenuEvent *event) QGraphicsView::contextMenuEvent(event); QMenu *menu = nullptr; - bool isZoomFitMenu; + bool isZoomFitMenu = false; if (auto *dfModel = dynamic_cast(&nodeScene()->graphModel())) { if (auto n = qgraphicsitem_cast(itemAt(event->pos()))) { @@ -211,8 +211,6 @@ void GraphicsView::contextMenuEvent(QContextMenuEvent *event) if (menu) { menu->exec(event->globalPos()); } - - return; } void GraphicsView::wheelEvent(QWheelEvent *event) From 891b70235073ee8b5a8176075473ead5ac3ea689 Mon Sep 17 00:00:00 2001 From: g-abilio Date: Wed, 7 Jan 2026 15:43:25 -0300 Subject: [PATCH 30/31] remove bad coupling --- include/QtNodes/internal/AbstractGraphModel.hpp | 14 ++++++++------ include/QtNodes/internal/DataFlowGraphModel.hpp | 8 +++++--- src/DataFlowGraphModel.cpp | 11 +++++++++++ src/GraphicsView.cpp | 12 ++++++------ 4 files changed, 30 insertions(+), 15 deletions(-) diff --git a/include/QtNodes/internal/AbstractGraphModel.hpp b/include/QtNodes/internal/AbstractGraphModel.hpp index 716550114..b4d928291 100644 --- a/include/QtNodes/internal/AbstractGraphModel.hpp +++ b/include/QtNodes/internal/AbstractGraphModel.hpp @@ -1,8 +1,8 @@ #pragma once -#include "Export.hpp" #include "ConnectionIdHash.hpp" #include "Definitions.hpp" +#include "Export.hpp" #include #include @@ -10,7 +10,6 @@ #include - namespace QtNodes { /** @@ -51,8 +50,7 @@ class NODE_EDITOR_PUBLIC AbstractGraphModel : public QObject */ virtual std::unordered_set connections(NodeId nodeId, PortType portType, - PortIndex index) const - = 0; + PortIndex index) const = 0; /// Checks if two nodes with the given `connectionId` are connected. virtual bool connectionExists(ConnectionId const connectionId) const = 0; @@ -132,8 +130,10 @@ class NODE_EDITOR_PUBLIC AbstractGraphModel : public QObject * @returns Port Data Type, Port Data, Connection Policy, Port * Caption. */ - virtual QVariant portData(NodeId nodeId, PortType portType, PortIndex index, PortRole role) const - = 0; + virtual QVariant portData(NodeId nodeId, + PortType portType, + PortIndex index, + PortRole role) const = 0; /** * A utility function that unwraps the `QVariant` value returned from the @@ -188,6 +188,8 @@ class NODE_EDITOR_PUBLIC AbstractGraphModel : public QObject virtual bool loopsEnabled() const { return true; } + virtual bool nodeZoomFitMenu(NodeId) { return false; } + public: /** * Function clears connections attached to the ports that are scheduled to be diff --git a/include/QtNodes/internal/DataFlowGraphModel.hpp b/include/QtNodes/internal/DataFlowGraphModel.hpp index ff93c6eb4..c6c069df9 100644 --- a/include/QtNodes/internal/DataFlowGraphModel.hpp +++ b/include/QtNodes/internal/DataFlowGraphModel.hpp @@ -14,7 +14,9 @@ namespace QtNodes { -class NODE_EDITOR_PUBLIC DataFlowGraphModel : public AbstractGraphModel, public Serializable +class NODE_EDITOR_PUBLIC DataFlowGraphModel + : public AbstractGraphModel + , public Serializable { Q_OBJECT @@ -43,7 +45,6 @@ class NODE_EDITOR_PUBLIC DataFlowGraphModel : public AbstractGraphModel, public NodeId addNode(QString const nodeType) override; - bool connectionPossible(ConnectionId const connectionId) const override; void addConnection(ConnectionId const connectionId) override; @@ -75,7 +76,6 @@ class NODE_EDITOR_PUBLIC DataFlowGraphModel : public AbstractGraphModel, public void loadNode(QJsonObject const &nodeJson) override; - // From Serializable QJsonObject save() const override; @@ -101,6 +101,8 @@ class NODE_EDITOR_PUBLIC DataFlowGraphModel : public AbstractGraphModel, public /// Loops do not make any sense in uni-direction data propagation bool loopsEnabled() const override { return false; } + bool nodeZoomFitMenu(NodeId) override; + Q_SIGNALS: void inPortDataWasSet(NodeId const, PortType const, PortIndex const); diff --git a/src/DataFlowGraphModel.cpp b/src/DataFlowGraphModel.cpp index fa5655cde..aa4189056 100644 --- a/src/DataFlowGraphModel.cpp +++ b/src/DataFlowGraphModel.cpp @@ -646,4 +646,15 @@ void DataFlowGraphModel::propagateEmptyDataTo(NodeId const nodeId, PortIndex con setPortData(nodeId, PortType::In, portIndex, emptyData, PortRole::Data); } +bool DataFlowGraphModel::nodeZoomFitMenu(NodeId const nodeId) +{ + bool isZoomFitMenu = false; + auto delegate = delegateModel(nodeId); + + if (delegate) + isZoomFitMenu = delegate->zoomFitMenu(); + + return isZoomFitMenu; +} + } // namespace QtNodes diff --git a/src/GraphicsView.cpp b/src/GraphicsView.cpp index e331d2957..c87d09657 100644 --- a/src/GraphicsView.cpp +++ b/src/GraphicsView.cpp @@ -26,6 +26,7 @@ using QtNodes::BasicGraphicsScene; using QtNodes::DataFlowGraphModel; using QtNodes::GraphicsView; +using QtNodes::NodeGraphicsObject; GraphicsView::GraphicsView(QWidget *parent) : QGraphicsView(parent) @@ -194,12 +195,11 @@ void GraphicsView::contextMenuEvent(QContextMenuEvent *event) bool isZoomFitMenu = false; - if (auto *dfModel = dynamic_cast(&nodeScene()->graphModel())) { - if (auto n = qgraphicsitem_cast(itemAt(event->pos()))) { - if (auto *delegate = dfModel->delegateModel(n->nodeId())) { - isZoomFitMenu = delegate->zoomFitMenu(); - } - } + auto *dfModel = &nodeScene()->graphModel(); + auto n = qgraphicsitem_cast(itemAt(event->pos())); + + if (dfModel && n) { + isZoomFitMenu = dfModel->nodeZoomFitMenu(n->nodeId()); } if (itemAt(event->pos()) && isZoomFitMenu) { From 8d6c26b96786397a8d883c3358d32c2bab41368e Mon Sep 17 00:00:00 2001 From: g-abilio Date: Mon, 12 Jan 2026 14:41:37 -0300 Subject: [PATCH 31/31] refactoring to simpler code --- src/DataFlowGraphModel.cpp | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/DataFlowGraphModel.cpp b/src/DataFlowGraphModel.cpp index aa4189056..c06743598 100644 --- a/src/DataFlowGraphModel.cpp +++ b/src/DataFlowGraphModel.cpp @@ -648,13 +648,8 @@ void DataFlowGraphModel::propagateEmptyDataTo(NodeId const nodeId, PortIndex con bool DataFlowGraphModel::nodeZoomFitMenu(NodeId const nodeId) { - bool isZoomFitMenu = false; auto delegate = delegateModel(nodeId); - - if (delegate) - isZoomFitMenu = delegate->zoomFitMenu(); - - return isZoomFitMenu; + return delegate && delegate->zoomFitMenu(); } } // namespace QtNodes