#include "BasicGraphicsScene.hpp"
#include "AbstractNodeGeometry.hpp"
#include "ConnectionGraphicsObject.hpp"
#include "ConnectionIdUtils.hpp"
#include "DataFlowGraphModel.hpp"
#include "DefaultConnectionPainter.hpp"
#include "DefaultHorizontalNodeGeometry.hpp"
#include "DefaultNodePainter.hpp"
#include "DefaultVerticalNodeGeometry.hpp"
#include "GraphicsView.hpp"
#include "NodeDelegateModel.hpp"
#include "NodeGraphicsObject.hpp"
#include <QUndoStack>
#include <QHeaderView>
#include <QLineEdit>
#include <QTreeWidget>
#include <QWidgetAction>
#include <QtWidgets/QFileDialog>
#include <QtWidgets/QGraphicsSceneMoveEvent>
#include <QtCore/QBuffer>
#include <QtCore/QByteArray>
#include <QtCore/QDataStream>
#include <QtCore/QFile>
#include <QtCore/QIODevice>
#include <QtCore/QJsonArray>
#include <QtCore/QJsonDocument>
#include <QtCore/QJsonObject>
#include <QtCore/QJsonValue>
#include <QtCore/QString>
#include <QtCore/QtGlobal>
#include <iostream>
#include <stdexcept>
#include <unordered_set>
#include <utility>
#include <queue>
namespace {
using QtNodes::GroupId;
using QtNodes::InvalidGroupId;
using QtNodes::InvalidNodeId;
using QtNodes::NodeId;
NodeId jsonValueToNodeId(QJsonValue const &value)
{
if (value.isDouble()) {
return static_cast<NodeId>(value.toInt());
}
if (value.isString()) {
auto const textValue = value.toString();
bool ok = false;
auto const numericValue = textValue.toULongLong(&ok, 10);
if (ok) {
return static_cast<NodeId>(numericValue);
}
QUuid uuidValue(textValue);
if (!uuidValue.isNull()) {
auto const bytes = uuidValue.toRfc4122();
if (bytes.size() >= static_cast<int>(sizeof(quint32))) {
QDataStream stream(bytes);
quint32 value32 = 0U;
stream >> value32;
return static_cast<NodeId>(value32);
}
}
}
return InvalidNodeId;
}
}
namespace QtNodes {
BasicGraphicsScene::BasicGraphicsScene(AbstractGraphModel &graphModel, QObject *parent)
: QGraphicsScene(parent)
, _graphModel(graphModel)
, _nodeGeometry(std::make_unique<DefaultHorizontalNodeGeometry>(_graphModel))
, _nodePainter(std::make_unique<DefaultNodePainter>())
, _connectionPainter(std::make_unique<DefaultConnectionPainter>())
, _nodeDrag(false)
, _undoStack(new QUndoStack(this))
, _orientation(Qt::Horizontal)
, _groupingEnabled(true)
{
setItemIndexMethod(QGraphicsScene::NoIndex);
connect(&_graphModel,
&AbstractGraphModel::connectionCreated,
this,
&BasicGraphicsScene::onConnectionCreated);
connect(&_graphModel,
&AbstractGraphModel::connectionDeleted,
this,
&BasicGraphicsScene::onConnectionDeleted);
connect(&_graphModel,
&AbstractGraphModel::nodeCreated,
this,
&BasicGraphicsScene::onNodeCreated);
connect(&_graphModel,
&AbstractGraphModel::nodeDeleted,
this,
&BasicGraphicsScene::onNodeDeleted);
connect(&_graphModel,
&AbstractGraphModel::nodePositionUpdated,
this,
&BasicGraphicsScene::onNodePositionUpdated);
connect(&_graphModel,
&AbstractGraphModel::nodeUpdated,
this,
&BasicGraphicsScene::onNodeUpdated);
connect(this, &BasicGraphicsScene::nodeClicked, this, &BasicGraphicsScene::onNodeClicked);
connect(&_graphModel, &AbstractGraphModel::modelReset, this, &BasicGraphicsScene::onModelReset);
traverseGraphAndPopulateGraphicsObjects();
}
BasicGraphicsScene::~BasicGraphicsScene() = default;
AbstractGraphModel const &BasicGraphicsScene::graphModel() const
{
return _graphModel;
}
AbstractGraphModel &BasicGraphicsScene::graphModel()
{
return _graphModel;
}
AbstractNodeGeometry const &BasicGraphicsScene::nodeGeometry() const
{
return *_nodeGeometry;
}
AbstractNodeGeometry &BasicGraphicsScene::nodeGeometry()
{
return *_nodeGeometry;
}
AbstractNodePainter &BasicGraphicsScene::nodePainter()
{
return *_nodePainter;
}
AbstractConnectionPainter &BasicGraphicsScene::connectionPainter()
{
return *_connectionPainter;
}
void BasicGraphicsScene::setNodePainter(std::unique_ptr<AbstractNodePainter> newPainter)
{
_nodePainter = std::move(newPainter);
}
void BasicGraphicsScene::setConnectionPainter(std::unique_ptr<AbstractConnectionPainter> newPainter)
{
_connectionPainter = std::move(newPainter);
}
void BasicGraphicsScene::setNodeGeometry(std::unique_ptr<AbstractNodeGeometry> newGeom)
{
_nodeGeometry = std::move(newGeom);
}
QUndoStack &BasicGraphicsScene::undoStack()
{
return *_undoStack;
}
void BasicGraphicsScene::setGroupingEnabled(bool enabled)
{
if (_groupingEnabled == enabled)
return;
if (!enabled) {
for (auto &groupEntry : _groups) {
auto &group = groupEntry.second;
if (!group)
continue;
for (auto *node : group->childNodes()) {
if (!node)
continue;
node->unsetNodeGroup();
node->lock(false);
}
}
_groups.clear();
_nextGroupId = 0;
}
_groupingEnabled = enabled;
}
std::unique_ptr<ConnectionGraphicsObject> const &BasicGraphicsScene::makeDraftConnection(
ConnectionId const incompleteConnectionId)
{
_draftConnection = std::make_unique<ConnectionGraphicsObject>(*this, incompleteConnectionId);
_draftConnection->grabMouse();
return _draftConnection;
}
void BasicGraphicsScene::resetDraftConnection()
{
_draftConnection.reset();
}
void BasicGraphicsScene::clearScene()
{
auto const &allNodeIds = graphModel().allNodeIds();
for (auto nodeId : allNodeIds) {
graphModel().deleteNode(nodeId);
}
}
std::vector<std::shared_ptr<ConnectionId>> BasicGraphicsScene::connectionsWithinGroup(GroupId groupID)
{
if (!_groupingEnabled)
return {};
std::vector<std::shared_ptr<ConnectionId>> ret{};
for (auto const &connection : _connectionGraphicsObjects) {
auto outNode = nodeGraphicsObject(connection.first.outNodeId);
auto inNode = nodeGraphicsObject(connection.first.inNodeId);
if (outNode && inNode) {
auto group1 = outNode->nodeGroup().lock();
auto group2 = inNode->nodeGroup().lock();
if (group1 && group2 && group1->id() == group2->id() && group1->id() == groupID) {
ret.push_back(std::make_shared<ConnectionId>(connection.first));
}
}
}
return ret;
}
NodeGraphicsObject *BasicGraphicsScene::nodeGraphicsObject(NodeId nodeId)
{
NodeGraphicsObject *ngo = nullptr;
auto it = _nodeGraphicsObjects.find(nodeId);
if (it != _nodeGraphicsObjects.end()) {
ngo = it->second.get();
}
return ngo;
}
ConnectionGraphicsObject *BasicGraphicsScene::connectionGraphicsObject(ConnectionId connectionId)
{
ConnectionGraphicsObject *cgo = nullptr;
auto it = _connectionGraphicsObjects.find(connectionId);
if (it != _connectionGraphicsObjects.end()) {
cgo = it->second.get();
}
return cgo;
}
void BasicGraphicsScene::setOrientation(Qt::Orientation const orientation)
{
if (_orientation != orientation) {
_orientation = orientation;
switch (_orientation) {
case Qt::Horizontal:
_nodeGeometry = std::make_unique<DefaultHorizontalNodeGeometry>(_graphModel);
break;
case Qt::Vertical:
_nodeGeometry = std::make_unique<DefaultVerticalNodeGeometry>(_graphModel);
break;
}
onModelReset();
}
}
QMenu *BasicGraphicsScene::createSceneMenu(QPointF const scenePos)
{
Q_UNUSED(scenePos);
return nullptr;
}
void BasicGraphicsScene::traverseGraphAndPopulateGraphicsObjects()
{
auto allNodeIds = _graphModel.allNodeIds();
for (NodeId const nodeId : allNodeIds) {
_nodeGraphicsObjects[nodeId] = std::make_unique<NodeGraphicsObject>(*this, nodeId);
}
for (NodeId const nodeId : allNodeIds) {
auto nOutPorts = _graphModel.nodeData<PortCount>(nodeId, NodeRole::OutPortCount);
for (PortIndex index = 0; index < nOutPorts; ++index) {
auto const &outConnectionIds = _graphModel.connections(nodeId, PortType::Out, index);
for (auto cid : outConnectionIds) {
_connectionGraphicsObjects[cid] = std::make_unique<ConnectionGraphicsObject>(*this,
cid);
}
}
}
}
void BasicGraphicsScene::updateAttachedNodes(ConnectionId const connectionId,
PortType const portType)
{
auto node = nodeGraphicsObject(getNodeId(portType, connectionId));
if (node) {
node->update();
}
}
void BasicGraphicsScene::onConnectionDeleted(ConnectionId const connectionId)
{
auto it = _connectionGraphicsObjects.find(connectionId);
if (it != _connectionGraphicsObjects.end()) {
_connectionGraphicsObjects.erase(it);
}
if (_draftConnection && _draftConnection->connectionId() == connectionId) {
_draftConnection.reset();
}
updateAttachedNodes(connectionId, PortType::Out);
updateAttachedNodes(connectionId, PortType::In);
Q_EMIT modified(this);
}
void BasicGraphicsScene::onConnectionCreated(ConnectionId const connectionId)
{
_connectionGraphicsObjects[connectionId]
= std::make_unique<ConnectionGraphicsObject>(*this, connectionId);
updateAttachedNodes(connectionId, PortType::Out);
updateAttachedNodes(connectionId, PortType::In);
Q_EMIT modified(this);
}
void BasicGraphicsScene::onNodeDeleted(NodeId const nodeId)
{
auto it = _nodeGraphicsObjects.find(nodeId);
if (it != _nodeGraphicsObjects.end()) {
removeNodeFromGroup(nodeId);
_nodeGraphicsObjects.erase(it);
Q_EMIT modified(this);
}
}
void BasicGraphicsScene::onNodeCreated(NodeId const nodeId)
{
_nodeGraphicsObjects[nodeId] = std::make_unique<NodeGraphicsObject>(*this, nodeId);
Q_EMIT modified(this);
}
void BasicGraphicsScene::onNodePositionUpdated(NodeId const nodeId)
{
auto node = nodeGraphicsObject(nodeId);
if (node) {
node->setPos(_graphModel.nodeData(nodeId, NodeRole::Position).value<QPointF>());
node->update();
_nodeDrag = true;
}
}
void BasicGraphicsScene::onNodeUpdated(NodeId const nodeId)
{
auto node = nodeGraphicsObject(nodeId);
if (node) {
node->setGeometryChanged();
_nodeGeometry->recomputeSize(nodeId);
node->updateQWidgetEmbedPos();
node->update();
node->moveConnections();
}
}
void BasicGraphicsScene::onNodeClicked(NodeId const nodeId)
{
if (_nodeDrag) {
Q_EMIT nodeMoved(nodeId, _graphModel.nodeData(nodeId, NodeRole::Position).value<QPointF>());
Q_EMIT modified(this);
}
_nodeDrag = false;
}
void BasicGraphicsScene::onModelReset()
{
_connectionGraphicsObjects.clear();
_nodeGraphicsObjects.clear();
clear();
traverseGraphAndPopulateGraphicsObjects();
}
void BasicGraphicsScene::freezeModelAndConnections(bool isFreeze)
{
for (QGraphicsItem *item : selectedItems()) {
if (auto n = qgraphicsitem_cast<NodeGraphicsObject *>(item)) {
int portCount = graphModel().nodeData(n->nodeId(), NodeRole::OutPortCount).toInt();
for (int i = 0; i < portCount; i++) {
auto graphConnections = graphModel().connections(n->nodeId(),
QtNodes::PortType::Out,
QtNodes::PortIndex(i));
for (auto const &c : graphConnections) {
if (auto *cgo = connectionGraphicsObject(c)) {
cgo->connectionState().setFrozen(isFreeze);
cgo->update();
}
}
}
if (auto *dfModel = dynamic_cast<DataFlowGraphModel *>(&graphModel())) {
if (auto *delegate = dfModel->delegateModel<NodeDelegateModel>(n->nodeId())) {
delegate->setFrozenState(isFreeze);
}
}
}
}
}
std::weak_ptr<NodeGroup> BasicGraphicsScene::createGroup(std::vector<NodeGraphicsObject *> &nodes,
QString groupName,
GroupId groupId)
{
if (!_groupingEnabled)
return std::weak_ptr<NodeGroup>();
if (nodes.empty())
return std::weak_ptr<NodeGroup>();
for (auto *node : nodes) {
if (!node->nodeGroup().expired())
removeNodeFromGroup(node->nodeId());
}
if (groupName.isEmpty()) {
groupName = "Group " + QString::number(NodeGroup::groupCount());
}
if (groupId == InvalidGroupId) {
groupId = nextGroupId();
} else {
if (_groups.count(groupId) != 0) {
throw std::runtime_error("Group identifier collision");
}
if (groupId >= _nextGroupId && _nextGroupId != InvalidGroupId) {
_nextGroupId = groupId + 1;
}
}
auto group = std::make_shared<NodeGroup>(nodes, groupId, groupName, this);
auto ggo = std::make_unique<GroupGraphicsObject>(*this, *group);
group->setGraphicsObject(std::move(ggo));
for (auto &nodePtr : nodes) {
auto node = _nodeGraphicsObjects[nodePtr->nodeId()].get();
node->setNodeGroup(group);
}
std::weak_ptr<NodeGroup> groupWeakPtr = group;
_groups[group->id()] = std::move(group);
return groupWeakPtr;
}
std::vector<NodeGraphicsObject *> BasicGraphicsScene::selectedNodes() const
{
QList<QGraphicsItem *> graphicsItems = selectedItems();
std::vector<NodeGraphicsObject *> result;
result.reserve(graphicsItems.size());
for (QGraphicsItem *item : graphicsItems) {
auto ngo = qgraphicsitem_cast<NodeGraphicsObject *>(item);
if (ngo) {
result.push_back(ngo);
}
}
return result;
}
std::vector<GroupGraphicsObject *> BasicGraphicsScene::selectedGroups() const
{
if (!_groupingEnabled)
return {};
QList<QGraphicsItem *> graphicsItems = selectedItems();
std::vector<GroupGraphicsObject *> result;
result.reserve(graphicsItems.size());
for (QGraphicsItem *item : graphicsItems) {
auto ngo = qgraphicsitem_cast<GroupGraphicsObject *>(item);
if (ngo) {
result.push_back(ngo);
}
}
return result;
}
void BasicGraphicsScene::addNodeToGroup(NodeId nodeId, GroupId groupId)
{
if (!_groupingEnabled)
return;
auto groupIt = _groups.find(groupId);
auto nodeIt = _nodeGraphicsObjects.find(nodeId);
if (groupIt == _groups.end() || nodeIt == _nodeGraphicsObjects.end())
return;
auto group = groupIt->second;
auto node = nodeIt->second.get();
group->addNode(node);
node->setNodeGroup(group);
}
void BasicGraphicsScene::removeNodeFromGroup(NodeId nodeId)
{
if (!_groupingEnabled)
return;
auto nodeIt = _nodeGraphicsObjects.find(nodeId);
if (nodeIt == _nodeGraphicsObjects.end())
return;
auto group = nodeIt->second->nodeGroup().lock();
if (group) {
group->removeNode(nodeIt->second.get());
if (group->empty()) {
_groups.erase(group->id());
}
}
nodeIt->second->unsetNodeGroup();
nodeIt->second->lock(false);
}
std::unordered_map<QUuid, QUuid> BasicGraphicsScene::loadItems(const QByteArray &data,
QPointF pastePos,
bool usePastePos)
{
QJsonObject const jsonDocument = QJsonDocument::fromJson(data).object();
std::unordered_map<QUuid, QUuid> IDMap{};
QPointF offset;
bool offsetInitialized{false};
clearSelection();
QJsonArray nodesJsonArray = jsonDocument.value("nodes").toArray();
QHash<NodeId, QJsonObject> nodeById;
nodeById.reserve(nodesJsonArray.size());
for (const QJsonValue &v : nodesJsonArray) {
QJsonObject o = v.toObject();
NodeId id = static_cast<NodeId>(o.value("id").toInt(InvalidNodeId));
if (id != InvalidNodeId)
nodeById.insert(id, o);
}
QSet<NodeId> createdOldNodeIds;
QJsonArray groupsJsonArray = jsonDocument.value("groups").toArray();
for (const QJsonValue &groupVal : groupsJsonArray) {
auto [groupWeakPtr, groupIDsMap] = restoreGroup(groupVal.toObject(), nodeById);
for (const auto &[oldGroupId, newGroupId] : groupIDsMap) {
NodeId oldNodeId = static_cast<NodeId>(oldGroupId);
NodeId newNodeId = static_cast<NodeId>(newGroupId);
createdOldNodeIds.insert(oldNodeId);
QUuid oldUuid = encodeNodeId(oldNodeId);
QUuid newUuid = encodeNodeId(newNodeId);
IDMap[oldUuid] = newUuid;
}
if (auto groupPtr = groupWeakPtr.lock(); groupPtr) {
auto &ggoRef = groupPtr->groupGraphicsObject();
if (usePastePos && !offsetInitialized) {
offset = pastePos - ggoRef.pos();
offsetInitialized = true;
}
if (usePastePos) {
ggoRef.moveNodes(offset);
}
ggoRef.moveConnections();
ggoRef.setSelected(true);
}
}
for (QJsonValueRef node : nodesJsonArray) {
QJsonObject nodeObj = node.toObject();
NodeId oldNodeId = static_cast<NodeId>(nodeObj.value("id").toInt(InvalidNodeId));
if (createdOldNodeIds.contains(oldNodeId)) {
continue;
}
auto &nodeRef = loadNodeToMap(nodeObj, false);
NodeId newNodeId = nodeRef.nodeId();
QUuid oldId = encodeNodeId(oldNodeId);
QUuid newId = encodeNodeId(newNodeId);
IDMap.insert(std::make_pair(oldId, newId));
if (usePastePos && !offsetInitialized) {
offset = pastePos - nodeRef.pos();
offsetInitialized = true;
}
if (usePastePos) {
nodeRef.moveBy(offset.x(), offset.y());
}
nodeRef.moveConnections();
nodeRef.setSelected(true);
}
QJsonArray connectionJsonArray = jsonDocument.value("connections").toArray();
for (QJsonValueRef connection : connectionJsonArray) {
auto nodeIdMap = convertMap(IDMap);
loadConnectionToMap(connection.toObject(), nodeIdMap);
ConnectionId connId = fromJson(connection.toObject());
auto it = _connectionGraphicsObjects.find(connId);
if (it != _connectionGraphicsObjects.end()) {
UniqueConnectionGraphicsObject &obj = it->second;
obj->setSelected(true);
}
}
return IDMap;
}
std::unordered_map<QUuid, QUuid> BasicGraphicsScene::loadFromMemory(const QByteArray &data)
{
std::unordered_map<QUuid, QUuid> map = loadItems(data, QPointF(), false);
clearSelection();
return map;
}
QUuid BasicGraphicsScene::encodeNodeId(NodeId nodeId)
{
QByteArray bytes(16, 0);
QDataStream stream(&bytes, QIODevice::WriteOnly);
stream << static_cast<quint32>(nodeId);
return QUuid::fromRfc4122(bytes);
}
NodeId BasicGraphicsScene::decodeNodeUuid(QUuid const &uuid)
{
auto bytes = uuid.toRfc4122();
if (bytes.size() < static_cast<int>(sizeof(quint32)))
return QtNodes::InvalidNodeId;
QDataStream stream(bytes);
quint32 value = 0;
stream >> value;
return static_cast<NodeId>(value);
}
std::unordered_map<NodeId, NodeId> BasicGraphicsScene::convertMap(
std::unordered_map<QUuid, QUuid> const &uuidMap)
{
std::unordered_map<NodeId, NodeId> idMap;
for (const auto &pair : uuidMap) {
NodeId keyNodeId = decodeNodeUuid(pair.first);
NodeId valueNodeId = decodeNodeUuid(pair.second);
if (keyNodeId != QtNodes::InvalidNodeId && valueNodeId != QtNodes::InvalidNodeId) {
idMap[keyNodeId] = valueNodeId;
}
}
return idMap;
}
std::weak_ptr<QtNodes::NodeGroup> BasicGraphicsScene::createGroupFromSelection(QString groupName)
{
if (!_groupingEnabled)
return std::weak_ptr<NodeGroup>();
auto nodes = selectedNodes();
return createGroup(nodes, groupName);
}
NodeGraphicsObject &BasicGraphicsScene::loadNodeToMap(QJsonObject nodeJson, bool keepOriginalId)
{
NodeId newNodeId = InvalidNodeId;
if (keepOriginalId) {
newNodeId = jsonValueToNodeId(nodeJson["id"]);
}
if (newNodeId == InvalidNodeId) {
newNodeId = _graphModel.newNodeId();
nodeJson["id"] = static_cast<qint64>(newNodeId);
}
_graphModel.loadNode(nodeJson);
auto *nodeObject = nodeGraphicsObject(newNodeId);
if (!nodeObject) {
auto graphicsObject = std::make_unique<NodeGraphicsObject>(*this, newNodeId);
nodeObject = graphicsObject.get();
_nodeGraphicsObjects[newNodeId] = std::move(graphicsObject);
}
return *nodeObject;
}
void BasicGraphicsScene::loadConnectionToMap(QJsonObject const &connectionJson,
std::unordered_map<NodeId, NodeId> const &nodeIdMap)
{
ConnectionId connId = fromJson(connectionJson);
auto const outIt = nodeIdMap.find(connId.outNodeId);
auto const inIt = nodeIdMap.find(connId.inNodeId);
if (outIt == nodeIdMap.end() || inIt == nodeIdMap.end()) {
return;
}
ConnectionId remapped{outIt->second, connId.outPortIndex, inIt->second, connId.inPortIndex};
if (_graphModel.connectionExists(remapped)) {
return;
}
if (_graphModel.connectionPossible(remapped)) {
_graphModel.addConnection(remapped);
}
}
std::pair<std::weak_ptr<NodeGroup>, std::unordered_map<GroupId, GroupId>>
BasicGraphicsScene::restoreGroup(QJsonObject const &groupJson,
QHash<NodeId, QJsonObject> const &nodeById)
{
if (!_groupingEnabled)
return {std::weak_ptr<NodeGroup>(), {}};
std::unordered_map<GroupId, GroupId> IDsMap{};
std::unordered_map<NodeId, NodeId> nodeIdMap{};
std::vector<NodeGraphicsObject *> group_children{};
QJsonArray nodesJson = groupJson.value("nodes").toArray();
for (QJsonValue const &nodeVal : nodesJson) {
QJsonObject nodeObject;
if (nodeVal.isDouble()) {
NodeId const oldNodeId = static_cast<NodeId>(nodeVal.toInt(InvalidNodeId));
if (oldNodeId == InvalidNodeId) {
qWarning() << "restoreGroup(): invalid node id in group:" << nodeVal;
continue;
}
auto it = nodeById.find(oldNodeId);
if (it == nodeById.end()) {
qWarning() << "restoreGroup(): group references missing node id:" << oldNodeId;
continue;
}
nodeObject = it.value();
}
else if (nodeVal.isObject()) {
nodeObject = nodeVal.toObject();
} else {
qWarning() << "restoreGroup(): unexpected node entry type:" << nodeVal;
continue;
}
NodeId const oldNodeId = jsonValueToNodeId(nodeObject.value("id"));
NodeGraphicsObject &nodeRef = loadNodeToMap(nodeObject, false);
NodeId const newNodeId = nodeRef.nodeId();
if (oldNodeId != InvalidNodeId) {
nodeIdMap.emplace(oldNodeId, newNodeId);
IDsMap.emplace(static_cast<GroupId>(oldNodeId), static_cast<GroupId>(newNodeId));
}
group_children.push_back(&nodeRef);
}
QJsonArray connectionJsonArray = groupJson.value("connections").toArray();
for (QJsonValue const &connectionVal : connectionJsonArray) {
loadConnectionToMap(connectionVal.toObject(), nodeIdMap);
}
return std::make_pair(createGroup(group_children, groupJson.value("name").toString()), IDsMap);
}
std::unordered_map<GroupId, std::shared_ptr<NodeGroup>> const &BasicGraphicsScene::groups() const
{
return _groups;
}
QMenu *BasicGraphicsScene::createStdMenu(QPointF const scenePos)
{
Q_UNUSED(scenePos);
QMenu *menu = new QMenu();
if (_groupingEnabled) {
QMenu *addToGroupMenu = menu->addMenu("Add to group...");
for (const auto &groupMap : _groups) {
auto groupPtr = groupMap.second;
auto id = groupMap.first;
if (!groupPtr)
continue;
auto groupName = groupPtr->name();
QAction *groupAction = addToGroupMenu->addAction(groupName);
for (const auto &node : selectedNodes()) {
connect(groupAction, &QAction::triggered, [this, id, node]() {
this->addNodeToGroup(node->nodeId(), id);
});
}
}
QAction *createGroupAction = menu->addAction("Create group from selection");
connect(createGroupAction, &QAction::triggered, [this]() { createGroupFromSelection(); });
}
QAction *copyAction = menu->addAction("Copy");
copyAction->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_C));
QAction *cutAction = menu->addAction("Cut");
cutAction->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_X));
connect(copyAction, &QAction::triggered, this, &BasicGraphicsScene::onCopySelectedObjects);
connect(cutAction, &QAction::triggered, [this] {
onCopySelectedObjects();
onDeleteSelectedObjects();
});
menu->setAttribute(Qt::WA_DeleteOnClose);
return menu;
}
QMenu *BasicGraphicsScene::createGroupMenu(QPointF const scenePos, GroupGraphicsObject *groupGo)
{
Q_UNUSED(scenePos);
QMenu *menu = new QMenu();
QAction *saveGroup = nullptr;
if (_groupingEnabled) {
saveGroup = menu->addAction("Save group...");
}
QAction *copyAction = menu->addAction("Copy");
copyAction->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_C));
QAction *cutAction = menu->addAction("Cut");
cutAction->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_X));
if (saveGroup) {
connect(saveGroup, &QAction::triggered, [this, groupGo] {
saveGroupFile(groupGo->group().id());
});
}
connect(copyAction, &QAction::triggered, this, &BasicGraphicsScene::onCopySelectedObjects);
connect(cutAction, &QAction::triggered, [this] {
onCopySelectedObjects();
onDeleteSelectedObjects();
});
menu->setAttribute(Qt::WA_DeleteOnClose);
return menu;
}
void BasicGraphicsScene::saveGroupFile(GroupId groupID)
{
if (!_groupingEnabled)
return;
QString fileName = QFileDialog::getSaveFileName(nullptr,
tr("Save Node Group"),
QDir::homePath(),
tr("Node Group files (*.group)"));
if (!fileName.isEmpty()) {
if (!fileName.endsWith("group", Qt::CaseInsensitive))
fileName += ".group";
if (auto groupIt = _groups.find(groupID); groupIt != _groups.end()) {
QFile file(fileName);
if (file.open(QIODevice::WriteOnly)) {
file.write(groupIt->second->saveToFile());
} else {
qDebug() << "Error saving group file!";
}
} else {
qDebug() << "Error! Couldn't find group while saving.";
}
}
}
std::weak_ptr<NodeGroup> BasicGraphicsScene::loadGroupFile()
{
if (!_groupingEnabled)
return std::weak_ptr<NodeGroup>();
QString fileName = QFileDialog::getOpenFileName(nullptr,
tr("Open Node Group"),
QDir::currentPath(),
tr("Node Group files (*.group)"));
if (!QFileInfo::exists(fileName))
return std::weak_ptr<NodeGroup>();
QFile file(fileName);
if (!file.open(QIODevice::ReadOnly)) {
qDebug() << "Error loading group file!";
return std::weak_ptr<NodeGroup>();
}
QDir d = QFileInfo(fileName).absoluteDir();
QDir::setCurrent(d.absolutePath());
QByteArray wholeFile = file.readAll();
const QJsonObject fileJson = QJsonDocument::fromJson(wholeFile).object();
QHash<NodeId, QJsonObject> nodeById;
QJsonArray nodesArr = fileJson.value("nodes").toArray();
nodeById.reserve(nodesArr.size());
for (QJsonValue const &v : nodesArr) {
QJsonObject o = v.toObject();
NodeId id = jsonValueToNodeId(o.value("id"));
if (id != InvalidNodeId) {
nodeById.insert(id, o);
}
}
return restoreGroup(fileJson, nodeById).first;
}
GroupId BasicGraphicsScene::nextGroupId()
{
if (_nextGroupId == InvalidGroupId) {
throw std::runtime_error("No available group identifiers");
}
while (_groups.count(_nextGroupId) != 0) {
++_nextGroupId;
if (_nextGroupId == InvalidGroupId) {
throw std::runtime_error("No available group identifiers");
}
}
GroupId const newId = _nextGroupId;
++_nextGroupId;
return newId;
}
}