Add cut/copy/paste

master
Jeremy Hu 2018-04-09 16:46:06 +08:00
parent de0075bab4
commit f49e88484f
6 changed files with 218 additions and 59 deletions

View File

@ -2,8 +2,12 @@
#include <QDebug>
#include <QThread>
#include <QGuiApplication>
#include <QClipboard>
#include <QMimeData>
#include <QApplication>
#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<QUuid> &limitNodeIds) const
{
std::set<QUuid> 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<QString, QString> 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<QString, QString> 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<QString, QString> 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<QUuid, QUuid> 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);
}
}

View File

@ -160,8 +160,9 @@ public:
std::vector<QUuid> partIds;
QImage turnaround;
SkeletonDocumentEditMode editMode;
void toSnapshot(SkeletonSnapshot *snapshot);
void toSnapshot(SkeletonSnapshot *snapshot, const std::set<QUuid> &limitNodeIds=std::set<QUuid>()) 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<std::vector<QUuid>> *groups, QUuid nodeId);
void joinNodeAndNeiborsToGroup(std::vector<QUuid> *group, QUuid nodeId, std::set<QUuid> *visitMap, QUuid noUseEdgeId=QUuid());

View File

@ -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);

View File

@ -6,9 +6,12 @@
#include <algorithm>
#include <QVector2D>
#include <QMenu>
#include <QApplication>
#include <QClipboard>
#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(&copyAction, &QAction::triggered, this, &SkeletonGraphicsWidget::copy);
copyAction.setEnabled(!nodeItemMap.empty());
contextMenu.addAction(&copyAction);
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<QUuid> nodeIdSet;
std::set<QUuid> 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);
}

View File

@ -262,6 +262,7 @@ signals:
void groupOperationAdded();
void undo();
void redo();
void paste();
public:
SkeletonGraphicsWidget(const SkeletonDocument *document);
std::map<QUuid, std::pair<SkeletonGraphicsNodeItem *, SkeletonGraphicsNodeItem *>> nodeItemMap;
@ -301,6 +302,8 @@ public slots:
void selectAll();
void unselectAll();
void selectPartAll();
void cut();
void copy();
private slots:
void turnaroundImageReady();
private:

View File

@ -11,6 +11,15 @@ void saveSkeletonToXmlStream(SkeletonSnapshot *snapshot, QXmlStreamWriter *write
writer->writeAttribute(canvasIterator->first, canvasIterator->second);
}
writer->writeStartElement("partIdList");
std::vector<QString>::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<QString, std::map<QString, QString>>::iterator nodeIterator;
for (nodeIterator = snapshot->nodes.begin(); nodeIterator != snapshot->nodes.end(); nodeIterator++) {
@ -34,6 +43,18 @@ void saveSkeletonToXmlStream(SkeletonSnapshot *snapshot, QXmlStreamWriter *write
writer->writeEndElement();
}
writer->writeEndElement();
writer->writeStartElement("parts");
std::map<QString, std::map<QString, QString>>::iterator partIterator;
for (partIterator = snapshot->parts.begin(); partIterator != snapshot->parts.end(); partIterator++) {
std::map<QString, QString>::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<QString, QString> *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<QString, QString> *edgeMap = &snapshot->edges[nodeId];
QString edgeId = reader.attributes().value("id").toString();
if (edgeId.isEmpty())
continue;
std::map<QString, QString> *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<QString, QString> *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);
}
}
}