summaryrefslogtreecommitdiff
path: root/src/gui/tikzscene.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/gui/tikzscene.cpp')
-rw-r--r--src/gui/tikzscene.cpp244
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;
+}