diff --git a/src/skeletondocument.cpp b/src/skeletondocument.cpp index 990e812c..da24ed7d 100644 --- a/src/skeletondocument.cpp +++ b/src/skeletondocument.cpp @@ -2,8 +2,12 @@ #include #include #include +#include +#include +#include #include "skeletondocument.h" #include "util.h" +#include "skeletonxml.h" unsigned long SkeletonDocument::m_maxSnapshot = 1000; @@ -497,9 +501,18 @@ void SkeletonDocument::setNodeRootMarkMode(QUuid nodeId, SkeletonNodeRootMarkMod nodeIt->second.rootMarkMode = mode; } -void SkeletonDocument::toSnapshot(SkeletonSnapshot *snapshot) +void SkeletonDocument::toSnapshot(SkeletonSnapshot *snapshot, const std::set &limitNodeIds) const { + std::set limitPartIds; + for (const auto &nodeId: limitNodeIds) { + const SkeletonNode *node = findNode(nodeId); + if (!node) + continue; + limitPartIds.insert(node->partId); + } for (const auto &partIt : partMap) { + if (!limitPartIds.empty() && limitPartIds.find(partIt.first) == limitPartIds.end()) + continue; std::map part; part["id"] = partIt.second.id.toString(); part["visible"] = partIt.second.visible ? "true" : "false"; @@ -510,6 +523,8 @@ void SkeletonDocument::toSnapshot(SkeletonSnapshot *snapshot) snapshot->parts[part["id"]] = part; } for (const auto &nodeIt: nodeMap) { + if (!limitNodeIds.empty() && limitNodeIds.find(nodeIt.first) == limitNodeIds.end()) + continue; std::map node; node["id"] = nodeIt.second.id.toString(); node["radius"] = QString::number(nodeIt.second.radius); @@ -526,6 +541,10 @@ void SkeletonDocument::toSnapshot(SkeletonSnapshot *snapshot) for (const auto &edgeIt: edgeMap) { if (edgeIt.second.nodeIds.size() != 2) continue; + if (!limitNodeIds.empty() && + (limitNodeIds.find(edgeIt.second.nodeIds[0]) == limitNodeIds.end() || + limitNodeIds.find(edgeIt.second.nodeIds[1]) == limitNodeIds.end())) + continue; std::map edge; edge["id"] = edgeIt.second.id.toString(); edge["from"] = edgeIt.second.nodeIds[0].toString(); @@ -538,10 +557,87 @@ void SkeletonDocument::toSnapshot(SkeletonSnapshot *snapshot) qDebug() << "Export edge to snapshot " << edge["from"] << "<=>" << edge["to"]; } for (const auto &partIdIt: partIds) { + if (!limitPartIds.empty() && limitPartIds.find(partIdIt) == limitPartIds.end()) + continue; snapshot->partIdList.push_back(partIdIt.toString()); } } +void SkeletonDocument::addFromSnapshot(const SkeletonSnapshot &snapshot) +{ + std::map oldNewIdMap; + for (const auto &partKv : snapshot.parts) { + SkeletonPart part; + oldNewIdMap[QUuid(partKv.first)] = part.id; + part.name = valueOfKeyInMapOrEmpty(partKv.second, "name"); + part.visible = isTrueValueString(valueOfKeyInMapOrEmpty(partKv.second, "visible")); + part.locked = isTrueValueString(valueOfKeyInMapOrEmpty(partKv.second, "locked")); + part.subdived = isTrueValueString(valueOfKeyInMapOrEmpty(partKv.second, "subdived")); + partMap[part.id] = part; + } + for (const auto &nodeKv : snapshot.nodes) { + if (nodeKv.second.find("radius") == nodeKv.second.end() || + nodeKv.second.find("x") == nodeKv.second.end() || + nodeKv.second.find("y") == nodeKv.second.end() || + nodeKv.second.find("z") == nodeKv.second.end() || + nodeKv.second.find("partId") == nodeKv.second.end()) + continue; + SkeletonNode node; + oldNewIdMap[QUuid(nodeKv.first)] = node.id; + node.name = valueOfKeyInMapOrEmpty(nodeKv.second, "name"); + node.radius = valueOfKeyInMapOrEmpty(nodeKv.second, "radius").toFloat(); + node.x = valueOfKeyInMapOrEmpty(nodeKv.second, "x").toFloat(); + node.y = valueOfKeyInMapOrEmpty(nodeKv.second, "y").toFloat(); + node.z = valueOfKeyInMapOrEmpty(nodeKv.second, "z").toFloat(); + node.partId = oldNewIdMap[QUuid(valueOfKeyInMapOrEmpty(nodeKv.second, "partId"))]; + node.rootMarkMode = SkeletonNodeRootMarkModeFromString(valueOfKeyInMapOrEmpty(nodeKv.second, "rootMarkMode")); + nodeMap[node.id] = node; + } + for (const auto &edgeKv : snapshot.edges) { + if (edgeKv.second.find("from") == edgeKv.second.end() || + edgeKv.second.find("to") == edgeKv.second.end() || + edgeKv.second.find("partId") == edgeKv.second.end()) + continue; + SkeletonEdge edge; + oldNewIdMap[QUuid(edgeKv.first)] = edge.id; + edge.name = valueOfKeyInMapOrEmpty(edgeKv.second, "name"); + edge.partId = oldNewIdMap[QUuid(valueOfKeyInMapOrEmpty(edgeKv.second, "partId"))]; + edge.branchMode = SkeletonEdgeBranchModeFromString(valueOfKeyInMapOrEmpty(edgeKv.second, "branchMode")); + QString fromNodeId = valueOfKeyInMapOrEmpty(edgeKv.second, "from"); + if (!fromNodeId.isEmpty()) { + QUuid fromId = oldNewIdMap[QUuid(fromNodeId)]; + edge.nodeIds.push_back(fromId); + nodeMap[fromId].edgeIds.push_back(edge.id); + } + QString toNodeId = valueOfKeyInMapOrEmpty(edgeKv.second, "to"); + if (!toNodeId.isEmpty()) { + QUuid toId = oldNewIdMap[QUuid(toNodeId)]; + edge.nodeIds.push_back(toId); + nodeMap[toId].edgeIds.push_back(edge.id); + } + edgeMap[edge.id] = edge; + } + for (const auto &nodeIt: nodeMap) { + partMap[nodeIt.second.partId].nodeIds.push_back(nodeIt.first); + } + for (const auto &partIdIt: snapshot.partIdList) { + partIds.push_back(oldNewIdMap[QUuid(partIdIt)]); + } + + for (const auto &nodeIt: nodeMap) { + emit nodeAdded(nodeIt.first); + } + for (const auto &edgeIt: edgeMap) { + emit edgeAdded(edgeIt.first); + } + for (const auto &partIt : partMap) { + emit partAdded(partIt.first); + } + + emit partListChanged(); + emit skeletonChanged(); +} + void SkeletonDocument::fromSnapshot(const SkeletonSnapshot &snapshot) { for (const auto &nodeIt: nodeMap) { @@ -560,61 +656,7 @@ void SkeletonDocument::fromSnapshot(const SkeletonSnapshot &snapshot) partIds.clear(); emit partListChanged(); - for (const auto &nodeKv : snapshot.nodes) { - SkeletonNode node(QUuid(nodeKv.first)); - node.name = valueOfKeyInMapOrEmpty(nodeKv.second, "name"); - node.radius = valueOfKeyInMapOrEmpty(nodeKv.second, "radius").toFloat(); - node.x = valueOfKeyInMapOrEmpty(nodeKv.second, "x").toFloat(); - node.y = valueOfKeyInMapOrEmpty(nodeKv.second, "y").toFloat(); - node.z = valueOfKeyInMapOrEmpty(nodeKv.second, "z").toFloat(); - node.partId = QUuid(valueOfKeyInMapOrEmpty(nodeKv.second, "partId")); - node.rootMarkMode = SkeletonNodeRootMarkModeFromString(valueOfKeyInMapOrEmpty(nodeKv.second, "rootMarkMode")); - nodeMap[node.id] = node; - } - for (const auto &edgeKv : snapshot.edges) { - SkeletonEdge edge(QUuid(edgeKv.first)); - edge.name = valueOfKeyInMapOrEmpty(edgeKv.second, "name"); - edge.partId = QUuid(valueOfKeyInMapOrEmpty(edgeKv.second, "partId")); - edge.branchMode = SkeletonEdgeBranchModeFromString(valueOfKeyInMapOrEmpty(edgeKv.second, "branchMode")); - QString fromNodeId = valueOfKeyInMapOrEmpty(edgeKv.second, "from"); - if (!fromNodeId.isEmpty()) { - edge.nodeIds.push_back(QUuid(fromNodeId)); - nodeMap[QUuid(fromNodeId)].edgeIds.push_back(edge.id); - } - QString toNodeId = valueOfKeyInMapOrEmpty(edgeKv.second, "to"); - if (!toNodeId.isEmpty()) { - edge.nodeIds.push_back(QUuid(toNodeId)); - nodeMap[QUuid(toNodeId)].edgeIds.push_back(edge.id); - } - edgeMap[edge.id] = edge; - } - for (const auto &partKv : snapshot.parts) { - SkeletonPart part(QUuid(partKv.first)); - part.name = valueOfKeyInMapOrEmpty(partKv.second, "name"); - part.visible = isTrueValueString(valueOfKeyInMapOrEmpty(partKv.second, "visible")); - part.locked = isTrueValueString(valueOfKeyInMapOrEmpty(partKv.second, "locked")); - part.subdived = isTrueValueString(valueOfKeyInMapOrEmpty(partKv.second, "subdived")); - partMap[part.id] = part; - } - for (const auto &nodeIt: nodeMap) { - partMap[nodeIt.second.partId].nodeIds.push_back(nodeIt.first); - } - for (const auto &partIdIt: snapshot.partIdList) { - partIds.push_back(QUuid(partIdIt)); - } - - for (const auto &nodeIt: nodeMap) { - emit nodeAdded(nodeIt.first); - } - for (const auto &edgeIt: edgeMap) { - emit edgeAdded(edgeIt.first); - } - for (const auto &partIt : partMap) { - emit partAdded(partIt.first); - } - - emit partListChanged(); - emit skeletonChanged(); + addFromSnapshot(snapshot); } const char *SkeletonNodeRootMarkModeToString(SkeletonNodeRootMarkMode mode) @@ -814,3 +856,14 @@ void SkeletonDocument::redo() qDebug() << "Undo/Redo items:" << m_undoItems.size() << m_redoItems.size(); } +void SkeletonDocument::paste() +{ + const QClipboard *clipboard = QApplication::clipboard(); + const QMimeData *mimeData = clipboard->mimeData(); + if (mimeData->hasText()) { + QXmlStreamReader xmlStreamReader(mimeData->text()); + SkeletonSnapshot snapshot; + loadSkeletonFromXmlStream(&snapshot, xmlStreamReader); + addFromSnapshot(snapshot); + } +} diff --git a/src/skeletondocument.h b/src/skeletondocument.h index 0b0b331c..90e56633 100644 --- a/src/skeletondocument.h +++ b/src/skeletondocument.h @@ -160,8 +160,9 @@ public: std::vector partIds; QImage turnaround; SkeletonDocumentEditMode editMode; - void toSnapshot(SkeletonSnapshot *snapshot); + void toSnapshot(SkeletonSnapshot *snapshot, const std::set &limitNodeIds=std::set()) const; void fromSnapshot(const SkeletonSnapshot &snapshot); + void addFromSnapshot(const SkeletonSnapshot &snapshot); const SkeletonNode *findNode(QUuid nodeId) const; const SkeletonEdge *findEdge(QUuid edgeId) const; const SkeletonPart *findPart(QUuid partId) const; @@ -191,6 +192,7 @@ public slots: void saveSnapshot(); void undo(); void redo(); + void paste(); private: void splitPartByNode(std::vector> *groups, QUuid nodeId); void joinNodeAndNeiborsToGroup(std::vector *group, QUuid nodeId, std::set *visitMap, QUuid noUseEdgeId=QUuid()); diff --git a/src/skeletondocumentwindow.cpp b/src/skeletondocumentwindow.cpp index f55959e8..7ab3d60e 100644 --- a/src/skeletondocumentwindow.cpp +++ b/src/skeletondocumentwindow.cpp @@ -193,6 +193,7 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() : connect(graphicsWidget, &SkeletonGraphicsWidget::groupOperationAdded, m_document, &SkeletonDocument::saveSnapshot); connect(graphicsWidget, &SkeletonGraphicsWidget::undo, m_document, &SkeletonDocument::undo); connect(graphicsWidget, &SkeletonGraphicsWidget::redo, m_document, &SkeletonDocument::redo); + connect(graphicsWidget, &SkeletonGraphicsWidget::paste, m_document, &SkeletonDocument::paste); connect(m_document, &SkeletonDocument::nodeAdded, graphicsWidget, &SkeletonGraphicsWidget::nodeAdded); connect(m_document, &SkeletonDocument::nodeRemoved, graphicsWidget, &SkeletonGraphicsWidget::nodeRemoved); diff --git a/src/skeletongraphicswidget.cpp b/src/skeletongraphicswidget.cpp index 8738af46..2e89272a 100644 --- a/src/skeletongraphicswidget.cpp +++ b/src/skeletongraphicswidget.cpp @@ -6,9 +6,12 @@ #include #include #include +#include +#include #include "skeletongraphicswidget.h" #include "theme.h" #include "util.h" +#include "skeletonxml.h" SkeletonGraphicsWidget::SkeletonGraphicsWidget(const SkeletonDocument *document) : m_document(document), @@ -79,12 +82,28 @@ void SkeletonGraphicsWidget::showContextMenu(const QPoint &pos) emit setEditMode(SkeletonDocumentEditMode::Add); }); contextMenu.addAction(&addAction); + contextMenu.addSeparator(); QAction deleteAction("Delete", this); connect(&deleteAction, &QAction::triggered, this, &SkeletonGraphicsWidget::deleteSelected); deleteAction.setEnabled(!m_rangeSelectionSet.empty()); contextMenu.addAction(&deleteAction); + + QAction cutAction("Cut", this); + connect(&cutAction, &QAction::triggered, this, &SkeletonGraphicsWidget::cut); + cutAction.setEnabled(!nodeItemMap.empty()); + contextMenu.addAction(&cutAction); + + QAction copyAction("Copy", this); + connect(©Action, &QAction::triggered, this, &SkeletonGraphicsWidget::copy); + copyAction.setEnabled(!nodeItemMap.empty()); + contextMenu.addAction(©Action); + + QAction pasteAction("Paste", this); + connect(&pasteAction, &QAction::triggered, m_document, &SkeletonDocument::paste); + contextMenu.addAction(&pasteAction); + contextMenu.addSeparator(); QAction selectAllAction("Select All", this); @@ -621,6 +640,18 @@ bool SkeletonGraphicsWidget::keyPress(QKeyEvent *event) emit redo(); } } + } else if (event->key() == Qt::Key_X) { + if (QGuiApplication::queryKeyboardModifiers().testFlag(Qt::ControlModifier)) { + cut(); + } + } else if (event->key() == Qt::Key_C) { + if (QGuiApplication::queryKeyboardModifiers().testFlag(Qt::ControlModifier)) { + copy(); + } + } else if (event->key() == Qt::Key_V) { + if (QGuiApplication::queryKeyboardModifiers().testFlag(Qt::ControlModifier)) { + emit paste(); + } } return false; } @@ -632,6 +663,10 @@ void SkeletonGraphicsWidget::nodeAdded(QUuid nodeId) qDebug() << "New node added but node id not exist:" << nodeId; return; } + if (nodeItemMap.find(nodeId) != nodeItemMap.end()) { + qDebug() << "New node added but node item already exist:" << nodeId; + return; + } SkeletonGraphicsNodeItem *mainProfileItem = new SkeletonGraphicsNodeItem(SkeletonProfile::Main); SkeletonGraphicsNodeItem *sideProfileItem = new SkeletonGraphicsNodeItem(SkeletonProfile::Side); mainProfileItem->setOrigin(scenePosFromUnified(QPointF(node->x, node->y))); @@ -679,6 +714,10 @@ void SkeletonGraphicsWidget::edgeAdded(QUuid edgeId) qDebug() << "Node not found:" << toNodeId; return; } + if (edgeItemMap.find(edgeId) != edgeItemMap.end()) { + qDebug() << "New edge added but edge item already exist:" << edgeId; + return; + } SkeletonGraphicsEdgeItem *mainProfileEdgeItem = new SkeletonGraphicsEdgeItem(); SkeletonGraphicsEdgeItem *sideProfileEdgeItem = new SkeletonGraphicsEdgeItem(); mainProfileEdgeItem->setId(edgeId); @@ -980,3 +1019,26 @@ void SkeletonGraphicsWidget::unselectAll() } m_rangeSelectionSet.clear(); } + +void SkeletonGraphicsWidget::cut() +{ + copy(); + deleteSelected(); +} + +void SkeletonGraphicsWidget::copy() +{ + std::set nodeIdSet; + std::set edgeIdSet; + readSkeletonNodeAndEdgeIdSetFromRangeSelection(&nodeIdSet, &edgeIdSet); + if (nodeIdSet.empty()) + return; + SkeletonSnapshot snapshot; + m_document->toSnapshot(&snapshot, nodeIdSet); + QString snapshotXml; + QXmlStreamWriter xmlStreamWriter(&snapshotXml); + saveSkeletonToXmlStream(&snapshot, &xmlStreamWriter); + QClipboard *clipboard = QApplication::clipboard(); + clipboard->setText(snapshotXml); +} + diff --git a/src/skeletongraphicswidget.h b/src/skeletongraphicswidget.h index 10e5b28a..783042d1 100644 --- a/src/skeletongraphicswidget.h +++ b/src/skeletongraphicswidget.h @@ -262,6 +262,7 @@ signals: void groupOperationAdded(); void undo(); void redo(); + void paste(); public: SkeletonGraphicsWidget(const SkeletonDocument *document); std::map> nodeItemMap; @@ -301,6 +302,8 @@ public slots: void selectAll(); void unselectAll(); void selectPartAll(); + void cut(); + void copy(); private slots: void turnaroundImageReady(); private: diff --git a/src/skeletonxml.cpp b/src/skeletonxml.cpp index 7a5ef500..08da900f 100644 --- a/src/skeletonxml.cpp +++ b/src/skeletonxml.cpp @@ -10,6 +10,15 @@ void saveSkeletonToXmlStream(SkeletonSnapshot *snapshot, QXmlStreamWriter *write for (canvasIterator = snapshot->canvas.begin(); canvasIterator != snapshot->canvas.end(); canvasIterator++) { writer->writeAttribute(canvasIterator->first, canvasIterator->second); } + + writer->writeStartElement("partIdList"); + std::vector::iterator partIdIterator; + for (partIdIterator = snapshot->partIdList.begin(); partIdIterator != snapshot->partIdList.end(); partIdIterator++) { + writer->writeStartElement("partId"); + writer->writeAttribute("id", *partIdIterator); + writer->writeEndElement(); + } + writer->writeEndElement(); writer->writeStartElement("nodes"); std::map>::iterator nodeIterator; @@ -34,6 +43,18 @@ void saveSkeletonToXmlStream(SkeletonSnapshot *snapshot, QXmlStreamWriter *write writer->writeEndElement(); } writer->writeEndElement(); + + writer->writeStartElement("parts"); + std::map>::iterator partIterator; + for (partIterator = snapshot->parts.begin(); partIterator != snapshot->parts.end(); partIterator++) { + std::map::iterator partAttributeIterator; + writer->writeStartElement("part"); + for (partAttributeIterator = partIterator->second.begin(); partAttributeIterator != partIterator->second.end(); partAttributeIterator++) { + writer->writeAttribute(partAttributeIterator->first, partAttributeIterator->second); + } + writer->writeEndElement(); + } + writer->writeEndElement(); writer->writeEndElement(); writer->writeEndDocument(); @@ -50,16 +71,33 @@ void loadSkeletonFromXmlStream(SkeletonSnapshot *snapshot, QXmlStreamReader &rea } } else if (reader.name() == "node") { QString nodeId = reader.attributes().value("id").toString(); + if (nodeId.isEmpty()) + continue; std::map *nodeMap = &snapshot->nodes[nodeId]; foreach(const QXmlStreamAttribute &attr, reader.attributes()) { (*nodeMap)[attr.name().toString()] = attr.value().toString(); } } else if (reader.name() == "edge") { - QString nodeId = reader.attributes().value("id").toString(); - std::map *edgeMap = &snapshot->edges[nodeId]; + QString edgeId = reader.attributes().value("id").toString(); + if (edgeId.isEmpty()) + continue; + std::map *edgeMap = &snapshot->edges[edgeId]; foreach(const QXmlStreamAttribute &attr, reader.attributes()) { (*edgeMap)[attr.name().toString()] = attr.value().toString(); } + } else if (reader.name() == "part") { + QString partId = reader.attributes().value("id").toString(); + if (partId.isEmpty()) + continue; + std::map *partMap = &snapshot->parts[partId]; + foreach(const QXmlStreamAttribute &attr, reader.attributes()) { + (*partMap)[attr.name().toString()] = attr.value().toString(); + } + } else if (reader.name() == "partId") { + QString partId = reader.attributes().value("id").toString(); + if (partId.isEmpty()) + continue; + snapshot->partIdList.push_back(partId); } } }