You've already forked nodeeditor
mirror of
https://github.com/AxioDL/nodeeditor.git
synced 2026-03-30 11:48:31 -07:00
590 lines
12 KiB
C++
590 lines
12 KiB
C++
#include "FlowScene.hpp"
|
|
|
|
#include <stdexcept>
|
|
#include <utility>
|
|
|
|
#include <QtWidgets/QGraphicsSceneMoveEvent>
|
|
#include <QtWidgets/QFileDialog>
|
|
#include <QtCore/QByteArray>
|
|
#include <QtCore/QBuffer>
|
|
#include <QtCore/QDataStream>
|
|
#include <QtCore/QFile>
|
|
|
|
#include <QtCore/QJsonDocument>
|
|
#include <QtCore/QJsonObject>
|
|
#include <QtCore/QJsonArray>
|
|
|
|
#include <QDebug>
|
|
|
|
#include "Node.hpp"
|
|
#include "NodeGraphicsObject.hpp"
|
|
|
|
#include "NodeGraphicsObject.hpp"
|
|
#include "ConnectionGraphicsObject.hpp"
|
|
|
|
#include "Connection.hpp"
|
|
|
|
#include "FlowView.hpp"
|
|
#include "DataModelRegistry.hpp"
|
|
|
|
using QtNodes::FlowScene;
|
|
using QtNodes::Node;
|
|
using QtNodes::NodeGraphicsObject;
|
|
using QtNodes::Connection;
|
|
using QtNodes::DataModelRegistry;
|
|
using QtNodes::NodeDataModel;
|
|
using QtNodes::PortType;
|
|
using QtNodes::PortIndex;
|
|
using QtNodes::TypeConverter;
|
|
|
|
|
|
FlowScene::
|
|
FlowScene(std::shared_ptr<DataModelRegistry> registry,
|
|
QObject * parent)
|
|
: QGraphicsScene(parent)
|
|
, _registry(std::move(registry))
|
|
{
|
|
setItemIndexMethod(QGraphicsScene::NoIndex);
|
|
}
|
|
|
|
FlowScene::
|
|
FlowScene(QObject * parent)
|
|
: FlowScene(std::make_shared<DataModelRegistry>(),
|
|
parent)
|
|
{}
|
|
|
|
|
|
FlowScene::
|
|
~FlowScene()
|
|
{
|
|
clearScene();
|
|
}
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
std::shared_ptr<Connection>
|
|
FlowScene::
|
|
createConnection(PortType connectedPort,
|
|
Node& node,
|
|
PortIndex portIndex)
|
|
{
|
|
auto connection = std::make_shared<Connection>(connectedPort, node, portIndex);
|
|
|
|
auto cgo = detail::make_unique<ConnectionGraphicsObject>(*this, *connection);
|
|
|
|
// after this function connection points are set to node port
|
|
connection->setGraphicsObject(std::move(cgo));
|
|
|
|
_connections[connection->id()] = connection;
|
|
|
|
connectionCreated(*connection);
|
|
return connection;
|
|
}
|
|
|
|
|
|
std::shared_ptr<Connection>
|
|
FlowScene::
|
|
createConnection(Node& nodeIn,
|
|
PortIndex portIndexIn,
|
|
Node& nodeOut,
|
|
PortIndex portIndexOut,
|
|
TypeConverter const &converter)
|
|
{
|
|
auto connection =
|
|
std::make_shared<Connection>(nodeIn,
|
|
portIndexIn,
|
|
nodeOut,
|
|
portIndexOut,
|
|
converter);
|
|
|
|
auto cgo = detail::make_unique<ConnectionGraphicsObject>(*this, *connection);
|
|
|
|
nodeIn.nodeState().setConnection(PortType::In, portIndexIn, *connection);
|
|
nodeOut.nodeState().setConnection(PortType::Out, portIndexOut, *connection);
|
|
|
|
// after this function connection points are set to node port
|
|
connection->setGraphicsObject(std::move(cgo));
|
|
|
|
// trigger data propagation
|
|
nodeOut.onDataUpdated(portIndexOut);
|
|
|
|
_connections[connection->id()] = connection;
|
|
|
|
connectionCreated(*connection);
|
|
|
|
return connection;
|
|
}
|
|
|
|
|
|
std::shared_ptr<Connection>
|
|
FlowScene::
|
|
restoreConnection(QJsonObject const &connectionJson)
|
|
{
|
|
QUuid nodeInId = QUuid(connectionJson["in_id"].toString());
|
|
QUuid nodeOutId = QUuid(connectionJson["out_id"].toString());
|
|
|
|
PortIndex portIndexIn = connectionJson["in_index"].toInt();
|
|
PortIndex portIndexOut = connectionJson["out_index"].toInt();
|
|
|
|
auto nodeIn = _nodes[nodeInId].get();
|
|
auto nodeOut = _nodes[nodeOutId].get();
|
|
|
|
auto getConverter = [&]()
|
|
{
|
|
QJsonValue converterVal = connectionJson["converter"];
|
|
|
|
if (!converterVal.isUndefined())
|
|
{
|
|
QJsonObject converterJson = converterVal.toObject();
|
|
|
|
NodeDataType inType { converterJson["in"].toObject()["id"].toString(),
|
|
converterJson["in"].toObject()["name"].toString() };
|
|
|
|
NodeDataType outType { converterJson["out"].toObject()["id"].toString(),
|
|
converterJson["out"].toObject()["name"].toString() };
|
|
|
|
auto converter =
|
|
registry().getTypeConverter(outType, inType);
|
|
|
|
if (converter)
|
|
return converter;
|
|
}
|
|
|
|
return TypeConverter{};
|
|
};
|
|
|
|
std::shared_ptr<Connection> connection =
|
|
createConnection(*nodeIn, portIndexIn,
|
|
*nodeOut, portIndexOut,
|
|
getConverter());
|
|
|
|
return connection;
|
|
}
|
|
|
|
|
|
void
|
|
FlowScene::
|
|
deleteConnection(Connection& connection)
|
|
{
|
|
connectionDeleted(connection);
|
|
connection.removeFromNodes();
|
|
_connections.erase(connection.id());
|
|
}
|
|
|
|
|
|
Node&
|
|
FlowScene::
|
|
createNode(std::unique_ptr<NodeDataModel> && dataModel)
|
|
{
|
|
auto node = detail::make_unique<Node>(std::move(dataModel));
|
|
auto ngo = detail::make_unique<NodeGraphicsObject>(*this, *node);
|
|
|
|
node->setGraphicsObject(std::move(ngo));
|
|
|
|
auto nodePtr = node.get();
|
|
_nodes[node->id()] = std::move(node);
|
|
|
|
nodeCreated(*nodePtr);
|
|
return *nodePtr;
|
|
}
|
|
|
|
|
|
Node&
|
|
FlowScene::
|
|
restoreNode(QJsonObject const& nodeJson)
|
|
{
|
|
QString modelName = nodeJson["model"].toObject()["name"].toString();
|
|
|
|
auto dataModel = registry().create(modelName);
|
|
|
|
if (!dataModel)
|
|
throw std::logic_error(std::string("No registered model with name ") +
|
|
modelName.toLocal8Bit().data());
|
|
|
|
auto node = detail::make_unique<Node>(std::move(dataModel));
|
|
auto ngo = detail::make_unique<NodeGraphicsObject>(*this, *node);
|
|
node->setGraphicsObject(std::move(ngo));
|
|
|
|
node->restore(nodeJson);
|
|
|
|
auto nodePtr = node.get();
|
|
_nodes[node->id()] = std::move(node);
|
|
|
|
nodeCreated(*nodePtr);
|
|
return *nodePtr;
|
|
}
|
|
|
|
|
|
void
|
|
FlowScene::
|
|
removeNode(Node& node)
|
|
{
|
|
// call signal
|
|
nodeDeleted(node);
|
|
|
|
for(auto portType: {PortType::In,PortType::Out})
|
|
{
|
|
auto nodeState = node.nodeState();
|
|
auto const & nodeEntries = nodeState.getEntries(portType);
|
|
|
|
for (auto &connections : nodeEntries)
|
|
{
|
|
for (auto const &pair : connections)
|
|
deleteConnection(*pair.second);
|
|
}
|
|
}
|
|
|
|
_nodes.erase(node.id());
|
|
}
|
|
|
|
|
|
DataModelRegistry&
|
|
FlowScene::
|
|
registry() const
|
|
{
|
|
return *_registry;
|
|
}
|
|
|
|
|
|
void
|
|
FlowScene::
|
|
setRegistry(std::shared_ptr<DataModelRegistry> registry)
|
|
{
|
|
_registry = std::move(registry);
|
|
}
|
|
|
|
|
|
void
|
|
FlowScene::
|
|
iterateOverNodes(std::function<void(Node*)> const & visitor)
|
|
{
|
|
for (const auto& _node : _nodes)
|
|
{
|
|
visitor(_node.second.get());
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
FlowScene::
|
|
iterateOverNodeData(std::function<void(NodeDataModel*)> const & visitor)
|
|
{
|
|
for (const auto& _node : _nodes)
|
|
{
|
|
visitor(_node.second->nodeDataModel());
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
FlowScene::
|
|
iterateOverNodeDataDependentOrder(std::function<void(NodeDataModel*)> const & visitor)
|
|
{
|
|
std::set<QUuid> visitedNodesSet;
|
|
|
|
//A leaf node is a node with no input ports, or all possible input ports empty
|
|
auto isNodeLeaf =
|
|
[](Node const &node, NodeDataModel const &model)
|
|
{
|
|
for (unsigned int i = 0; i < model.nPorts(PortType::In); ++i)
|
|
{
|
|
auto connections = node.nodeState().connections(PortType::In, i);
|
|
if (!connections.empty())
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
//Iterate over "leaf" nodes
|
|
for (auto const &_node : _nodes)
|
|
{
|
|
auto const &node = _node.second;
|
|
auto model = node->nodeDataModel();
|
|
|
|
if (isNodeLeaf(*node, *model))
|
|
{
|
|
visitor(model);
|
|
visitedNodesSet.insert(node->id());
|
|
}
|
|
}
|
|
|
|
auto areNodeInputsVisitedBefore =
|
|
[&](Node const &node, NodeDataModel const &model)
|
|
{
|
|
for (size_t i = 0; i < model.nPorts(PortType::In); ++i)
|
|
{
|
|
auto connections = node.nodeState().connections(PortType::In, i);
|
|
|
|
for (auto& conn : connections)
|
|
{
|
|
if (visitedNodesSet.find(conn.second->getNode(PortType::Out)->id()) == visitedNodesSet.end())
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
//Iterate over dependent nodes
|
|
while (_nodes.size() != visitedNodesSet.size())
|
|
{
|
|
for (auto const &_node : _nodes)
|
|
{
|
|
auto const &node = _node.second;
|
|
if (visitedNodesSet.find(node->id()) != visitedNodesSet.end())
|
|
continue;
|
|
|
|
auto model = node->nodeDataModel();
|
|
|
|
if (areNodeInputsVisitedBefore(*node, *model))
|
|
{
|
|
visitor(model);
|
|
visitedNodesSet.insert(node->id());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
QPointF
|
|
FlowScene::
|
|
getNodePosition(const Node& node) const
|
|
{
|
|
return node.nodeGraphicsObject().pos();
|
|
}
|
|
|
|
|
|
void
|
|
FlowScene::
|
|
setNodePosition(Node& node, const QPointF& pos) const
|
|
{
|
|
node.nodeGraphicsObject().setPos(pos);
|
|
node.nodeGraphicsObject().moveConnections();
|
|
}
|
|
|
|
|
|
QSizeF
|
|
FlowScene::
|
|
getNodeSize(const Node& node) const
|
|
{
|
|
return QSizeF(node.nodeGeometry().width(), node.nodeGeometry().height());
|
|
}
|
|
|
|
|
|
std::unordered_map<QUuid, std::unique_ptr<Node> > const &
|
|
FlowScene::
|
|
nodes() const
|
|
{
|
|
return _nodes;
|
|
}
|
|
|
|
|
|
std::unordered_map<QUuid, std::shared_ptr<Connection> > const &
|
|
FlowScene::
|
|
connections() const
|
|
{
|
|
return _connections;
|
|
}
|
|
|
|
|
|
std::vector<Node*>
|
|
FlowScene::
|
|
selectedNodes() const
|
|
{
|
|
QList<QGraphicsItem*> graphicsItems = selectedItems();
|
|
|
|
std::vector<Node*> ret;
|
|
ret.reserve(graphicsItems.size());
|
|
|
|
for (QGraphicsItem* item : graphicsItems)
|
|
{
|
|
auto ngo = qgraphicsitem_cast<NodeGraphicsObject*>(item);
|
|
|
|
if (ngo != nullptr)
|
|
{
|
|
ret.push_back(&ngo->node());
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
void
|
|
FlowScene::
|
|
clearScene()
|
|
{
|
|
//Manual node cleanup. Simply clearing the holding datastructures doesn't work, the code crashes when
|
|
// there are both nodes and connections in the scene. (The data propagation internal logic tries to propagate
|
|
// data through already freed connections.)
|
|
std::vector<Node*> nodesToDelete;
|
|
for (auto& node : _nodes)
|
|
{
|
|
nodesToDelete.push_back(node.second.get());
|
|
}
|
|
|
|
for (auto& node : nodesToDelete)
|
|
{
|
|
removeNode(*node);
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
FlowScene::
|
|
save() const
|
|
{
|
|
QString fileName =
|
|
QFileDialog::getSaveFileName(nullptr,
|
|
tr("Open Flow Scene"),
|
|
QDir::homePath(),
|
|
tr("Flow Scene Files (*.flow)"));
|
|
|
|
if (!fileName.isEmpty())
|
|
{
|
|
if (!fileName.endsWith("flow", Qt::CaseInsensitive))
|
|
fileName += ".flow";
|
|
|
|
QFile file(fileName);
|
|
if (file.open(QIODevice::WriteOnly))
|
|
{
|
|
file.write(saveToMemory());
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
FlowScene::
|
|
load()
|
|
{
|
|
clearScene();
|
|
|
|
//-------------
|
|
|
|
QString fileName =
|
|
QFileDialog::getOpenFileName(nullptr,
|
|
tr("Open Flow Scene"),
|
|
QDir::homePath(),
|
|
tr("Flow Scene Files (*.flow)"));
|
|
|
|
if (!QFileInfo::exists(fileName))
|
|
return;
|
|
|
|
QFile file(fileName);
|
|
|
|
if (!file.open(QIODevice::ReadOnly))
|
|
return;
|
|
|
|
QByteArray wholeFile = file.readAll();
|
|
|
|
loadFromMemory(wholeFile);
|
|
}
|
|
|
|
|
|
QByteArray
|
|
FlowScene::
|
|
saveToMemory() const
|
|
{
|
|
QJsonObject sceneJson;
|
|
|
|
QJsonArray nodesJsonArray;
|
|
|
|
for (auto const & pair : _nodes)
|
|
{
|
|
auto const &node = pair.second;
|
|
|
|
nodesJsonArray.append(node->save());
|
|
}
|
|
|
|
sceneJson["nodes"] = nodesJsonArray;
|
|
|
|
QJsonArray connectionJsonArray;
|
|
for (auto const & pair : _connections)
|
|
{
|
|
auto const &connection = pair.second;
|
|
|
|
QJsonObject connectionJson = connection->save();
|
|
|
|
if (!connectionJson.isEmpty())
|
|
connectionJsonArray.append(connectionJson);
|
|
}
|
|
|
|
sceneJson["connections"] = connectionJsonArray;
|
|
|
|
QJsonDocument document(sceneJson);
|
|
|
|
return document.toJson();
|
|
}
|
|
|
|
|
|
void
|
|
FlowScene::
|
|
loadFromMemory(const QByteArray& data)
|
|
{
|
|
QJsonObject const jsonDocument = QJsonDocument::fromJson(data).object();
|
|
|
|
QJsonArray nodesJsonArray = jsonDocument["nodes"].toArray();
|
|
|
|
for (QJsonValueRef node : nodesJsonArray)
|
|
{
|
|
restoreNode(node.toObject());
|
|
}
|
|
|
|
QJsonArray connectionJsonArray = jsonDocument["connections"].toArray();
|
|
|
|
for (QJsonValueRef connection : connectionJsonArray)
|
|
{
|
|
restoreConnection(connection.toObject());
|
|
}
|
|
}
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
namespace QtNodes
|
|
{
|
|
|
|
Node*
|
|
locateNodeAt(QPointF scenePoint, FlowScene &scene,
|
|
QTransform const & viewTransform)
|
|
{
|
|
// items under cursor
|
|
QList<QGraphicsItem*> items =
|
|
scene.items(scenePoint,
|
|
Qt::IntersectsItemShape,
|
|
Qt::DescendingOrder,
|
|
viewTransform);
|
|
|
|
//// items convertable to NodeGraphicsObject
|
|
std::vector<QGraphicsItem*> filteredItems;
|
|
|
|
std::copy_if(items.begin(),
|
|
items.end(),
|
|
std::back_inserter(filteredItems),
|
|
[] (QGraphicsItem * item)
|
|
{
|
|
return (dynamic_cast<NodeGraphicsObject*>(item) != nullptr);
|
|
});
|
|
|
|
Node* resultNode = nullptr;
|
|
|
|
if (!filteredItems.empty())
|
|
{
|
|
QGraphicsItem* graphicsItem = filteredItems.front();
|
|
auto ngo = dynamic_cast<NodeGraphicsObject*>(graphicsItem);
|
|
|
|
resultNode = &ngo->node();
|
|
}
|
|
|
|
return resultNode;
|
|
}
|
|
}
|