Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion include/QtNodes/internal/NodeGraphicsObject.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include <QtCore/QUuid>
#include <QtWidgets/QGraphicsObject>

#include "Export.hpp"
#include "NodeState.hpp"

class QGraphicsProxyWidget;
Expand All @@ -13,7 +14,7 @@ namespace QtNodes {
class BasicGraphicsScene;
class AbstractGraphModel;

class NodeGraphicsObject : public QGraphicsObject
class NODE_EDITOR_PUBLIC NodeGraphicsObject : public QGraphicsObject
{
Q_OBJECT
public:
Expand Down
31 changes: 31 additions & 0 deletions test/include/TestGraphModel.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -241,10 +241,41 @@ class TestGraphModel : public AbstractGraphModel
posObj["y"] = pos.y();
result["position"] = posObj;
}
auto typeIt = data.find(NodeRole::Type);
if (typeIt != data.end()) {
result["type"] = typeIt->second.toString();
}
}
return result;
}

void loadNode(QJsonObject const &nodeJson) override
{
NodeId id = static_cast<NodeId>(nodeJson["id"].toInt());

_nodeIds.insert(id);

if (id >= _nextNodeId) {
_nextNodeId = id + 1;
}

QJsonObject posObj = nodeJson["position"].toObject();
QPointF pos(posObj["x"].toDouble(), posObj["y"].toDouble());
_nodeData[id][NodeRole::Position] = pos;

if (nodeJson.contains("type")) {
_nodeData[id][NodeRole::Type] = nodeJson["type"].toString();
} else {
_nodeData[id][NodeRole::Type] = QString("TestNode");
}

_nodeData[id][NodeRole::Caption] = QString("Node %1").arg(id);
_nodeData[id][NodeRole::InPortCount] = 1u;
_nodeData[id][NodeRole::OutPortCount] = 1u;

Q_EMIT nodeCreated(id);
}

private:
NodeId _nextNodeId = 1;
std::unordered_set<NodeId> _nodeIds;
Expand Down
55 changes: 40 additions & 15 deletions test/src/TestCustomPainters.cpp
Original file line number Diff line number Diff line change
@@ -1,22 +1,27 @@
#include "ApplicationSetup.hpp"
#include "TestGraphModel.hpp"
#include "UITestHelper.hpp"

#include <catch2/catch.hpp>

#include <QtNodes/internal/AbstractConnectionPainter.hpp>
#include <QtNodes/internal/AbstractNodePainter.hpp>
#include <QtNodes/internal/BasicGraphicsScene.hpp>
#include <QtNodes/internal/ConnectionGraphicsObject.hpp>
#include <QtNodes/internal/GraphicsView.hpp>
#include <QtNodes/internal/NodeGraphicsObject.hpp>

#include <QImage>
#include <QPainter>
#include <QPixmap>
#include <QTest>

using QtNodes::AbstractConnectionPainter;
using QtNodes::AbstractNodePainter;
using QtNodes::BasicGraphicsScene;
using QtNodes::ConnectionGraphicsObject;
using QtNodes::ConnectionId;
using QtNodes::GraphicsView;
using QtNodes::NodeGraphicsObject;
using QtNodes::NodeId;
using QtNodes::NodeRole;
Expand Down Expand Up @@ -123,33 +128,53 @@ TEST_CASE("Custom painters registration", "[painters]")
}
}

