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 f5a51179..2719c7b7 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 46246141..fb3c80cc 100644 --- a/include/QtNodes/internal/DefaultNodePainter.hpp +++ b/include/QtNodes/internal/DefaultNodePainter.hpp @@ -37,6 +37,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 3ab665f6..99339872 100644 --- a/include/QtNodes/internal/Definitions.hpp +++ b/include/QtNodes/internal/Definitions.hpp @@ -35,9 +35,11 @@ Q_NAMESPACE_EXPORT(NODE_EDITOR_PUBLIC) 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. + ProgressValue = 14, ///< 'QString' for the progress value + Label = 15, ///< `QString` for node label. + LabelEditable = 16, ///< `bool` to indicate label editing support. }; + Q_ENUM_NS(NodeRole) /** diff --git a/include/QtNodes/internal/NodeDelegateModel.hpp b/include/QtNodes/internal/NodeDelegateModel.hpp index 2f54fb3c..bb359a45 100644 --- a/include/QtNodes/internal/NodeDelegateModel.hpp +++ b/include/QtNodes/internal/NodeDelegateModel.hpp @@ -90,6 +90,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; @@ -118,6 +122,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; @@ -199,6 +205,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 55cee093..95d02887 100644 --- a/src/DataFlowGraphModel.cpp +++ b/src/DataFlowGraphModel.cpp @@ -321,6 +321,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; @@ -415,6 +419,9 @@ bool DataFlowGraphModel::setNodeData(NodeId nodeId, NodeRole role, QVariant valu case NodeRole::LabelEditable: break; + + case NodeRole::ProgressValue: + break; } return result; diff --git a/src/DefaultNodePainter.cpp b/src/DefaultNodePainter.cpp index 7c43822b..2d3dae9b 100644 --- a/src/DefaultNodePainter.cpp +++ b/src/DefaultNodePainter.cpp @@ -40,6 +40,8 @@ void DefaultNodePainter::paint(QPainter *painter, NodeGraphicsObject &ngo) const drawNodeLabel(painter, ngo); drawValidationIcon(painter, ngo); + + drawProgressValue(painter, ngo); } void DefaultNodePainter::drawNodeRect(QPainter *painter, NodeGraphicsObject &ngo) const @@ -431,4 +433,31 @@ void DefaultNodePainter::drawValidationIcon(QPainter *painter, NodeGraphicsObjec pixmap); } +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(7); + auto rect = QFontMetrics(font).boundingRect(nodeProgress); + + QSize size = geometry.size(nodeId); + QPointF position(rect.width() / 1.5, size.height() - 1.0 * 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