From 6dda16a24dfe7cbd0d90b77c57f1cf789210feb5 Mon Sep 17 00:00:00 2001 From: Aleks Kissinger Date: Mon, 13 Apr 2020 14:40:07 +0100 Subject: ability to make paths from edges --- src/data/graph.cpp | 4 +- src/data/tikzdocument.cpp | 1 + src/gui/mainmenu.cpp | 18 ++++++++- src/gui/mainmenu.h | 2 + src/gui/mainmenu.ui | 25 +++++++++++++ src/gui/tikzscene.cpp | 94 +++++++++++++++++++++++++++++++++++++++++++++++ src/gui/tikzscene.h | 2 + src/gui/undocommands.cpp | 48 ++++++++++++++++++++++++ src/gui/undocommands.h | 16 ++++++++ tex/tikzit-logo.tikz | 38 ++++++++++++++++++- 10 files changed, 242 insertions(+), 6 deletions(-) diff --git a/src/data/graph.cpp b/src/data/graph.cpp index 24a17a5..979423e 100644 --- a/src/data/graph.cpp +++ b/src/data/graph.cpp @@ -269,10 +269,10 @@ QString Graph::tikz() code << ")"; foreach (Edge *e1, p->edges()) { - e1->setTikzLine(line); - e1->updateData(); code << "\n\t\t\t to "; line++; + e1->setTikzLine(line); + e1->updateData(); GraphElementData *pd = e1->data()->pathData(); if (!pd->isEmpty()) diff --git a/src/data/tikzdocument.cpp b/src/data/tikzdocument.cpp index 1099779..633242d 100644 --- a/src/data/tikzdocument.cpp +++ b/src/data/tikzdocument.cpp @@ -90,6 +90,7 @@ void TikzDocument::open(QString fileName) refreshTikz(); setClean(); } else { + // TODO: should not quietly fail to open newGraph->deleteLater(); _parseSuccess = false; } diff --git a/src/gui/mainmenu.cpp b/src/gui/mainmenu.cpp index 7b7623b..092d8b4 100644 --- a/src/gui/mainmenu.cpp +++ b/src/gui/mainmenu.cpp @@ -239,6 +239,18 @@ void MainMenu::on_actionMerge_Nodes_triggered() tikzit->activeWindow()->tikzScene()->mergeNodes(); } +void MainMenu::on_actionMake_Path_triggered() +{ + if (tikzit->activeWindow() != 0) + tikzit->activeWindow()->tikzScene()->makePath(); +} + +void MainMenu::on_actionSplit_Path_triggered() +{ + if (tikzit->activeWindow() != 0) + tikzit->activeWindow()->tikzScene()->splitPath(); +} + // Tikz void MainMenu::on_actionParse_triggered() @@ -333,8 +345,10 @@ void MainMenu::on_actionZoom_Out_triggered() void MainMenu::on_actionShow_Node_Labels_triggered() { - tikzit->activeWindow()->tikzScene()->setDrawNodeLabels(ui.actionShow_Node_Labels->isChecked()); - tikzit->activeWindow()->tikzScene()->invalidate(); + if (tikzit->activeWindow() != 0) { + tikzit->activeWindow()->tikzScene()->setDrawNodeLabels(ui.actionShow_Node_Labels->isChecked()); + tikzit->activeWindow()->tikzScene()->invalidate(); + } } void MainMenu::on_actionAbout_triggered() diff --git a/src/gui/mainmenu.h b/src/gui/mainmenu.h index 8268802..431e43a 100644 --- a/src/gui/mainmenu.h +++ b/src/gui/mainmenu.h @@ -67,6 +67,8 @@ public slots: void on_actionExtendRight_triggered(); void on_actionReverse_Edge_Direction_triggered(); void on_actionMerge_Nodes_triggered(); + void on_actionMake_Path_triggered(); + void on_actionSplit_Path_triggered(); // Tools void on_actionParse_triggered(); diff --git a/src/gui/mainmenu.ui b/src/gui/mainmenu.ui index 46f4881..2e390f9 100644 --- a/src/gui/mainmenu.ui +++ b/src/gui/mainmenu.ui @@ -58,6 +58,13 @@ + + + Path + + + + @@ -74,6 +81,8 @@ + + @@ -460,6 +469,22 @@ Ctrl+Shift+Space + + + Make Path + + + Ctrl+P + + + + + Split Path + + + Ctrl+Shift+P + + diff --git a/src/gui/tikzscene.cpp b/src/gui/tikzscene.cpp index 087f621..cebf5be 100644 --- a/src/gui/tikzscene.cpp +++ b/src/gui/tikzscene.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -295,6 +296,99 @@ void TikzScene::reverseSelectedEdges() _tikzDocument->undoStack()->push(cmd); } +void TikzScene::makePath() +{ + QSet selNodes; + QSet 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; + } + } + + if (edges.size() < 2) { + //QMessageBox::warning(nullptr, "Error", "Paths must contain at least 2 edges."); + return; + } + + foreach (Edge *e, edges) { + if (e->path() != nullptr) { + //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; + } + } + + // 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 flip; + QVector 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; + } + + //qDebug() << p; + //qDebug() << flip; + + QMap oldEdgeData; + foreach (Edge *e, p) { + if (e != p.first()) oldEdgeData[e] = e->data()->copy(); + } + qDebug() << oldEdgeData; + + _tikzDocument->undoStack()->beginMacro("Make Path"); + _tikzDocument->undoStack()->push(new ReverseEdgesCommand(this, flip)); + _tikzDocument->undoStack()->push(new MakePathCommand(this, p, oldEdgeData)); + _tikzDocument->undoStack()->endMacro(); +} + +void TikzScene::splitPath() +{ + // TODO: stub +} + void TikzScene::refreshZIndices() { qreal z = 0.0; diff --git a/src/gui/tikzscene.h b/src/gui/tikzscene.h index 2e7baa5..5996263 100644 --- a/src/gui/tikzscene.h +++ b/src/gui/tikzscene.h @@ -81,6 +81,8 @@ public: void reverseSelectedEdges(); + void makePath(); + void splitPath(); void getSelection(QSet &selNodes, QSet &selEdges) const; QSet getSelectedNodes() const; diff --git a/src/gui/undocommands.cpp b/src/gui/undocommands.cpp index 9a1ef34..a07f251 100644 --- a/src/gui/undocommands.cpp +++ b/src/gui/undocommands.cpp @@ -594,3 +594,51 @@ void ReverseEdgesCommand::redo() GraphUpdateCommand::redo(); } + +MakePathCommand::MakePathCommand(TikzScene *scene, + const QVector &edgeList, + const QMap &oldEdgeData, + QUndoCommand *parent) : + GraphUpdateCommand(scene, parent), + _edgeList(edgeList), _oldEdgeData(oldEdgeData) +{ +} + +void MakePathCommand::undo() +{ + Path *p = _edgeList.first()->path(); + p->removeEdges(); + _scene->graph()->removePath(p); + + foreach (Edge *e, _edgeList) { + if (e != _edgeList.first()) { + // setData transfers ownership, so make a copy + e->setData(_oldEdgeData[e]->copy()); + } + } + + GraphUpdateCommand::undo(); +} + +void MakePathCommand::redo() +{ + GraphElementData *npd = _edgeList.first()->data()->nonPathData(); + GraphElementData *d; + + Path *p = new Path(); + foreach (Edge *e, _edgeList) { + p->addEdge(e); + + if (e != _edgeList.first()) { + d = e->data()->pathData(); + d->mergeData(npd); + e->setData(d); + } + } + + delete npd; + + _scene->graph()->addPath(p); + + GraphUpdateCommand::redo(); +} diff --git a/src/gui/undocommands.h b/src/gui/undocommands.h index 40f0a3b..a1daa07 100644 --- a/src/gui/undocommands.h +++ b/src/gui/undocommands.h @@ -255,4 +255,20 @@ private: QVector _newEdgeOrder; }; +class MakePathCommand : public GraphUpdateCommand +{ +public: + explicit MakePathCommand(TikzScene *scene, + const QVector &edgeList, + const QMap &oldEdgeData, + QUndoCommand *parent = nullptr); + void undo() override; + void redo() override; +private: + QVector _edgeList; + + // creating path clobbers data on all but first edge + QMap _oldEdgeData; +}; + #endif // UNDOCOMMANDS_H diff --git a/tex/tikzit-logo.tikz b/tex/tikzit-logo.tikz index 4cc1b06..be1eb66 100755 --- a/tex/tikzit-logo.tikz +++ b/tex/tikzit-logo.tikz @@ -36,7 +36,41 @@ \node [style=none] (88) at (2.9, 0.775) {}; \end{pgfonlayer} \begin{pgfonlayer}{edgelayer} - \draw [fill=black] (36.center) to [in=135, out=45] (6.center) to [in=-90, out=-45, looseness=1.25] (8.center) to [in=-180, out=90] (11.center) to [in=90, out=0] (10.center) to [in=0, out=-90] (9.center) to [in=135, out=180, looseness=1.25] (7.center) to [in=45, out=-45] (35.center) to [in=180, out=-135, looseness=1.25] (23.center) to [in=90, out=0] (20.center) to [in=0, out=-90] (22.center) to [in=-90, out=180] (18.center) to [in=45, out=90, looseness=1.25] (32.center) to [in=-45, out=-135, looseness=1.25] (5.center) to [in=90, out=135, looseness=1.25] (28.center) to [in=0, out=-90] (30.center) to [in=-90, out=180] (26.center) to [in=-180, out=90] (31.center) to [in=-45, out=0, looseness=1.25] (33.center) to [in=-135, out=135] (37.center) to cycle; - \draw (72.center) to [in=90, out=-30, looseness=0.75] (71.center) to [in=0, out=-90] (70.center) to [in=-60, out=180, looseness=0.75] (69.center) to [in=-15, out=-165] (81.center) to [in=0, out=-120, looseness=0.75] (82.center) to [in=-90, out=180] (83.center) to [in=-150, out=90, looseness=0.75] (84.center) to [in=-90, out=105, looseness=0.75] (80.center) to [in=-180, out=90] (65.center) to [in=165, out=0, looseness=0.75] (85.center) to [in=-180, out=60, looseness=0.75] (86.center) to [in=90, out=0] (87.center) to [in=30, out=-90, looseness=0.75] (88.center) to [in=75, out=-75] cycle; + \draw [style=bg] (72.center) + to [in=90, out=-30, looseness=0.75] (71.center) + to [in=0, out=-90] (70.center) + to [in=-60, out=180, looseness=0.75] (69.center) + to [in=-15, out=-165] (81.center) + to [in=0, out=-120, looseness=0.75] (82.center) + to [in=-90, out=180] (83.center) + to [in=-150, out=90, looseness=0.75] (84.center) + to [in=-90, out=105, looseness=0.75] (80.center) + to [in=-180, out=90] (65.center) + to [in=165, out=0, looseness=0.75] (85.center) + to [in=-180, out=60, looseness=0.75] (86.center) + to [in=90, out=0] (87.center) + to [in=30, out=-90, looseness=0.75] (88.center) + to [in=75, out=-75] cycle; + \draw [style=fg] (36.center) + to [in=135, out=45] (6.center) + to [in=-90, out=-45, looseness=1.25] (8.center) + to [in=-180, out=90] (11.center) + to [in=90, out=0] (10.center) + to [in=0, out=-90] (9.center) + to [in=135, out=180, looseness=1.25] (7.center) + to [in=45, out=-45] (35.center) + to [in=180, out=-135, looseness=1.25] (23.center) + to [in=90, out=0] (20.center) + to [in=0, out=-90] (22.center) + to [in=-90, out=180] (18.center) + to [in=45, out=90, looseness=1.25] (32.center) + to [in=-45, out=-135, looseness=1.25] (5.center) + to [in=90, out=135, looseness=1.25] (28.center) + to [in=0, out=-90] (30.center) + to [in=-90, out=180] (26.center) + to [in=-180, out=90] (31.center) + to [in=-45, out=0, looseness=1.25] (33.center) + to [in=-135, out=135] (37.center) + to cycle; \end{pgfonlayer} \end{tikzpicture} -- cgit v1.2.3