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 d64f4ba7..d56d0bd6 100644 --- a/examples/calculator/LongProcessingRandomNumber.hpp +++ b/examples/calculator/LongProcessingRandomNumber.hpp @@ -3,14 +3,15 @@ #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 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 { @@ -20,21 +21,53 @@ class RandomNumberModel : public MathOperationDataModel 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 ~RandomNumberModel() { + if (_progressTimer) { + _progressTimer->stop(); + delete _progressTimer; + } + } public: QString caption() const override { return QStringLiteral("Random Number"); } @@ -44,40 +77,50 @@ class RandomNumberModel : public MathOperationDataModel private: void compute() override { + 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); - 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) { - 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); }); } + + QTimer *_progressTimer = nullptr; + QElapsedTimer _elapsedTimer; + + const int _totalDurationMs = 3000; + const int _progressUpdateIntervalMs = 50; }; diff --git a/include/QtNodes/internal/DefaultNodePainter.hpp b/include/QtNodes/internal/DefaultNodePainter.hpp index 953faa06..4ba13e43 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 8730805c..fc4c19f9 100644 --- a/include/QtNodes/internal/NodeDelegateModel.hpp +++ b/include/QtNodes/internal/NodeDelegateModel.hpp @@ -85,6 +85,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; @@ -109,6 +113,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; @@ -184,6 +190,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 fa5655cd..5e7ea27f 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 d313d3a8..33260ede 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 @@ -375,4 +377,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