TEST_CASE("Custom painter invocation", "[painters]")
TEST_CASE("Custom painter with scene operations", "[painters]")
{
auto app = applicationSetup();

TestGraphModel model;
BasicGraphicsScene scene(model);
auto model = std::make_shared<TestGraphModel>();
BasicGraphicsScene scene(*model);

auto customNodePainter = std::make_unique<TestNodePainter>();
TestNodePainter *nodePainterPtr = customNodePainter.get();
scene.setNodePainter(std::move(customNodePainter));

SECTION("Node painter is called when nodes are rendered")
SECTION("Custom painter persists after node creation and view operations")
{
GraphicsView view(&scene);
view.resize(800, 600);
view.show();
REQUIRE(QTest::qWaitForWindowExposed(&view));

NodeId nodeId = model->addNode("TestNode");
model->setNodeData(nodeId, NodeRole::Position, QPointF(100, 100));

QCoreApplication::processEvents();

// Verify the node graphics object exists
auto *ngo = scene.nodeGraphicsObject(nodeId);
REQUIRE(ngo != nullptr);

// Verify the custom painter is still set on the scene after all operations
CHECK(&scene.nodePainter() == nodePainterPtr);
}

SECTION("Custom painter persists through multiple node lifecycle events")
{
NodeId nodeId = model.addNode("TestNode");
model.setNodeData(nodeId, NodeRole::Position, QPointF(0, 0));
// Create nodes
NodeId node1 = model->addNode("TestNode1");
NodeId node2 = model->addNode("TestNode2");
model->setNodeData(node1, NodeRole::Position, QPointF(0, 0));
model->setNodeData(node2, NodeRole::Position, QPointF(200, 0));

// Force scene update
QCoreApplication::processEvents();

// Create a pixmap and render the scene to trigger painting
QPixmap pixmap(200, 200);
QPainter painter(&pixmap);
scene.render(&painter);
painter.end();
// Delete one node
model->deleteNode(node1);

QCoreApplication::processEvents();

// Painter should have been called at least once
CHECK(nodePainterPtr->paintCallCount > 0);
CHECK(nodePainterPtr->lastPaintedNodeId == nodeId);
// Custom painter should still be set
CHECK(&scene.nodePainter() == nodePainterPtr);
}
}
15 changes: 10 additions & 5 deletions test/src/TestLoopDetection.cpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#include "ApplicationSetup.hpp"
#include "TestGraphModel.hpp"
#include "TestDataFlowNodes.hpp"

Expand Down Expand Up @@ -35,6 +36,7 @@ TEST_CASE("Loop detection configuration", "[loops]")

SECTION("DataFlowGraphModel disables loops by default")
{
auto app = applicationSetup();
auto registry = std::make_shared<NodeDelegateModelRegistry>();
registry->registerModel<TestSourceNode>("Sources");

Expand All @@ -57,6 +59,7 @@ TEST_CASE("Loop detection configuration", "[loops]")

TEST_CASE("Loop detection in DataFlowGraphModel", "[loops]")
{
auto app = applicationSetup();
auto registry = std::make_shared<NodeDelegateModelRegistry>();
registry->registerModel<TestSourceNode>("Sources");
registry->registerModel<TestDisplayNode>("Sinks");
Expand Down Expand Up @@ -86,8 +89,9 @@ TEST_CASE("Loop detection in DataFlowGraphModel", "[loops]")

SECTION("Indirect loop A->B->A is prevented")
{
NodeId node1 = model.addNode("TestSourceNode");
NodeId node2 = model.addNode("TestSourceNode");
// Use TestDisplayNode which has both input and output ports
NodeId node1 = model.addNode("TestDisplayNode");
NodeId node2 = model.addNode("TestDisplayNode");

// Create A->B connection
ConnectionId conn1{node1, 0, node2, 0};
Expand All @@ -101,9 +105,10 @@ TEST_CASE("Loop detection in DataFlowGraphModel", "[loops]")

SECTION("Three node loop A->B->C->A is prevented")
{
NodeId node1 = model.addNode("TestSourceNode");
NodeId node2 = model.addNode("TestSourceNode");
NodeId node3 = model.addNode("TestSourceNode");
// Use TestDisplayNode which has both input and output ports
NodeId node1 = model.addNode("TestDisplayNode");
NodeId node2 = model.addNode("TestDisplayNode");
NodeId node3 = model.addNode("TestDisplayNode");

// Create A->B
ConnectionId conn1{node1, 0, node2, 0};
Expand Down
Loading