summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAleks Kissinger <aleks0@gmail.com>2020-04-13 14:40:07 +0100
committerAleks Kissinger <aleks0@gmail.com>2020-04-13 14:40:07 +0100
commit6dda16a24dfe7cbd0d90b77c57f1cf789210feb5 (patch)
tree7748bbbe8e484af230481ff092fcf7268c6af24b
parentb48731917964d263175716a29a58d965cb726798 (diff)
ability to make paths from edges
-rw-r--r--src/data/graph.cpp4
-rw-r--r--src/data/tikzdocument.cpp1
-rw-r--r--src/gui/mainmenu.cpp18
-rw-r--r--src/gui/mainmenu.h2
-rw-r--r--src/gui/mainmenu.ui25
-rw-r--r--src/gui/tikzscene.cpp94
-rw-r--r--src/gui/tikzscene.h2
-rw-r--r--src/gui/undocommands.cpp48
-rw-r--r--src/gui/undocommands.h16
-rwxr-xr-xtex/tikzit-logo.tikz38
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 @@
<addaction name="actionRotateCW"/>
<addaction name="actionRotateCCW"/>
</widget>
+ <widget class="QMenu" name="menuPath">
+ <property name="title">
+ <string>Path</string>
+ </property>
+ <addaction name="actionMake_Path"/>
+ <addaction name="actionSplit_Path"/>
+ </widget>
<addaction name="actionUndo"/>
<addaction name="actionRedo"/>
<addaction name="separator"/>
@@ -74,6 +81,8 @@
<addaction name="menuTransform"/>
<addaction name="actionReverse_Edge_Direction"/>
<addaction name="actionMerge_Nodes"/>
+ <addaction name="separator"/>
+ <addaction name="menuPath"/>
</widget>
<widget class="QMenu" name="menuTikz">
<property name="title">
@@ -460,6 +469,22 @@
<string>Ctrl+Shift+Space</string>
</property>
</action>
+ <action name="actionMake_Path">
+ <property name="text">
+ <string>Make Path</string>
+ </property>
+ <property name="shortcut">
+ <string>Ctrl+P</string>
+ </property>
+ </action>
+ <action name="actionSplit_Path">
+ <property name="text">
+ <string>Split Path</string>
+ </property>
+ <property name="shortcut">
+ <string>Ctrl+Shift+P</string>
+ </property>
+ </action>
<addaction name="menuFile"/>
<addaction name="menuEdit"/>
<addaction name="menuView"/>
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 <QDebug>
#include <QClipboard>
#include <QInputDialog>
+#include <QMessageBox>
#include <cmath>
#include <delimitedstringvalidator.h>
#include <QSettings>
@@ -295,6 +296,99 @@ void TikzScene::reverseSelectedEdges()
_tikzDocument->undoStack()->push(cmd);
}
+void TikzScene::makePath()
+{
+ 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;
+ }
+ }
+
+ 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<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;
+ }
+
+ //qDebug() << p;
+ //qDebug() << flip;
+
+ QMap<Edge*, GraphElementData*> 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<Node*> &selNodes, QSet<Edge*> &selEdges) const;
QSet<Node*> 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<Edge *> &edgeList,
+ const QMap<Edge *, GraphElementData *> &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<Edge*> _newEdgeOrder;
};
+class MakePathCommand : public GraphUpdateCommand
+{
+public:
+ explicit MakePathCommand(TikzScene *scene,
+ const QVector<Edge*> &edgeList,
+ const QMap<Edge*,GraphElementData*> &oldEdgeData,
+ QUndoCommand *parent = nullptr);
+ void undo() override;
+ void redo() override;
+private:
+ QVector<Edge*> _edgeList;
+
+ // creating path clobbers data on all but first edge
+ QMap<Edge*,GraphElementData*> _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}