From d9ec25d1bcea4e45d1965e95bb3099c3864e04a0 Mon Sep 17 00:00:00 2001 From: Aleks Kissinger Date: Sun, 12 Apr 2020 16:43:44 +0100 Subject: parsing and outputting complex paths --- src/data/edge.cpp | 11 ++++ src/data/edge.h | 8 ++- src/data/graph.cpp | 117 +++++++++++++++++++++++++++++++++--------- src/data/graph.h | 4 ++ src/data/graphelementdata.cpp | 26 ++++++++++ src/data/graphelementdata.h | 4 ++ src/data/path.cpp | 29 +++++++++++ src/data/path.h | 6 +++ src/data/tikzassembler.cpp | 34 +++++++++++- src/data/tikzassembler.h | 5 +- src/data/tikzlexer.l | 1 + src/data/tikzparser.y | 7 +-- src/gui/undocommands.cpp | 8 +-- 13 files changed, 225 insertions(+), 35 deletions(-) diff --git a/src/data/edge.cpp b/src/data/edge.cpp index 0bd49e8..652b480 100644 --- a/src/data/edge.cpp +++ b/src/data/edge.cpp @@ -28,6 +28,7 @@ Edge::Edge(Node *s, Node *t, QObject *parent) : { _data = new GraphElementData(this); _edgeNode = nullptr; + _path = nullptr; _dirty = true; if (s != t) { @@ -436,3 +437,13 @@ QPointF Edge::bezierTangent(qreal start, qreal end) const return QPointF(dx, dy); } + +Path *Edge::path() const +{ + return _path; +} + +void Edge::setPath(Path *path) +{ + _path = path; +} diff --git a/src/data/edge.h b/src/data/edge.h index 954145f..ad21f36 100644 --- a/src/data/edge.h +++ b/src/data/edge.h @@ -27,6 +27,8 @@ #include "node.h" #include "style.h" +class Path; + #include #include @@ -92,12 +94,15 @@ public: void setStyleName(const QString & styleName); Style *style() const; + Path *path() const; + void setPath(Path *path); + signals: public slots: private: - QPointF bezierTangent(qreal start, qreal end) const; + QPointF bezierTangent(qreal start, qreal end) const; QString _sourceAnchor; QString _targetAnchor; @@ -108,6 +113,7 @@ private: // referenced Node *_source; Node *_target; + Path *_path; Style *_style; diff --git a/src/data/graph.cpp b/src/data/graph.cpp index 1dd5574..354d22d 100644 --- a/src/data/graph.cpp +++ b/src/data/graph.cpp @@ -73,6 +73,17 @@ void Graph::removeEdge(Edge *e) _edges.removeOne(e); } +void Graph::addPath(Path *p) +{ + p->setParent(this); + _paths << p; +} + +void Graph::removePath(Path *p) +{ + _paths.removeOne(p); +} + int Graph::maxIntName() { int max = -1; @@ -167,6 +178,11 @@ const QVector &Graph::edges() return _edges; } +const QVector &Graph::paths() +{ + return _paths; +} + QRectF Graph::bbox() const { return _bbox; @@ -230,37 +246,90 @@ QString Graph::tikz() Edge *e; + Path *p; foreach (e, _edges) { e->setTikzLine(line); e->updateData(); - code << "\t\t\\draw "; - if (!e->data()->isEmpty()) - code << e->data()->tikz() << " "; - - code << "(" << e->source()->name(); - if (e->sourceAnchor() != "") - code << "." << e->sourceAnchor(); - code << ") to "; + p = e->path(); + if (p) { // if edge is part of a path + if (p->edges().first() == e) { // only add tikz code once per path + code << "\t\t\\draw "; + + GraphElementData *npd = e->data()->nonPathData(); + if (!npd->isEmpty()) + code << npd->tikz() << " "; + delete npd; + + code << "(" << e->source()->name(); + if (e->sourceAnchor() != "") { + code << "." << e->sourceAnchor(); + } else if (p->isCycle()) { + code << ".center"; + } + code << ")"; + + foreach (Edge *e1, p->edges()) { + e1->updateData(); + code << " to "; + + GraphElementData *pd = e1->data()->pathData(); + if (!pd->isEmpty()) + code << pd->tikz() << " "; + delete pd; + + if (e1->hasEdgeNode()) { + code << "node "; + if (!e1->edgeNode()->data()->isEmpty()) + code << e1->edgeNode()->data()->tikz() << " "; + code << "{" << e1->edgeNode()->label() << "} "; + } + + if (e->source() == e1->target()) { + code << "cycle"; + } else { + code << "(" << e1->target()->name(); + if (e1->targetAnchor() != "") { + code << "." << e1->targetAnchor(); + } else if (e1 != p->edges().last()) { + code << ".center"; + } + code << ")"; + } + } + code << ";\n"; + line++; + } + } else { // edge is not part of a path + code << "\t\t\\draw "; + + if (!e->data()->isEmpty()) + code << e->data()->tikz() << " "; + + code << "(" << e->source()->name(); + if (e->sourceAnchor() != "") + code << "." << e->sourceAnchor(); + code << ") to "; + + if (e->hasEdgeNode()) { + code << "node "; + if (!e->edgeNode()->data()->isEmpty()) + code << e->edgeNode()->data()->tikz() << " "; + code << "{" << e->edgeNode()->label() << "} "; + } - if (e->hasEdgeNode()) { - code << "node "; - if (!e->edgeNode()->data()->isEmpty()) - code << e->edgeNode()->data()->tikz() << " "; - code << "{" << e->edgeNode()->label() << "} "; - } + if (e->source() == e->target()) { + code << "()"; + } else { + code << "(" << e->target()->name(); + if (e->targetAnchor() != "") + code << "." << e->targetAnchor(); + code << ")"; + } - if (e->source() == e->target()) { - code << "()"; - } else { - code << "(" << e->target()->name(); - if (e->targetAnchor() != "") - code << "." << e->targetAnchor(); - code << ")"; + code << ";\n"; + line++; } - - code << ";\n"; - line++; } if (!_edges.isEmpty()) { diff --git a/src/data/graph.h b/src/data/graph.h index a996bcb..c306bb2 100644 --- a/src/data/graph.h +++ b/src/data/graph.h @@ -47,6 +47,8 @@ public: void addEdge(Edge *e); void addEdge(Edge *e, int index); void removeEdge(Edge *e); + void addPath(Path *p); + void removePath(Path *p); int maxIntName(); void reorderNodes(const QVector &newOrder); void reorderEdges(const QVector &newOrder); @@ -65,6 +67,7 @@ public: const QVector &nodes(); const QVector &edges(); + const QVector &paths(); QRectF bbox() const; void setBbox(const QRectF &bbox); @@ -122,6 +125,7 @@ public slots: private: QVector _nodes; QVector _edges; + QVector _paths; //QMultiHash inEdges; //QMultiHash outEdges; GraphElementData *_data; diff --git a/src/data/graphelementdata.cpp b/src/data/graphelementdata.cpp index 931f86a..d2146d9 100644 --- a/src/data/graphelementdata.cpp +++ b/src/data/graphelementdata.cpp @@ -265,3 +265,29 @@ QVector GraphElementData::properties() const { return _properties; } + +GraphElementData *GraphElementData::pathData() const +{ + GraphElementData *d = new GraphElementData(); + foreach(GraphElementProperty p, _properties) { + if (isPathProperty(p.key())) d->add(p); + } + return d; +} + +GraphElementData *GraphElementData::nonPathData() const +{ + GraphElementData *d = new GraphElementData(); + foreach(GraphElementProperty p, _properties) { + if (!isPathProperty(p.key())) d->add(p); + } + return d; +} + +bool GraphElementData::isPathProperty(QString key) +{ + return (key == "bend left" || + key == "bend right" || + key == "in" || + key == "out"); +} diff --git a/src/data/graphelementdata.h b/src/data/graphelementdata.h index 8a50a93..8022a14 100644 --- a/src/data/graphelementdata.h +++ b/src/data/graphelementdata.h @@ -78,12 +78,16 @@ public: bool isEmpty(); QVector properties() const; + GraphElementData *pathData() const; + GraphElementData *nonPathData() const; + signals: public slots: private: QVector _properties; + static bool isPathProperty(QString key); }; #endif // GRAPHELEMENTDATA_H diff --git a/src/data/path.cpp b/src/data/path.cpp index f213b22..1438d64 100644 --- a/src/data/path.cpp +++ b/src/data/path.cpp @@ -4,3 +4,32 @@ Path::Path(QObject *parent) : QObject(parent) { } + +int Path::length() const +{ + return _edges.length(); +} + +void Path::addEdge(Edge *e) +{ + e->setPath(this); + _edges << e; +} + +void Path::removeEdges() +{ + foreach(Edge *e, _edges) { + e->setPath(nullptr); + } + _edges.clear(); +} + +bool Path::isCycle() const +{ + return !_edges.isEmpty() && _edges.first()->source() == _edges.last()->target(); +} + +QVector Path::edges() const +{ + return _edges; +} diff --git a/src/data/path.h b/src/data/path.h index 381d486..3c83170 100644 --- a/src/data/path.h +++ b/src/data/path.h @@ -10,6 +10,12 @@ class Path : public QObject Q_OBJECT public: explicit Path(QObject *parent = nullptr); + int length() const; + void addEdge(Edge *e); + void removeEdges(); + bool isCycle() const; + + QVector edges() const; private: QVector _edges; diff --git a/src/data/tikzassembler.cpp b/src/data/tikzassembler.cpp index ee75f7b..3cb3c10 100644 --- a/src/data/tikzassembler.cpp +++ b/src/data/tikzassembler.cpp @@ -30,6 +30,7 @@ TikzAssembler::TikzAssembler(Graph *graph, QObject *parent) : yylex_init(&scanner); yyset_extra(this, scanner); _currentEdgeData = nullptr; + _currentPath = nullptr; } TikzAssembler::TikzAssembler(TikzStyles *tikzStyles, QObject *parent) : @@ -38,6 +39,7 @@ TikzAssembler::TikzAssembler(TikzStyles *tikzStyles, QObject *parent) : yylex_init(&scanner); yyset_extra(this, scanner); _currentEdgeData = nullptr; + _currentPath = nullptr; } void TikzAssembler::addNodeToMap(Node *n) { _nodeMap.insert(n->name(), n); } @@ -82,6 +84,15 @@ void TikzAssembler::setCurrentEdgeSource(Node *currentEdgeSource) _currentEdgeSource = currentEdgeSource; } +Node *TikzAssembler::currentPathSource() const +{ + if (_currentPath && _currentPath->length() > 0) { + return _currentPath->edges()[0]->source(); + } else { + return nullptr; + } +} + GraphElementData *TikzAssembler::currentEdgeData() const { return _currentEdgeData; @@ -102,12 +113,31 @@ void TikzAssembler::setCurrentEdgeSourceAnchor(const QString ¤tEdgeSourceA _currentEdgeSourceAnchor = currentEdgeSourceAnchor; } +void TikzAssembler::addEdge(Edge *e) +{ + if (!_currentPath) _currentPath = new Path(); + _currentPath->addEdge(e); + _graph->addEdge(e); +} + void TikzAssembler::finishCurrentPath() { if (_currentEdgeData) { - delete _currentEdgeData; + GraphElementData *d = _currentEdgeData; _currentEdgeData = nullptr; + delete d; + } + + if (_currentPath) { + if (_currentPath->length() < 2) { + _currentPath->removeEdges(); + Path *p = _currentPath; + _currentPath = nullptr; + delete p; + } else { + _graph->addPath(_currentPath); + _currentPath = nullptr; + } } - // TODO: create a path and add it to graph } diff --git a/src/data/tikzassembler.h b/src/data/tikzassembler.h index f3cabb6..f86abcd 100644 --- a/src/data/tikzassembler.h +++ b/src/data/tikzassembler.h @@ -23,7 +23,6 @@ #ifndef TIKZASSEMBLER_H #define TIKZASSEMBLER_H -#include "node.h" #include "graph.h" #include "tikzstyles.h" @@ -49,12 +48,15 @@ public: Node *currentEdgeSource() const; void setCurrentEdgeSource(Node *currentEdgeSource); + Node *currentPathSource() const; + GraphElementData *currentEdgeData() const; void setCurrentEdgeData(GraphElementData *currentEdgeData); QString currentEdgeSourceAnchor() const; void setCurrentEdgeSourceAnchor(const QString ¤tEdgeSourceAnchor); + void addEdge(Edge *e); void finishCurrentPath(); signals: @@ -65,6 +67,7 @@ private: QHash _nodeMap; Graph *_graph; TikzStyles *_tikzStyles; + Path *_currentPath; Node *_currentEdgeSource; GraphElementData *_currentEdgeData; QString _currentEdgeSourceAnchor; diff --git a/src/data/tikzlexer.l b/src/data/tikzlexer.l index 0d80467..615cf0d 100644 --- a/src/data/tikzlexer.l +++ b/src/data/tikzlexer.l @@ -82,6 +82,7 @@ FLOAT \-?[0-9]*(\.[0-9]+)? node { return NODE; } at { return AT; } to { return TO; } +cycle { return CYCLE; } \([ ]*{FLOAT}[ ]*,[ ]*{FLOAT}[ ]*\) { yylloc->last_column = yylloc->first_column + 1; diff --git a/src/data/tikzparser.y b/src/data/tikzparser.y index 895f75d..1e7b8cc 100644 --- a/src/data/tikzparser.y +++ b/src/data/tikzparser.y @@ -96,6 +96,7 @@ void yyerror(YYLTYPE *yylloc, void * /*scanner*/, const char *str) { %token NODE "node" %token AT "at" %token TO "to" +%token CYCLE "cycle" %token SEMICOLON ";" %token COMMA "," @@ -252,8 +253,8 @@ edgetarget: "to" optproperties optedgenode optnoderef { if ($4.loop) { t = assembler->currentEdgeSource(); } else if ($4.cycle) { - // TODO: should be source of first edge in path - t = assembler->currentEdgeSource(); + t = assembler->currentPathSource(); + if (!t) t = s; } else { t = $4.node; } @@ -285,7 +286,7 @@ edgetarget: "to" optproperties optedgenode optnoderef { if (cd) e->setData(cd->copy()); } e->setAttributesFromData(); - assembler->graph()->addEdge(e); + assembler->addEdge(e); } } diff --git a/src/gui/undocommands.cpp b/src/gui/undocommands.cpp index c5c26af..9a1ef34 100644 --- a/src/gui/undocommands.cpp +++ b/src/gui/undocommands.cpp @@ -488,7 +488,7 @@ void ReflectNodesCommand::undo() } } - _scene->refreshAdjacentEdges(_nodes.toList()); + _scene->refreshAdjacentEdges(_nodes.values()); GraphUpdateCommand::undo(); } @@ -501,7 +501,7 @@ void ReflectNodesCommand::redo() } } - _scene->refreshAdjacentEdges(_nodes.toList()); + _scene->refreshAdjacentEdges(_nodes.values()); GraphUpdateCommand::redo(); } @@ -520,7 +520,7 @@ void RotateNodesCommand::undo() } } - _scene->refreshAdjacentEdges(_nodes.toList()); + _scene->refreshAdjacentEdges(_nodes.values()); GraphUpdateCommand::undo(); } @@ -533,7 +533,7 @@ void RotateNodesCommand::redo() } } - _scene->refreshAdjacentEdges(_nodes.toList()); + _scene->refreshAdjacentEdges(_nodes.values()); GraphUpdateCommand::redo(); } -- cgit v1.2.3