#include "DataFlowGraphModel.hpp" #include "ConnectionIdHash.hpp" #include #include namespace QtNodes { DataFlowGraphModel::DataFlowGraphModel(std::shared_ptr registry) : _registry(std::move(registry)) , _nextNodeId{0} {} std::unordered_set DataFlowGraphModel::allNodeIds() const { std::unordered_set nodeIds; for_each(_models.begin(), _models.end(), [&nodeIds](auto const &p) { nodeIds.insert(p.first); }); return nodeIds; } std::unordered_set DataFlowGraphModel::allConnectionIds(NodeId const nodeId) const { std::unordered_set result; std::copy_if(_connectivity.begin(), _connectivity.end(), std::inserter(result, std::end(result)), [&nodeId](ConnectionId const &cid) { return cid.inNodeId == nodeId || cid.outNodeId == nodeId; }); return result; } std::unordered_set DataFlowGraphModel::connections(NodeId nodeId, PortType portType, PortIndex portIndex) const { std::unordered_set result; std::copy_if(_connectivity.begin(), _connectivity.end(), std::inserter(result, std::end(result)), [&portType, &portIndex, &nodeId](ConnectionId const &cid) { return (getNodeId(portType, cid) == nodeId && getPortIndex(portType, cid) == portIndex); }); return result; } bool DataFlowGraphModel::connectionExists(ConnectionId const connectionId) const { return (_connectivity.find(connectionId) != _connectivity.end()); } NodeId DataFlowGraphModel::addNode(QString const nodeType) { std::unique_ptr model = _registry->create(nodeType); if (model) { NodeId newId = newNodeId(); connect(model.get(), &NodeDelegateModel::dataUpdated, [newId, this](PortIndex const portIndex) { onOutPortDataUpdated(newId, portIndex); }); connect(model.get(), &NodeDelegateModel::portsAboutToBeDeleted, this, [newId, this](PortType const portType, PortIndex const first, PortIndex const last) { portsAboutToBeDeleted(newId, portType, first, last); }); connect(model.get(), &NodeDelegateModel::portsDeleted, this, &DataFlowGraphModel::portsDeleted); connect(model.get(), &NodeDelegateModel::portsAboutToBeInserted, this, [newId, this](PortType const portType, PortIndex const first, PortIndex const last) { portsAboutToBeInserted(newId, portType, first, last); }); connect(model.get(), &NodeDelegateModel::portsInserted, this, &DataFlowGraphModel::portsInserted); _models[newId] = std::move(model); Q_EMIT nodeCreated(newId); return newId; } return InvalidNodeId; } bool DataFlowGraphModel::connectionPossible(ConnectionId const connectionId) const { auto getDataType = [&](PortType const portType) { return portData(getNodeId(portType, connectionId), portType, getPortIndex(portType, connectionId), PortRole::DataType) .value(); }; auto portVacant = [&](PortType const portType) { NodeId const nodeId = getNodeId(portType, connectionId); PortIndex const portIndex = getPortIndex(portType, connectionId); auto const connected = connections(nodeId, portType, portIndex); auto policy = portData(nodeId, portType, portIndex, PortRole::ConnectionPolicyRole) .value(); return connected.empty() || (policy == ConnectionPolicy::Many); }; return getDataType(PortType::Out).id == getDataType(PortType::In).id && portVacant(PortType::Out) && portVacant(PortType::In); } void DataFlowGraphModel::addConnection(ConnectionId const connectionId) { _connectivity.insert(connectionId); sendConnectionCreation(connectionId); QVariant const portDataToPropagate = portData(connectionId.outNodeId, PortType::Out, connectionId.outPortIndex, PortRole::Data); setPortData(connectionId.inNodeId, PortType::In, connectionId.inPortIndex, portDataToPropagate, PortRole::Data); } void DataFlowGraphModel::sendConnectionCreation(ConnectionId const connectionId) { Q_EMIT connectionCreated(connectionId); auto iti = _models.find(connectionId.inNodeId); auto ito = _models.find(connectionId.outNodeId); if (iti != _models.end() && ito != _models.end()) { auto &modeli = iti->second; auto &modelo = ito->second; modeli->inputConnectionCreated(connectionId); modelo->outputConnectionCreated(connectionId); } } void DataFlowGraphModel::sendConnectionDeletion(ConnectionId const connectionId) { Q_EMIT connectionDeleted(connectionId); auto iti = _models.find(connectionId.inNodeId); auto ito = _models.find(connectionId.outNodeId); if (iti != _models.end() && ito != _models.end()) { auto &modeli = iti->second; auto &modelo = ito->second; modeli->inputConnectionDeleted(connectionId); modelo->outputConnectionDeleted(connectionId); } } bool DataFlowGraphModel::nodeExists(NodeId const nodeId) const { return (_models.find(nodeId) != _models.end()); } QVariant DataFlowGraphModel::nodeData(NodeId nodeId, NodeRole role) const { QVariant result; auto it = _models.find(nodeId); if (it == _models.end()) return result; auto &model = it->second; switch (role) { case NodeRole::Id: break; case NodeRole::Type: result = model->name(); break; case NodeRole::Position: result = _nodeGeometryData[nodeId].pos; break; case NodeRole::Size: result = _nodeGeometryData[nodeId].size; break; case NodeRole::CaptionVisible: result = model->captionVisible(); break; case NodeRole::Caption: result = model->caption(); break; case NodeRole::Style: { auto style = StyleCollection::nodeStyle(); result = style.toJson().toVariantMap(); } break; case NodeRole::InternalData: { QJsonObject nodeJson; nodeJson["internal-data"] = _models.at(nodeId)->save(); result = nodeJson.toVariantMap(); break; } case NodeRole::InPortCount: result = model->nPorts(PortType::In); break; case NodeRole::OutPortCount: result = model->nPorts(PortType::Out); break; case NodeRole::Widget: { auto w = model->embeddedWidget(); result = QVariant::fromValue(w); } break; } return result; } NodeFlags DataFlowGraphModel::nodeFlags(NodeId nodeId) const { auto it = _models.find(nodeId); if (it != _models.end() && it->second->resizable()) return NodeFlag::Resizable; return NodeFlag::NoFlags; } bool DataFlowGraphModel::setNodeData(NodeId nodeId, NodeRole role, QVariant value) { Q_UNUSED(nodeId); Q_UNUSED(role); Q_UNUSED(value); bool result = false; switch (role) { case NodeRole::Id: break; case NodeRole::Type: break; case NodeRole::Position: { _nodeGeometryData[nodeId].pos = value.value(); Q_EMIT nodePositionUpdated(nodeId); result = true; } break; case NodeRole::Size: { _nodeGeometryData[nodeId].size = value.value(); result = true; } break; case NodeRole::CaptionVisible: break; case NodeRole::Caption: break; case NodeRole::Style: break; case NodeRole::InternalData: break; case NodeRole::InPortCount: break; case NodeRole::OutPortCount: break; case NodeRole::Widget: break; } return result; } QVariant DataFlowGraphModel::portData(NodeId nodeId, PortType portType, PortIndex portIndex, PortRole role) const { QVariant result; auto it = _models.find(nodeId); if (it == _models.end()) return result; auto &model = it->second; switch (role) { case PortRole::Data: if (portType == PortType::Out) result = QVariant::fromValue(model->outData(portIndex)); break; case PortRole::DataType: result = QVariant::fromValue(model->dataType(portType, portIndex)); break; case PortRole::ConnectionPolicyRole: result = QVariant::fromValue(model->portConnectionPolicy(portType, portIndex)); break; case PortRole::CaptionVisible: result = model->portCaptionVisible(portType, portIndex); break; case PortRole::Caption: result = model->portCaption(portType, portIndex); break; } return result; } bool DataFlowGraphModel::setPortData( NodeId nodeId, PortType portType, PortIndex portIndex, QVariant const &value, PortRole role) { Q_UNUSED(nodeId); QVariant result; auto it = _models.find(nodeId); if (it == _models.end()) return false; auto &model = it->second; switch (role) { case PortRole::Data: if (portType == PortType::In) { model->setInData(value.value>(), portIndex); // Triggers repainting on the scene. Q_EMIT inPortDataWasSet(nodeId, portType, portIndex); } break; default: break; } return false; } bool DataFlowGraphModel::deleteConnection(ConnectionId const connectionId) { bool disconnected = false; auto it = _connectivity.find(connectionId); if (it != _connectivity.end()) { disconnected = true; _connectivity.erase(it); } if (disconnected) { sendConnectionDeletion(connectionId); propagateEmptyDataTo(getNodeId(PortType::In, connectionId), getPortIndex(PortType::In, connectionId)); } return disconnected; } bool DataFlowGraphModel::deleteNode(NodeId const nodeId) { // Delete connections to this node first. auto connectionIds = allConnectionIds(nodeId); for (auto &cId : connectionIds) { deleteConnection(cId); } _nodeGeometryData.erase(nodeId); _models.erase(nodeId); Q_EMIT nodeDeleted(nodeId); return true; } QJsonObject DataFlowGraphModel::saveNode(NodeId const nodeId) const { QJsonObject nodeJson; nodeJson["id"] = static_cast(nodeId); nodeJson["internal-data"] = _models.at(nodeId)->save(); { QPointF const pos = nodeData(nodeId, NodeRole::Position).value(); QJsonObject posJson; posJson["x"] = pos.x(); posJson["y"] = pos.y(); nodeJson["position"] = posJson; } return nodeJson; } QJsonObject DataFlowGraphModel::save() const { QJsonObject sceneJson; QJsonArray nodesJsonArray; for (auto const nodeId : allNodeIds()) { nodesJsonArray.append(saveNode(nodeId)); } sceneJson["nodes"] = nodesJsonArray; QJsonArray connJsonArray; for (auto const &cid : _connectivity) { connJsonArray.append(toJson(cid)); } sceneJson["connections"] = connJsonArray; return sceneJson; } void DataFlowGraphModel::loadNode(QJsonObject const &nodeJson) { // Possibility of the id clash when reading it from json and not generating a // new value. // 1. When restoring a scene from a file. // Conflict is not possible because the scene must be cleared by the time of // loading. // 2. When undoing the deletion command. Conflict is not possible // because all the new ids were created past the removed nodes. NodeId restoredNodeId = nodeJson["id"].toInt(); _nextNodeId = std::max(_nextNodeId, restoredNodeId + 1); QJsonObject const internalDataJson = nodeJson["internal-data"].toObject(); QString delegateModelName = internalDataJson["model-name"].toString(); std::unique_ptr model = _registry->create(delegateModelName); if (model) { connect(model.get(), &NodeDelegateModel::dataUpdated, [restoredNodeId, this](PortIndex const portIndex) { onOutPortDataUpdated(restoredNodeId, portIndex); }); _models[restoredNodeId] = std::move(model); Q_EMIT nodeCreated(restoredNodeId); QJsonObject posJson = nodeJson["position"].toObject(); QPointF const pos(posJson["x"].toDouble(), posJson["y"].toDouble()); setNodeData(restoredNodeId, NodeRole::Position, pos); _models[restoredNodeId]->load(internalDataJson); } else { throw std::logic_error(std::string("No registered model with name ") + delegateModelName.toLocal8Bit().data()); } } void DataFlowGraphModel::load(QJsonObject const &jsonDocument) { QJsonArray nodesJsonArray = jsonDocument["nodes"].toArray(); for (QJsonValueRef nodeJson : nodesJsonArray) { loadNode(nodeJson.toObject()); } QJsonArray connectionJsonArray = jsonDocument["connections"].toArray(); for (QJsonValueRef connection : connectionJsonArray) { QJsonObject connJson = connection.toObject(); ConnectionId connId = fromJson(connJson); // Restore the connection addConnection(connId); } } void DataFlowGraphModel::onOutPortDataUpdated(NodeId const nodeId, PortIndex const portIndex) { std::unordered_set const &connected = connections(nodeId, PortType::Out, portIndex); QVariant const portDataToPropagate = portData(nodeId, PortType::Out, portIndex, PortRole::Data); for (auto const &cn : connected) { setPortData(cn.inNodeId, PortType::In, cn.inPortIndex, portDataToPropagate, PortRole::Data); } } void DataFlowGraphModel::propagateEmptyDataTo(NodeId const nodeId, PortIndex const portIndex) { QVariant emptyData{}; setPortData(nodeId, PortType::In, portIndex, emptyData, PortRole::Data); } } // namespace QtNodes