diff --git a/examples/calculator/CMakeLists.txt b/examples/calculator/CMakeLists.txt index dae6ee3b..dfeae5c4 100644 --- a/examples/calculator/CMakeLists.txt +++ b/examples/calculator/CMakeLists.txt @@ -13,6 +13,7 @@ set(CALC_HEADER_FILES NumberDisplayDataModel.hpp NumberSourceDataModel.hpp SubtractionModel.hpp + LongProcessingRandomNumber.hpp ) add_executable(calculator diff --git a/examples/calculator/LongProcessingRandomNumber.hpp b/examples/calculator/LongProcessingRandomNumber.hpp index 7977adbb..04275dbe 100644 --- a/examples/calculator/LongProcessingRandomNumber.hpp +++ b/examples/calculator/LongProcessingRandomNumber.hpp @@ -1,37 +1,72 @@ #pragma once -#include #include +#include #include -#include #include +#include +#include -#include "MathOperationDataModel.hpp" #include "DecimalData.hpp" +#include "MathOperationDataModel.hpp" -/// The model generates a random value in a long processing schema, -/// as it should demonstrate the usage of the NodeProcessingStatus. +/// The model generates a random value in a long processing schema, as it should demonstrate +/// the usage of the NodeProcessingStatus and the ProgressValue functionality. /// The random number is generate in the [n1, n2] interval. -class RandomNumberModel : public MathOperationDataModel +class LongProcessingRandomNumber : public MathOperationDataModel { public: - RandomNumberModel() - : _timer(new QTimer(this)) + LongProcessingRandomNumber() { this->setNodeProcessingStatus(QtNodes::NodeProcessingStatus::Empty); QObject::connect(this, &NodeDelegateModel::computingStarted, this, [this]() { - if (_number1.lock() && _number2.lock()) { - this->setNodeProcessingStatus(QtNodes::NodeProcessingStatus::Processing); + this->setNodeProcessingStatus(QtNodes::NodeProcessingStatus::Processing); + + setProgressValue(QString{"0%"}); + emit requestNodeUpdate(); + + _elapsedTimer.start(); + + if (!_progressTimer) { + _progressTimer = new QTimer(this); + connect(_progressTimer, &QTimer::timeout, this, [this]() { + qint64 elapsed = _elapsedTimer.elapsed(); + int percent = static_cast((double(elapsed) / _totalDurationMs) * 100.0); + + if (percent > 100) + percent = 100; + + setProgressValue(QString::number(percent) + "%"); + emit requestNodeUpdate(); + }); } + + _progressTimer->start(_progressUpdateIntervalMs); + emit requestNodeUpdate(); }); + QObject::connect(this, &NodeDelegateModel::computingFinished, this, [this]() { + if (_progressTimer) { + _progressTimer->stop(); + } + + setProgressValue(QString()); + this->setNodeProcessingStatus(QtNodes::NodeProcessingStatus::Updated); + emit requestNodeUpdate(); }); } - virtual ~RandomNumberModel() {} + + virtual ~LongProcessingRandomNumber() + { + if (_progressTimer) { + _progressTimer->stop(); + delete _progressTimer; + } + } public: QString caption() const override { return QStringLiteral("Random Number"); } @@ -41,47 +76,50 @@ class RandomNumberModel : public MathOperationDataModel private: void compute() override { - // Stop any previous computation - _timer->stop(); - _timer->disconnect(); + auto n1 = _number1.lock(); + auto n2 = _number2.lock(); + + if (!n1 || !n2) { + return; + } Q_EMIT computingStarted(); PortIndex const outPortIndex = 0; - auto n1 = _number1.lock(); - auto n2 = _number2.lock(); + QTimer::singleShot(_totalDurationMs, this, [this, n1, n2]() { + if (n1 && n2) { + double a = n1->number(); + double b = n2->number(); + + if (a > b) { + setNodeProcessingStatus(QtNodes::NodeProcessingStatus::Failed); - _secondsRemaining = 3; - _timer->start(1000); - connect(_timer, &QTimer::timeout, this, [this, n1, n2, outPortIndex]() { - if (--_secondsRemaining <= 0) { - _timer->stop(); - if (n1 && n2) { - double a = n1->number(); - double b = n2->number(); - - if (a > b) { - setNodeProcessingStatus(QtNodes::NodeProcessingStatus::Failed); - emit requestNodeUpdate(); - return; + if (_progressTimer) { + _progressTimer->stop(); } - double upper = std::nextafter(b, std::numeric_limits::max()); - double randomValue = QRandomGenerator::global()->generateDouble() * (upper - a) - + a; + setProgressValue(QString()); - _result = std::make_shared(randomValue); - Q_EMIT computingFinished(); - } else { - _result.reset(); + emit requestNodeUpdate(); + return; } - Q_EMIT dataUpdated(outPortIndex); + double upper = std::nextafter(b, std::numeric_limits::max()); + double randomValue = QRandomGenerator::global()->generateDouble() * (upper - a) + a; + + _result = std::make_shared(randomValue); + emit computingFinished(); + } else { + _result.reset(); } + + Q_EMIT dataUpdated(outPortIndex); }); } -private: - QTimer *_timer; - int _secondsRemaining = 0; + QTimer *_progressTimer = nullptr; + QElapsedTimer _elapsedTimer; + + const int _totalDurationMs = 3000; + const int _progressUpdateIntervalMs = 50; }; diff --git a/examples/calculator/headless_main.cpp b/examples/calculator/headless_main.cpp index 89103a4f..3ac6f290 100644 --- a/examples/calculator/headless_main.cpp +++ b/examples/calculator/headless_main.cpp @@ -28,7 +28,7 @@ static std::shared_ptr registerDataModels() ret->registerModel("Operators"); - ret->registerModel("Operators"); + ret->registerModel("Operators"); return ret; } diff --git a/examples/calculator/main.cpp b/examples/calculator/main.cpp index 4d2504f0..e9abbca5 100644 --- a/examples/calculator/main.cpp +++ b/examples/calculator/main.cpp @@ -41,7 +41,7 @@ static std::shared_ptr registerDataModels() ret->registerModel("Operators"); - ret->registerModel("Operators"); + ret->registerModel("Operators"); return ret; } diff --git a/include/QtNodes/internal/DefaultNodePainter.hpp b/include/QtNodes/internal/DefaultNodePainter.hpp index dbea2a3c..1828bb55 100644 --- a/include/QtNodes/internal/DefaultNodePainter.hpp +++ b/include/QtNodes/internal/DefaultNodePainter.hpp @@ -36,6 +36,8 @@ class NODE_EDITOR_PUBLIC DefaultNodePainter : public AbstractNodePainter void drawValidationIcon(QPainter *painter, NodeGraphicsObject &ngo) const; + void drawProgressValue(QPainter *painter, NodeGraphicsObject &ngo) const; + private: QIcon _toolTipIcon{":/info-tooltip.svg"}; }; diff --git a/include/QtNodes/internal/Definitions.hpp b/include/QtNodes/internal/Definitions.hpp index 8c01475f..de07bf20 100644 --- a/include/QtNodes/internal/Definitions.hpp +++ b/include/QtNodes/internal/Definitions.hpp @@ -18,23 +18,24 @@ 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 + ProcessingStatus = 12, ///< Enum NodeProcessingStatus of the node + ProgressValue = 13, ///< 'QString' for the progress value + }; Q_ENUM_NS(NodeRole) /** diff --git a/include/QtNodes/internal/NodeDelegateModel.hpp b/include/QtNodes/internal/NodeDelegateModel.hpp index 04cc78a1..303ef852 100644 --- a/include/QtNodes/internal/NodeDelegateModel.hpp +++ b/include/QtNodes/internal/NodeDelegateModel.hpp @@ -86,6 +86,10 @@ class NODE_EDITOR_PUBLIC NodeDelegateModel /// Returns the curent processing status virtual NodeProcessingStatus processingStatus() const { return _processingStatus; } + /// Progress is used in GUI + virtual QString progressValue() const { return _progressValue; } + +public: QJsonObject save() const override; void load(QJsonObject const &) override; @@ -113,6 +117,8 @@ class NODE_EDITOR_PUBLIC NodeDelegateModel void setStatusIconStyle(ProcessingIconStyle const &style); + void setProgressValue(QString new_progress) { _progressValue = new_progress; } + public: virtual void setInData(std::shared_ptr nodeData, PortIndex const portIndex) = 0; @@ -188,6 +194,8 @@ public Q_SLOTS: NodeValidationState _nodeValidationState; NodeProcessingStatus _processingStatus{NodeProcessingStatus::NoStatus}; + + QString _progressValue{QString()}; }; } // namespace QtNodes diff --git a/src/DataFlowGraphModel.cpp b/src/DataFlowGraphModel.cpp index d84add7f..2eb0b9fa 100644 --- a/src/DataFlowGraphModel.cpp +++ b/src/DataFlowGraphModel.cpp @@ -303,6 +303,10 @@ QVariant DataFlowGraphModel::nodeData(NodeId nodeId, NodeRole role) const auto processingStatus = model->processingStatus(); result = QVariant::fromValue(processingStatus); } break; + + case NodeRole::ProgressValue: + result = model->progressValue(); + break; } return result; @@ -382,8 +386,10 @@ bool DataFlowGraphModel::setNodeData(NodeId nodeId, NodeRole role, QVariant valu } Q_EMIT nodeUpdated(nodeId); } break; - } + case NodeRole::ProgressValue: + break; + } return result; } diff --git a/src/DefaultHorizontalNodeGeometry.cpp b/src/DefaultHorizontalNodeGeometry.cpp index a30121d2..466af743 100644 --- a/src/DefaultHorizontalNodeGeometry.cpp +++ b/src/DefaultHorizontalNodeGeometry.cpp @@ -55,6 +55,7 @@ void DefaultHorizontalNodeGeometry::recomputeSize(NodeId const nodeId) const height += _portSpasing; // space below caption QVariant var = _graphModel.nodeData(nodeId, NodeRole::ProcessingStatus); + auto processingStatusValue = var.value(); if (processingStatusValue != 0) diff --git a/src/DefaultNodePainter.cpp b/src/DefaultNodePainter.cpp index 96c7f16c..7a007b02 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); + + drawProgressValue(painter, ngo); } void DefaultNodePainter::drawNodeRect(QPainter *painter, NodeGraphicsObject &ngo) const @@ -386,4 +388,31 @@ void DefaultNodePainter::drawValidationIcon(QPainter *painter, NodeGraphicsObjec painter->restore(); } +void DefaultNodePainter::drawProgressValue(QPainter *painter, NodeGraphicsObject &ngo) const +{ + AbstractGraphModel &model = ngo.graphModel(); + NodeId const nodeId = ngo.nodeId(); + AbstractNodeGeometry &geometry = ngo.nodeScene()->nodeGeometry(); + + QString const nodeProgress = model.nodeData(nodeId, NodeRole::ProgressValue).toString(); + + QFont font = painter->font(); + font.setBold(true); + font.setPointSize(5); + auto rect = QFontMetrics(font).boundingRect(nodeProgress); + + QSize size = geometry.size(nodeId); + QPointF position(rect.width() / 4.0, size.height() - 0.5 * rect.height()); + + QJsonDocument json = QJsonDocument::fromVariant(model.nodeData(nodeId, NodeRole::Style)); + NodeStyle nodeStyle(json.object()); + + painter->setFont(font); + painter->setPen(nodeStyle.FontColor); + painter->drawText(position, nodeProgress); + + font.setBold(false); + painter->setFont(font); +} + } // namespace QtNodes