Skip to content

Commit

Permalink
Implement runtime validation and error reporting (#46)
Browse files Browse the repository at this point in the history
* Node validation and error display implemented

* Added option to iterate through nodes in the scene. (Useful to read out data from all the nodes, makes code generation based on the graphs easier.)

* Visual error fix for nodes that change size depending on input, plus minor styling and logic fixes in the example, based on the comments of @russelltg

* Validation extended to handle warnings, based on the idea of @RusselTG ; Error display colors now come from the style system;  Additional bugfixes

* DefaultStyle.json identation fix

* FlowScene.cpp identation fix

* Travis build test fix
  • Loading branch information
LandonJerre authored and paceholder committed Jan 25, 2017
1 parent a62f088 commit 770a1aa
Show file tree
Hide file tree
Showing 20 changed files with 240 additions and 7 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Expand Up @@ -32,7 +32,7 @@ matrix:

before_install:
#- if [ "$QT_BASE" = "54" ]; then sudo add-apt-repository ppa:beineri/opt-qt542-trusty -y; fi
- if [ "$QT_BASE" = "57" ]; then sudo add-apt-repository ppa:beineri/opt-qt57-trusty -y; fi
- if [ "$QT_BASE" = "57" ]; then sudo add-apt-repository ppa:beineri/opt-qt571-trusty -y; fi
- sudo apt-get update -qq

install:
Expand Down
4 changes: 4 additions & 0 deletions examples/calculator/AdditionModel.hpp
Expand Up @@ -52,11 +52,15 @@ class AdditionModel : public MathOperationDataModel

if (n1 && n2)
{
modelValidationState = NodeValidationState::Valid;
modelValidationError = QString("");
_result = std::make_shared<NumberData>(n1->number() +
n2->number());
}
else
{
modelValidationState = NodeValidationState::Warning;
modelValidationError = QString("Missing or incorrect inputs");
_result.reset();
}

Expand Down
18 changes: 14 additions & 4 deletions examples/calculator/DivisionModel.hpp
Expand Up @@ -45,7 +45,7 @@ class DivisionModel : public MathOperationDataModel
default:
break;
}
return QString("");
return QString("");
}

QString
Expand Down Expand Up @@ -74,16 +74,26 @@ class DivisionModel : public MathOperationDataModel
auto n1 = _number1.lock();
auto n2 = _number2.lock();

if (n1 && n2 && (n2->number() != 0.0))
if (n2 && (n2->number() == 0.0))
{
modelValidationState = NodeValidationState::Error;
modelValidationError = QString("Division by zero error");
_result.reset();
}
else if (n1 && n2)
{
modelValidationState = NodeValidationState::Valid;
modelValidationError = QString("");
_result = std::make_shared<NumberData>(n1->number() /
n2->number());
n2->number());
}
else
{
modelValidationState = NodeValidationState::Warning;
modelValidationError = QString("Missing or incorrect inputs");
_result.reset();
}

emit dataUpdated(outPortIndex);
}
};
14 changes: 14 additions & 0 deletions examples/calculator/MathOperationDataModel.cpp
Expand Up @@ -53,3 +53,17 @@ setInData(std::shared_ptr<NodeData> data, PortIndex portIndex)
}


NodeValidationState
MathOperationDataModel::
validationState() const
{
return modelValidationState;
}


QString
MathOperationDataModel::
validationMessage() const
{
return modelValidationError;
}
7 changes: 7 additions & 0 deletions examples/calculator/MathOperationDataModel.hpp
Expand Up @@ -32,6 +32,10 @@ class MathOperationDataModel : public NodeDataModel

QWidget * embeddedWidget() override { return nullptr; }

NodeValidationState validationState() const override;

QString validationMessage() const override;

protected:

virtual void compute() = 0;
Expand All @@ -42,4 +46,7 @@ class MathOperationDataModel : public NodeDataModel
std::weak_ptr<NumberData> _number2;

std::shared_ptr<NumberData> _result;

NodeValidationState modelValidationState = NodeValidationState::Warning;
QString modelValidationError = QString("Missing or incorrect inputs");
};
4 changes: 4 additions & 0 deletions examples/calculator/MultiplicationModel.hpp
Expand Up @@ -52,11 +52,15 @@ class MultiplicationModel : public MathOperationDataModel

if (n1 && n2)
{
modelValidationState = NodeValidationState::Valid;
modelValidationError = QString("");
_result = std::make_shared<NumberData>(n1->number() *
n2->number());
}
else
{
modelValidationState = NodeValidationState::Warning;
modelValidationError = QString("Missing or incorrect inputs");
_result.reset();
}

Expand Down
20 changes: 20 additions & 0 deletions examples/calculator/NumberDisplayDataModel.cpp
Expand Up @@ -58,12 +58,32 @@ setInData(std::shared_ptr<NodeData> data, int)

if (numberData)
{
modelValidationState = NodeValidationState::Valid;
modelValidationError = QString("");
_label->setText(numberData->numberAsText());
}
else
{
modelValidationState = NodeValidationState::Warning;
modelValidationError = QString("Missing or incorrect inputs");
_label->clear();
}

_label->adjustSize();
}


NodeValidationState
NumberDisplayDataModel::
validationState() const
{
return modelValidationState;
}


QString
NumberDisplayDataModel::
validationMessage() const
{
return modelValidationError;
}
9 changes: 9 additions & 0 deletions examples/calculator/NumberDisplayDataModel.hpp
Expand Up @@ -63,7 +63,16 @@ class NumberDisplayDataModel : public NodeDataModel
QWidget *
embeddedWidget() override { return _label; }

