diff options
Diffstat (limited to 'src/gui/tikzscene.cpp')
-rw-r--r-- | src/gui/tikzscene.cpp | 244 |
1 files changed, 232 insertions, 12 deletions
diff --git a/src/gui/tikzscene.cpp b/src/gui/tikzscene.cpp index 087f621..33e4710 100644 --- a/src/gui/tikzscene.cpp +++ b/src/gui/tikzscene.cpp @@ -27,6 +27,7 @@ #include <QDebug> #include <QClipboard> #include <QInputDialog> +#include <QMessageBox> #include <cmath> #include <delimitedstringvalidator.h> #include <QSettings> @@ -96,12 +97,25 @@ void TikzScene::graphReplaced() } _edgeItems.clear(); + foreach (PathItem *pi, _pathItems) { + removeItem(pi); + delete pi; + } + _pathItems.clear(); + foreach (Edge *e, graph()->edges()) { //e->attachStyle(); //e->updateControls(); EdgeItem *ei = new EdgeItem(e); _edgeItems.insert(e, ei); addItem(ei); + + Path *p = e->path(); + if (p && p->edges().first() == e) { + PathItem *pi = new PathItem(p); + _pathItems.insert(p, pi); + addItem(pi); + } } foreach (Node *n, graph()->nodes()) { @@ -236,13 +250,18 @@ void TikzScene::mergeNodes() Node *n = _tikzDocument->graph()->nodes()[i]; if (m1.contains(n)) delNodes.insert(i, n); } + + QSet<Path*> delPaths; for (int i = 0; i < _tikzDocument->graph()->edges().length(); ++i) { Edge *e = _tikzDocument->graph()->edges()[i]; - if (m1.contains(e->source()) || m1.contains(e->target())) delEdges.insert(i, e); + if (m1.contains(e->source()) || m1.contains(e->target())) { + delEdges.insert(i, e); + if (e->path()) delPaths << e->path(); + } } - DeleteCommand *cmd = new DeleteCommand(this, delNodes, delEdges, - selNodes, selEdges); - _tikzDocument->undoStack()->push(cmd); + _tikzDocument->undoStack()->push(new SplitPathCommand(this, delPaths)); + _tikzDocument->undoStack()->push(new DeleteCommand(this, delNodes, delEdges, + selNodes, selEdges)); _tikzDocument->undoStack()->endMacro(); } @@ -295,11 +314,161 @@ void TikzScene::reverseSelectedEdges() _tikzDocument->undoStack()->push(cmd); } +void TikzScene::makePath(bool duplicateEdges) +{ + QSet<Node*> selNodes; + QSet<Edge*> selEdges; + QSet<Edge*> edges; + getSelection(selNodes, selEdges); + + edges = selEdges; + + // if no edges are selected, try to infer edges from nodes + if (edges.isEmpty()) { + foreach(Edge *e, graph()->edges()) { + if (selNodes.contains(e->source()) && selNodes.contains(e->target())) + edges << e; + } + } + + if (edges.size() < 2) { + //QMessageBox::warning(nullptr, "Error", "Paths must contain at least 2 edges."); + return; + } + + foreach (Edge *e, edges) { + if (e->path() != nullptr && !duplicateEdges) { + //QMessageBox::warning(nullptr, "Error", "Edges must not already be in another path."); + // TODO: maybe we want to automatically split paths if edges are in a path already? + return; + } + } + + _tikzDocument->undoStack()->beginMacro("Make Path"); + + QVector<Edge *> oldEdgeOrder = graph()->edges(); + QSet<Edge *> oldEdges, newEdges; + oldEdges = edges; + + if (duplicateEdges) { + foreach (Edge *e, edges) { + Edge *e1 = e->copy(); + _tikzDocument->undoStack()->push(new AddEdgeCommand(this, e1, false, selNodes, selEdges)); + newEdges << e1; + oldEdgeOrder << e1; + } + edges = newEdges; + } + + // try to turn selected edges into one contiguous chain or cycle, recording + // which edges need to be flipped. + + // n.b. this is O(n^2) in path length. This could be optimised by saving + // vertex neighbourhoods, but probably doesn't win anything for n < 100. + + QSet<Edge*> flip; + QVector<Edge*> p; + int pLen = -1; + + // keep going as long as 'p' grows + while (pLen < p.length()) { + pLen = p.length(); + Edge *e = nullptr; + foreach (e, edges) { + Node *s = e->source(); + Node *t = e->target(); + if (p.isEmpty()) { + p.append(e); + break; + } + + Node *head = (flip.contains(p.first())) ? p.first()->target() : p.first()->source(); + Node *tail = (flip.contains(p.last())) ? p.last()->source() : p.last()->target(); + + if (s == head || t == head) { + if (s == head) flip << e; + p.prepend(e); + break; + } + + if (s == tail || t == tail) { + if (t == tail) flip << e; + p.append(e); + break; + } + } + + if (e) edges.remove(e); + } + + if (!edges.isEmpty()) { + QMessageBox::warning(nullptr, "Error", "Selected edges do not form a path."); + return; + } + + _tikzDocument->undoStack()->push(new ReverseEdgesCommand(this, flip)); + + // order all of the edges together, and in the case of + // duplicate edges, just below the first original. + QVector<Edge*> newEdgeOrder; + bool firstEdge = true; + foreach (Edge *e, oldEdgeOrder) { + if (oldEdges.contains(e)) { + if (firstEdge) { + newEdgeOrder += p; + firstEdge = false; + } + + if (duplicateEdges) newEdgeOrder << e; + } else if (!newEdges.contains(e)) { + newEdgeOrder << e; + } + } + + _tikzDocument->undoStack()->push(new ReorderCommand(this, + graph()->nodes(), graph()->nodes(), oldEdgeOrder, newEdgeOrder)); + + QMap<Edge*, GraphElementData*> oldEdgeData; + foreach (Edge *e, p) { + if (e != p.first()) oldEdgeData[e] = e->data()->copy(); + } + + _tikzDocument->undoStack()->push(new MakePathCommand(this, p, oldEdgeData)); + _tikzDocument->undoStack()->endMacro(); +} + +void TikzScene::splitPath() +{ + QSet<Node*> selNodes; + QSet<Edge*> edges; + getSelection(selNodes, edges); + + // if no edges are selected, try to infer edges from nodes + if (edges.isEmpty()) { + foreach(Edge *e, graph()->edges()) { + if (selNodes.contains(e->source()) && selNodes.contains(e->target())) + edges << e; + } + } + + QSet<Path*> paths; + foreach (Edge *e, edges) { + if (e->path()) paths << e->path(); + } + + _tikzDocument->undoStack()->push(new SplitPathCommand(this, paths)); +} + void TikzScene::refreshZIndices() { qreal z = 0.0; foreach (Edge *e, graph()->edges()) { - edgeItems()[e]->setZValue(z); + if (e->path() && e == e->path()->edges().first()) { + pathItems()[e->path()]->setZValue(z); + edgeItems()[e]->setZValue(z + 0.1); + } else { + edgeItems()[e]->setZValue(z); + } z += 1.0; } @@ -318,6 +487,7 @@ void TikzScene::mousePressEvent(QGraphicsSceneMouseEvent *event) _mouseDownPos = event->scenePos(); _draggingNodes = false; + _selectingEdge = nullptr; // radius of a control point for bezier edges, in scene coordinates qreal cpR = GLOBAL_SCALEF * (0.1); @@ -398,9 +568,19 @@ void TikzScene::mousePressEvent(QGraphicsSceneMouseEvent *event) } } - auto its = items(_mouseDownPos); - if (!its.isEmpty() && dynamic_cast<NodeItem*>(its[0])) - _draggingNodes = true; + QList<QGraphicsItem*> its = items(_mouseDownPos); + if (!its.isEmpty()) { + if (dynamic_cast<NodeItem*>(its[0])) { + _draggingNodes = true; + } else { + foreach (QGraphicsItem *gi, its) { + if (EdgeItem *ei = dynamic_cast<EdgeItem*>(gi)) { + _selectingEdge = ei->edge(); + break; + } + } + } + } } break; @@ -512,6 +692,8 @@ void TikzScene::mouseMoveEvent(QGraphicsSceneMouseEvent *event) } _modifyEdgeItem->readPos(); + Path *p = _modifyEdgeItem->edge()->path(); + if (p) pathItems()[p]->readPos(); } else if (_draggingNodes) { // nodes being dragged QGraphicsScene::mouseMoveEvent(event); @@ -598,6 +780,23 @@ void TikzScene::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) // otherwise, process mouse move normally QGraphicsScene::mouseReleaseEvent(event); + if (_selectingEdge) { + bool sel = edgeItems()[_selectingEdge]->isSelected(); + Path *p = _selectingEdge->path(); + if (p) { + foreach (Edge *e, p->edges()) { + if (e != _selectingEdge) + edgeItems()[e]->setSelected(sel); + nodeItems()[e->source()]->setSelected(sel); + nodeItems()[e->target()]->setSelected(sel); + } + } +// else { +// nodeItems()[_selectingEdge->source()]->setSelected(sel); +// nodeItems()[_selectingEdge->target()]->setSelected(sel); +// } + } + if (_rubberBandItem->isVisible()) { QPainterPath sel; sel.addRect(_rubberBandItem->rect()); @@ -963,6 +1162,7 @@ void TikzScene::deleteSelectedItems() QMap<int,Node*> deleteNodes; QMap<int,Edge*> deleteEdges; + QSet<Path*> deletePaths; for (int i = 0; i < _tikzDocument->graph()->nodes().length(); ++i) { Node *n = _tikzDocument->graph()->nodes()[i]; @@ -973,14 +1173,20 @@ void TikzScene::deleteSelectedItems() Edge *e = _tikzDocument->graph()->edges()[i]; if (selEdges.contains(e) || selNodes.contains(e->source()) || - selNodes.contains(e->target())) deleteEdges.insert(i, e); + selNodes.contains(e->target())) + { + if (e->path()) deletePaths << e->path(); + deleteEdges.insert(i, e); + } } //qDebug() << "nodes:" << deleteNodes; //qDebug() << "edges:" << deleteEdges; - DeleteCommand *cmd = new DeleteCommand(this, deleteNodes, deleteEdges, - selNodes, selEdges); - _tikzDocument->undoStack()->push(cmd); + _tikzDocument->undoStack()->beginMacro("Delete"); + _tikzDocument->undoStack()->push(new SplitPathCommand(this, deletePaths)); + _tikzDocument->undoStack()->push(new DeleteCommand(this, deleteNodes, deleteEdges, + selNodes, selEdges)); + _tikzDocument->undoStack()->endMacro(); } void TikzScene::copyToClipboard() @@ -1159,6 +1365,8 @@ void TikzScene::refreshSceneBounds() { void TikzScene::refreshAdjacentEdges(QList<Node*> nodes) { if (nodes.empty()) return; + + QSet<Path*> paths; foreach (Edge *e, _edgeItems.keys()) { EdgeItem *ei = _edgeItems[e]; @@ -1169,6 +1377,13 @@ void TikzScene::refreshAdjacentEdges(QList<Node*> nodes) ei->readPos(); } } + + // only update paths once + Path *p = ei->edge()->path(); + if (p && !paths.contains(p)) { + pathItems()[p]->readPos(); + paths << p; + } } } @@ -1195,3 +1410,8 @@ QMap<Edge*,EdgeItem*> &TikzScene::edgeItems() { return _edgeItems; } + +QMap<Path *, PathItem *> &TikzScene::pathItems() +{ + return _pathItems; +} |