Skip to content
1 change: 1 addition & 0 deletions examples/calculator/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ set(CALC_HEADER_FILES
NumberDisplayDataModel.hpp
NumberSourceDataModel.hpp
SubtractionModel.hpp
LongProcessingRandomNumber.hpp
)

add_executable(calculator
Expand Down
103 changes: 73 additions & 30 deletions examples/calculator/LongProcessingRandomNumber.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@
#include <QtNodes/NodeDelegateModel>
#include <QTimer>
#include <QtCore/QObject>
#include <QtCore/QElapsedTimer>
#include <QtWidgets/QLabel>
#include <QtCore/QRandomGenerator64>

#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
{
Expand All @@ -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<int>((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"); }
Expand All @@ -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<double>::max());
double randomValue = QRandomGenerator::global()->generateDouble() * (upper - a) + a;
setProgressValue(QString());

_result = std::make_shared<DecimalData>(randomValue);
Q_EMIT computingFinished();
} else {
_result.reset();
emit requestNodeUpdate();
return;
}

Q_EMIT dataUpdated(outPortIndex);
double upper = std::nextafter(b, std::numeric_limits<double>::max());
double randomValue = QRandomGenerator::global()->generateDouble() * (upper - a) + a;

_result = std::make_shared<DecimalData>(randomValue);
emit computingFinished();
} else {
_result.reset();
}

Q_EMIT dataUpdated(outPortIndex);
});
}

QTimer *_progressTimer = nullptr;
QElapsedTimer _elapsedTimer;

const int _totalDurationMs = 3000;
const int _progressUpdateIntervalMs = 50;
};
2 changes: 2 additions & 0 deletions include/QtNodes/internal/DefaultNodePainter.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"};
};
Expand Down
31 changes: 16 additions & 15 deletions include/QtNodes/internal/Definitions.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)

/**
Expand Down
8 changes: 8 additions & 0 deletions include/QtNodes/internal/NodeDelegateModel.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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> nodeData, PortIndex const portIndex) = 0;

Expand Down Expand Up @@ -184,6 +190,8 @@ public Q_SLOTS:
NodeValidationState _nodeValidationState;

NodeProcessingStatus _processingStatus{NodeProcessingStatus::NoStatus};

QString _progressValue{QString()};
};

} // namespace QtNodes
Expand Down
8 changes: 7 additions & 1 deletion src/DataFlowGraphModel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -382,8 +386,10 @@ bool DataFlowGraphModel::setNodeData(NodeId nodeId, NodeRole role, QVariant valu
}
Q_EMIT nodeUpdated(nodeId);
} break;
}

case NodeRole::ProgressValue:
break;
}
return result;
}

Expand Down
1 change: 1 addition & 0 deletions src/DefaultHorizontalNodeGeometry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<int>();

if (processingStatusValue != 0)
Expand Down
29 changes: 29 additions & 0 deletions src/DefaultNodePainter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Loading