#include "FlowScene.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #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 registry, QObject * parent) : QGraphicsScene(parent) , _registry(std::move(registry)) { setItemIndexMethod(QGraphicsScene::NoIndex); } FlowScene:: FlowScene(QObject * parent) : FlowScene(std::make_shared(), parent) {} FlowScene:: ~FlowScene() { clearScene(); } //------------------------------------------------------------------------------ std::shared_ptr FlowScene:: createConnection(PortType connectedPort, Node& node, PortIndex portIndex) { auto connection = std::make_shared(connectedPort, node, portIndex); auto cgo = detail::make_unique(*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 FlowScene:: createConnection(Node& nodeIn, PortIndex portIndexIn, Node& nodeOut, PortIndex portIndexOut, TypeConverter const &converter) { auto connection = std::make_shared(nodeIn, portIndexIn, nodeOut, portIndexOut, converter); auto cgo = detail::make_unique(*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 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 = 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 && dataModel) { auto node = detail::make_unique(std::move(dataModel)); auto ngo = detail::make_unique(*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(std::move(dataModel)); auto ngo = detail::make_unique(*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 registry) { _registry = std::move(registry); } void FlowScene:: iterateOverNodes(std::function const & visitor) { for (const auto& _node : _nodes) { visitor(_node.second.get()); } } void FlowScene:: iterateOverNodeData(std::function const & visitor) { for (const auto& _node : _nodes) { visitor(_node.second->nodeDataModel()); } } void FlowScene:: iterateOverNodeDataDependentOrder(std::function const & visitor) { std::set 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 > const & FlowScene:: nodes() const { return _nodes; } std::unordered_map > const & FlowScene:: connections() const { return _connections; } std::vector FlowScene:: selectedNodes() const { QList graphicsItems = selectedItems(); std::vector ret; ret.reserve(graphicsItems.size()); for (QGraphicsItem* item : graphicsItems) { auto ngo = qgraphicsitem_cast(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 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 items = scene.items(scenePoint, Qt::IntersectsItemShape, Qt::DescendingOrder, viewTransform); //// items convertable to NodeGraphicsObject std::vector filteredItems; std::copy_if(items.begin(), items.end(), std::back_inserter(filteredItems), [] (QGraphicsItem * item) { return (dynamic_cast(item) != nullptr); }); Node* resultNode = nullptr; if (!filteredItems.empty()) { QGraphicsItem* graphicsItem = filteredItems.front(); auto ngo = dynamic_cast(graphicsItem); resultNode = &ngo->node(); } return resultNode; } }