NodeValidationState
validationState() const override;

QString
validationMessage() const override;

private:

NodeValidationState modelValidationState = NodeValidationState::Warning;
QString modelValidationError = QString("Missing or incorrect inputs");

QLabel * _label;
};
4 changes: 4 additions & 0 deletions examples/calculator/SubtractionModel.hpp
Expand Up @@ -77,11 +77,15 @@ class SubtractionModel : public MathOperationDataModel

if (n1 && n2)
{
modelValidationState = NodeValidationState::Valid;
modelValidationError = QString("");
_result = std::make_shared<NumberData>(n1->number() -
n2->number());
}
else
{
modelValidationState = NodeValidationState::Warning;
modelValidationError = QString("Missing or incorrect inputs");
_result.reset();
}

Expand Down
2 changes: 2 additions & 0 deletions resources/DefaultStyle.json
Expand Up @@ -16,6 +16,8 @@
"FontColorFaded" : "gray",
"ConnectionPointColor": [169, 169, 169],
"FilledConnectionPointColor": "cyan",
"ErrorColor": "red",
"WarningColor": [128, 128, 0],

"PenWidth": 1.0,
"HoveredPenWidth": 1.5,
Expand Down
11 changes: 11 additions & 0 deletions src/FlowScene.cpp
Expand Up @@ -215,6 +215,17 @@ setRegistry(std::shared_ptr<DataModelRegistry> registry)
}


void
FlowScene::
iterateOverNodes(std::function<void(Node*)> visitor)
{
for (const auto& _node : _nodes)
{
visitor(_node.second.get());
}
}


//------------------------------------------------------------------------------

void
Expand Down
4 changes: 4 additions & 0 deletions src/FlowScene.hpp
Expand Up @@ -6,6 +6,7 @@
#include <unordered_map>
#include <tuple>
#include <memory>
#include <functional>

#include "Connection.hpp"
#include "Export.hpp"
Expand Down Expand Up @@ -64,6 +65,9 @@ class NODE_EDITOR_PUBLIC FlowScene
void
setRegistry(std::shared_ptr<DataModelRegistry> registry);

void
iterateOverNodes(std::function<void(Node*)> visitor);

public:

void
Expand Down
7 changes: 6 additions & 1 deletion src/Node.cpp
Expand Up @@ -176,7 +176,12 @@ propagateData(std::shared_ptr<NodeData> nodeData,
PortIndex inPortIndex) const
{
_nodeDataModel->setInData(nodeData, inPortIndex);


//Recalculate the nodes visuals. A data change can result in the node taking more space than before, so this forces a recalculate+repaint on the affected node
_nodeGraphicsObject->setGeometryChanged();
_nodeGeometry.recalculateSize();
_nodeGraphicsObject->update();
_nodeGraphicsObject->moveConnections();
}


Expand Down
15 changes: 15 additions & 0 deletions src/NodeDataModel.hpp
Expand Up @@ -10,6 +10,13 @@

#include "Export.hpp"

enum class NodeValidationState
{
Valid,
Warning,
Error
};

class NODE_EDITOR_PUBLIC NodeDataModel
: public QObject
, public Serializable
Expand Down Expand Up @@ -74,6 +81,14 @@ class NODE_EDITOR_PUBLIC NodeDataModel
virtual
bool
resizable() const { return false; }

virtual
NodeValidationState
validationState() const { return NodeValidationState::Valid; }

virtual
QString
validationMessage() const { return QString(""); }

signals:

Expand Down
36 changes: 35 additions & 1 deletion src/NodeGeometry.cpp
Expand Up @@ -89,6 +89,12 @@ recalculateSize() const
}

_width = std::max(_width, captionWidth());

if (_dataModel->validationState() != NodeValidationState::Valid)
{
_width = std::max(_width, validationWidth());
_height += validationHeight() + _spacing;
}
}


Expand Down Expand Up @@ -213,7 +219,11 @@ widgetPosition() const
{
if (auto w = _dataModel->embeddedWidget())
{

if (_dataModel->validationState() != NodeValidationState::Valid)
{
return QPointF(_spacing + portWidth(PortType::In),
(captionHeight() + _height - validationHeight() - _spacing - w->height()) / 2.0);
}
return QPointF(_spacing + portWidth(PortType::In),
(captionHeight() + _height - w->height()) / 2.0);
}
Expand Down Expand Up @@ -248,6 +258,26 @@ captionWidth() const
}


unsigned int
NodeGeometry::
validationHeight() const
{
QString msg = _dataModel->validationMessage();

return _boldFontMetrics.boundingRect(msg).height();
}


unsigned int
NodeGeometry::
validationWidth() const
{
QString msg = _dataModel->validationMessage();

return _boldFontMetrics.boundingRect(msg).width();
}


unsigned int
NodeGeometry::
portWidth(PortType portType) const
Expand All @@ -259,9 +289,13 @@ portWidth(PortType portType) const
QString name;

if (_dataModel->portCaptionVisible(portType, i))
{
name = _dataModel->portCaption(portType, i);
}
else
{
name = _dataModel->dataType(portType, i).name;
}

width = std::max(unsigned(_fontMetrics.width(name)),
width);
Expand Down
6 changes: 6 additions & 0 deletions src/NodeGeometry.hpp
Expand Up @@ -102,6 +102,12 @@ class NodeGeometry
QPointF
widgetPosition() const;

unsigned int
validationHeight() const;

unsigned int
validationWidth() const;

private:

unsigned int
Expand Down

0 comments on commit 770a1aa

Please sign in to comment.