summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGard Spreemann <gspr@nonempty.org>2020-08-20 09:03:17 +0200
committerGard Spreemann <gspr@nonempty.org>2020-08-20 09:03:17 +0200
commit4e1b4e9877732d1e1887674e48312902437f08c5 (patch)
tree7993cbc3ad71eed10da830aca85ef0b99d163bac
parent99f00a3ef9d1bd2d686b1521d25c9afedb880b34 (diff)
parent300267089b80785551c4721684280efe654ec834 (diff)
Merge tag 'v2.1.6' into debian/sid
-rw-r--r--src/data/edge.cpp11
-rw-r--r--src/data/edge.h8
-rw-r--r--src/data/graph.cpp154
-rw-r--r--src/data/graph.h5
-rw-r--r--src/data/graphelementdata.cpp35
-rw-r--r--src/data/graphelementdata.h5
-rw-r--r--src/data/path.cpp35
-rw-r--r--src/data/path.h25
-rw-r--r--src/data/style.cpp75
-rw-r--r--src/data/style.h11
-rw-r--r--src/data/tikzassembler.cpp71
-rw-r--r--src/data/tikzassembler.h19
-rw-r--r--src/data/tikzdocument.cpp1
-rw-r--r--src/data/tikzlexer.l1
-rw-r--r--src/data/tikzparser.y85
-rw-r--r--src/data/tikzparserdefs.h2
-rw-r--r--src/gui/edgeitem.cpp87
-rw-r--r--src/gui/mainmenu.cpp24
-rw-r--r--src/gui/mainmenu.h3
-rw-r--r--src/gui/mainmenu.ui34
-rw-r--r--src/gui/pathitem.cpp68
-rw-r--r--src/gui/pathitem.h28
-rw-r--r--src/gui/styleeditor.cpp81
-rw-r--r--src/gui/styleeditor.h2
-rw-r--r--src/gui/styleeditor.ui38
-rw-r--r--src/gui/tikzscene.cpp244
-rw-r--r--src/gui/tikzscene.h6
-rw-r--r--src/gui/tikzview.cpp2
-rw-r--r--src/gui/undocommands.cpp140
-rw-r--r--src/gui/undocommands.h31
-rw-r--r--src/tikzit.h2
-rw-r--r--tex/2cat.tikz40
-rw-r--r--tex/2cat2.tikz36
-rw-r--r--tex/3cat.tikz72
-rw-r--r--tex/logo.tikzstyles15
-rw-r--r--tex/paths.tikz42
-rwxr-xr-xtex/tikzit-logo.tikz76
-rw-r--r--tikzit.pro6
38 files changed, 1446 insertions, 174 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 <QObject>
#include <QPointF>
@@ -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..74de24d 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<Edge*> &Graph::edges()
return _edges;
}
+const QVector<Path *> &Graph::paths()
+{
+ return _paths;
+}
+
QRectF Graph::bbox() const
{
return _bbox;
@@ -230,37 +246,93 @@ 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 ";
-
- if (e->hasEdgeNode()) {
- code << "node ";
- if (!e->edgeNode()->data()->isEmpty())
- code << e->edgeNode()->data()->tikz() << " ";
- code << "{" << e->edgeNode()->label() << "} ";
- }
+ p = e->path();
+ if (p) { // if edge is part of a path
+ if (p->edges().first() == e) { // only add tikz code once per path
+ e->setTikzLine(line);
+ e->updateData();
+ 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()) {
+ code << "\n\t\t\t to ";
+ line++;
+ e1->setTikzLine(line);
+ e1->updateData();
+
+ 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
+ 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 ";
+
+ 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()) {
@@ -288,9 +360,33 @@ Graph *Graph::copyOfSubgraphWithNodes(QSet<Node *> nds)
g->addNode(n1);
}
}
+
+ QMap<Edge*,Edge*> edgeTable;
foreach (Edge *e, edges()) {
if (nds.contains(e->source()) && nds.contains(e->target())) {
- g->addEdge(e->copy(&nodeTable));
+ Edge *e1 = e->copy(&nodeTable);
+ g->addEdge(e1);
+ edgeTable.insert(e,e1);
+ }
+ }
+
+ // add a copy of a path to the new graph if all of the edges are there
+ foreach (Path *p, paths()) {
+ bool allEdges = true;
+ Path *p1 = new Path();
+ foreach (Edge *e1, p->edges()) {
+ if (edgeTable.contains(e1)) {
+ p1->addEdge(edgeTable[e1]);
+ } else {
+ allEdges = false;
+ break;
+ }
+ }
+ if (allEdges) {
+ g->addPath(p1);
+ } else {
+ p1->removeEdges();
+ delete p1;
}
}
diff --git a/src/data/graph.h b/src/data/graph.h
index 286ccdc..c306bb2 100644
--- a/src/data/graph.h
+++ b/src/data/graph.h
@@ -25,6 +25,7 @@
#include "node.h"
#include "edge.h"
+#include "path.h"
#include "graphelementdata.h"
#include <QObject>
@@ -46,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<Node*> &newOrder);
void reorderEdges(const QVector<Edge*> &newOrder);
@@ -64,6 +67,7 @@ public:
const QVector<Node *> &nodes();
const QVector<Edge*> &edges();
+ const QVector<Path*> &paths();
QRectF bbox() const;
void setBbox(const QRectF &bbox);
@@ -121,6 +125,7 @@ public slots:
private:
QVector<Node*> _nodes;
QVector<Edge*> _edges;
+ QVector<Path*> _paths;
//QMultiHash<Node*,Edge*> inEdges;
//QMultiHash<Node*,Edge*> outEdges;
GraphElementData *_data;
diff --git a/src/data/graphelementdata.cpp b/src/data/graphelementdata.cpp
index cd09a6d..e1e89b1 100644
--- a/src/data/graphelementdata.cpp
+++ b/src/data/graphelementdata.cpp
@@ -110,6 +110,14 @@ int GraphElementData::indexOfKey(QString key)
return -1;
}
+void GraphElementData::mergeData(GraphElementData *d)
+{
+ GraphElementProperty p;
+ foreach (p, d->properties()) {
+ if (!hasProperty(p.key())) add(p);
+ }
+}
+
bool GraphElementData::removeRows(int row, int /*count*/, const QModelIndex &parent)
{
if (row >= 0 && row < _properties.length()) {
@@ -257,3 +265,30 @@ QVector<GraphElementProperty> 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" ||
+ key == "looseness");
+}
diff --git a/src/data/graphelementdata.h b/src/data/graphelementdata.h
index dce0d46..8022a14 100644
--- a/src/data/graphelementdata.h
+++ b/src/data/graphelementdata.h
@@ -49,6 +49,7 @@ public:
bool hasProperty(QString key);
bool atom(QString atom);
int indexOfKey(QString key);
+ void mergeData(GraphElementData *d);
bool removeRows(int row, int count, const QModelIndex &parent) override;
bool moveRows(const QModelIndex &sourceParent,
int sourceRow, int,
@@ -77,12 +78,16 @@ public:
bool isEmpty();
QVector<GraphElementProperty> properties() const;
+ GraphElementData *pathData() const;
+ GraphElementData *nonPathData() const;
+
signals:
public slots:
private:
QVector<GraphElementProperty> _properties;
+ static bool isPathProperty(QString key);
};
#endif // GRAPHELEMENTDATA_H
diff --git a/src/data/path.cpp b/src/data/path.cpp
new file mode 100644
index 0000000..1438d64
--- /dev/null
+++ b/src/data/path.cpp
@@ -0,0 +1,35 @@
+#include "path.h"
+
+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<Edge *> Path::edges() const
+{
+ return _edges;
+}
diff --git a/src/data/path.h b/src/data/path.h
new file mode 100644
index 0000000..3c83170
--- /dev/null
+++ b/src/data/path.h
@@ -0,0 +1,25 @@
+#ifndef PATH_H
+#define PATH_H
+
+#include "edge.h"
+
+#include <QObject>
+
+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<Edge *> edges() const;
+
+private:
+ QVector<Edge*> _edges;
+
+};
+
+#endif // PATH_H
diff --git a/src/data/style.cpp b/src/data/style.cpp
index d0f011d..f4aa892 100644
--- a/src/data/style.cpp
+++ b/src/data/style.cpp
@@ -62,7 +62,22 @@ QColor Style::fillColor(bool tikzitOverride) const
QBrush Style::brush() const
{
- return QBrush(fillColor());
+ if (hasFill()) {
+ return QBrush(fillColor());
+ } else {
+ return Qt::NoBrush;
+ }
+}
+
+bool Style::hasFill() const
+{
+ return (propertyWithDefault("fill", "none") != "none");
+}
+
+bool Style::hasStroke() const
+{
+ if (isEdgeStyle()) return propertyWithDefault("draw", "black") != "none";
+ else return (propertyWithDefault("draw", "none") != "none");
}
QString Style::shape(bool tikzitOverride) const
@@ -152,24 +167,28 @@ Style::DrawStyle Style::drawStyle() const
QPen Style::pen() const
{
- QPen p(strokeColor());
- p.setWidthF((float)strokeThickness() * 2.0f);
-
- QVector<qreal> pat;
- switch (drawStyle()) {
- case Dashed:
- pat << 3.0 << 3.0;
- p.setDashPattern(pat);
- break;
- case Dotted:
- pat << 1.0 << 1.0;
- p.setDashPattern(pat);
- break;
- case Solid:
- break;
- }
+ if (hasStroke()) {
+ QPen p(strokeColor());
+ p.setWidthF((float)strokeThickness() * 2.0f);
+
+ QVector<qreal> pat;
+ switch (drawStyle()) {
+ case Dashed:
+ pat << 3.0 << 3.0;
+ p.setDashPattern(pat);
+ break;
+ case Dotted:
+ pat << 1.0 << 1.0;
+ p.setDashPattern(pat);
+ break;
+ case Solid:
+ break;
+ }
- return p;
+ return p;
+ } else {
+ return Qt::NoPen;
+ }
}
QPainterPath Style::path() const
@@ -225,19 +244,27 @@ QIcon Style::icon() const
px.fill(Qt::transparent);
QPainter painter(&px);
- if (_data == 0) {
- QPen pen(Qt::black);
- pen.setWidth(3);
- } else {
- painter.setPen(pen());
+// if (_data == 0) {
+// QPen pen(Qt::black);
+// pen.setWidth(3);
+// } else {
+// painter.setPen(pen());
+// }
+
+ QPen pn = pen();
+ painter.setPen(pn);
+
+ if (hasFill()) {
+ painter.fillRect(10,50,80,30,brush());
}
painter.drawLine(10, 50, 90, 50);
- QPen pn = pen();
pn.setStyle(Qt::SolidLine);
painter.setPen(pn);
+
+
switch (arrowHead()) {
case Pointer:
painter.drawLine(90,50,80,40);
diff --git a/src/data/style.h b/src/data/style.h
index 78e11dc..0e795e1 100644
--- a/src/data/style.h
+++ b/src/data/style.h
@@ -57,18 +57,21 @@ public:
void setName(const QString &name);
QString propertyWithDefault(QString prop, QString def, bool tikzitOverride=true) const;
QString tikz() const;
- void setArrowAtom(QString atom);
-
- // only relevant for node styles
QColor fillColor(bool tikzitOverride=true) const;
QBrush brush() const;
+ bool hasFill() const;
+ bool hasStroke() const;
+
+ // only relevant for node styles
QString shape(bool tikzitOverride=true) const;
+ QString category() const;
// only relevant for edge styles
Style::ArrowTipStyle arrowHead() const;
Style::ArrowTipStyle arrowTail() const;
Style::DrawStyle drawStyle() const;
- QString category() const;
+ void setArrowAtom(QString atom);
+
protected:
QString _name;
diff --git a/src/data/tikzassembler.cpp b/src/data/tikzassembler.cpp
index cd0b517..3cb3c10 100644
--- a/src/data/tikzassembler.cpp
+++ b/src/data/tikzassembler.cpp
@@ -29,6 +29,8 @@ TikzAssembler::TikzAssembler(Graph *graph, QObject *parent) :
{
yylex_init(&scanner);
yyset_extra(this, scanner);
+ _currentEdgeData = nullptr;
+ _currentPath = nullptr;
}
TikzAssembler::TikzAssembler(TikzStyles *tikzStyles, QObject *parent) :
@@ -36,6 +38,8 @@ 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); }
@@ -70,3 +74,70 @@ bool TikzAssembler::isTikzStyles() const
return _tikzStyles != 0;
}
+Node *TikzAssembler::currentEdgeSource() const
+{
+ return _currentEdgeSource;
+}
+
+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;
+}
+
+void TikzAssembler::setCurrentEdgeData(GraphElementData *currentEdgeData)
+{
+ _currentEdgeData = currentEdgeData;
+}
+
+QString TikzAssembler::currentEdgeSourceAnchor() const
+{
+ return _currentEdgeSourceAnchor;
+}
+
+void TikzAssembler::setCurrentEdgeSourceAnchor(const QString &currentEdgeSourceAnchor)
+{
+ _currentEdgeSourceAnchor = currentEdgeSourceAnchor;
+}
+
+void TikzAssembler::addEdge(Edge *e)
+{
+ if (!_currentPath) _currentPath = new Path();
+ _currentPath->addEdge(e);
+ _graph->addEdge(e);
+}
+
+void TikzAssembler::finishCurrentPath()
+{
+ if (_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;
+ }
+ }
+}
+
diff --git a/src/data/tikzassembler.h b/src/data/tikzassembler.h
index 7b32224..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"
@@ -46,6 +45,20 @@ public:
bool isTikzStyles() const;
+ Node *currentEdgeSource() const;
+ void setCurrentEdgeSource(Node *currentEdgeSource);
+
+ Node *currentPathSource() const;
+
+ GraphElementData *currentEdgeData() const;
+ void setCurrentEdgeData(GraphElementData *currentEdgeData);
+
+ QString currentEdgeSourceAnchor() const;
+ void setCurrentEdgeSourceAnchor(const QString &currentEdgeSourceAnchor);
+
+ void addEdge(Edge *e);
+ void finishCurrentPath();
+
signals:
public slots:
@@ -54,6 +67,10 @@ private:
QHash<QString,Node*> _nodeMap;
Graph *_graph;
TikzStyles *_tikzStyles;
+ Path *_currentPath;
+ Node *_currentEdgeSource;
+ GraphElementData *_currentEdgeData;
+ QString _currentEdgeSourceAnchor;
void *scanner;
};
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/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]+)?
<INITIAL>node { return NODE; }
<INITIAL>at { return AT; }
<INITIAL>to { return TO; }
+<INITIAL>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 4473107..678d8bf 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 ","
@@ -214,10 +215,13 @@ noderef: "(" REFSTRING optanchor ")"
$$.node = assembler->nodeWithName(QString($2));
free($2);
$$.anchor = $3;
+ $$.loop = false;
+ $$.cycle = false;
};
optnoderef:
- noderef { $$ = $1; }
- | "(" ")" { $$.node = 0; $$.anchor = 0; }
+ noderef { $$ = $1; }
+ | "(" ")" { $$.node = 0; $$.anchor = 0; $$.loop = true; $$.cycle = false; }
+ | "cycle" { $$.node = 0; $$.anchor = 0; $$.loop = false; $$.cycle = true; }
optedgenode:
{ $$ = 0; }
| "node" optproperties DELIMITEDSTRING
@@ -228,45 +232,68 @@ optedgenode:
$$->setLabel(QString($3));
free($3);
}
-edge: "\\draw" optproperties noderef "to" optedgenode optnoderef ";"
- {
- Node *s;
+
+edgesource: optproperties noderef {
+ assembler->setCurrentEdgeSource($2.node);
+ if ($2.anchor) {
+ assembler->setCurrentEdgeSourceAnchor(QString($2.anchor));
+ free($2.anchor);
+ } else {
+ assembler->setCurrentEdgeSourceAnchor(QString());
+ }
+ assembler->setCurrentEdgeData($1);
+ }
+
+optedgetargets: edgetarget optedgetargets |
+
+edgetarget: "to" optproperties optedgenode optnoderef {
+ Node *s = assembler->currentEdgeSource();;
Node *t;
-
- s = $3.node;
- if ($6.node) {
- t = $6.node;
+ if ($4.loop) {
+ t = assembler->currentEdgeSource();
+ } else if ($4.cycle) {
+ t = assembler->currentPathSource();
+ if (!t) t = s;
} else {
- t = s;
+ t = $4.node;
}
- // if the source or the target of the edge doesn't exist, quietly ignore it.
- if (s != 0 && t != 0) {
- Edge *edge = new Edge(s, t);
- if ($2) {
- edge->setData($2);
- edge->setAttributesFromData();
- }
+ if (s != 0 && t != 0) { // if source or target don't exist, quietly ignore edge
+ Edge *e = new Edge(s, t);
+ assembler->setCurrentEdgeSource(t);
- if ($5)
- edge->setEdgeNode($5);
- if ($3.anchor) {
- edge->setSourceAnchor(QString($3.anchor));
- free($3.anchor);
+ if (!assembler->currentEdgeSourceAnchor().isEmpty()) {
+ e->setSourceAnchor(assembler->currentEdgeSourceAnchor());
}
- if ($6.node) {
- if ($6.anchor) {
- edge->setTargetAnchor(QString($6.anchor));
- free($6.anchor);
- }
+ if ($4.anchor) {
+ QString a($4.anchor);
+ free($4.anchor);
+ e->setTargetAnchor(a);
+ assembler->setCurrentEdgeSourceAnchor(a);
} else {
- edge->setTargetAnchor(edge->sourceAnchor());
+ assembler->setCurrentEdgeSourceAnchor(QString());
}
- assembler->graph()->addEdge(edge);
+ if ($3) e->setEdgeNode($3);
+
+ GraphElementData *cd = assembler->currentEdgeData();
+ if ($2) {
+ if (cd) $2->mergeData(cd);
+ e->setData($2);
+ } else {
+ if (cd) e->setData(cd->copy());
+ }
+ e->setAttributesFromData();
+ assembler->addEdge(e);
}
+ }
+
+
+edge: "\\draw" edgesource edgetarget optedgetargets ";"
+ {
+ assembler->finishCurrentPath();
};
ignoreprop: val | val "=" val;
diff --git a/src/data/tikzparserdefs.h b/src/data/tikzparserdefs.h
index 02743fe..da60bbf 100644
--- a/src/data/tikzparserdefs.h
+++ b/src/data/tikzparserdefs.h
@@ -33,6 +33,8 @@
struct noderef {
Node *node;
char *anchor;
+ bool cycle;
+ bool loop;
};
inline int isatty(int) { return 0; }
diff --git a/src/gui/edgeitem.cpp b/src/gui/edgeitem.cpp
index 45ae159..675ddd7 100644
--- a/src/gui/edgeitem.cpp
+++ b/src/gui/edgeitem.cpp
@@ -71,7 +71,8 @@ void EdgeItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidge
QPen pen = _edge->style()->pen();
painter->setPen(pen);
painter->setBrush(Qt::NoBrush);
- painter->drawPath(path());
+
+ if (!_edge->path()) painter->drawPath(path());
QPointF ht = _edge->headTangent();
QPointF hLeft(-ht.y(), ht.x());
@@ -83,27 +84,27 @@ void EdgeItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidge
pen.setStyle(Qt::SolidLine);
painter->setPen(pen);
-
-
- switch (_edge->style()->arrowHead()) {
- case Style::Flat:
- {
- painter->drawLine(
- toScreen(_edge->head() + hLeft),
- toScreen(_edge->head() + hRight));
- break;
- }
- case Style::Pointer:
- {
- QPainterPath pth;
- pth.moveTo(toScreen(_edge->head() + ht + hLeft));
- pth.lineTo(toScreen(_edge->head()));
- pth.lineTo(toScreen(_edge->head() + ht + hRight));
- painter->drawPath(pth);
- break;
- }
- case Style::NoTip:
- break;
+ if (!_edge->path() || _edge->path()->edges().last() == _edge) {
+ switch (_edge->style()->arrowHead()) {
+ case Style::Flat:
+ {
+ painter->drawLine(
+ toScreen(_edge->head() + hLeft),
+ toScreen(_edge->head() + hRight));
+ break;
+ }
+ case Style::Pointer:
+ {
+ QPainterPath pth;
+ pth.moveTo(toScreen(_edge->head() + ht + hLeft));
+ pth.lineTo(toScreen(_edge->head()));
+ pth.lineTo(toScreen(_edge->head() + ht + hRight));
+ painter->drawPath(pth);
+ break;
+ }
+ case Style::NoTip:
+ break;
+ }
}
//QPen outline = QPen(Qt::red);
@@ -111,26 +112,28 @@ void EdgeItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidge
//painter->drawPath(_expPath);
//painter->setPen(pen);
- switch (_edge->style()->arrowTail()) {
- case Style::Flat:
- {
- painter->drawLine(
- toScreen(_edge->tail() + tLeft),
- toScreen(_edge->tail() + tRight));
- break;
- }
- case Style::Pointer:
- {
- QPainterPath pth;
- pth.moveTo(toScreen(_edge->tail() + tt + tLeft));
- pth.lineTo(toScreen(_edge->tail()));
- pth.lineTo(toScreen(_edge->tail() + tt + tRight));
- painter->drawPath(pth);
- break;
- }
- case Style::NoTip:
- break;
- }
+ if (!_edge->path() || _edge->path()->edges().first() == _edge) {
+ switch (_edge->style()->arrowTail()) {
+ case Style::Flat:
+ {
+ painter->drawLine(
+ toScreen(_edge->tail() + tLeft),
+ toScreen(_edge->tail() + tRight));
+ break;
+ }
+ case Style::Pointer:
+ {
+ QPainterPath pth;
+ pth.moveTo(toScreen(_edge->tail() + tt + tLeft));
+ pth.lineTo(toScreen(_edge->tail()));
+ pth.lineTo(toScreen(_edge->tail() + tt + tRight));
+ painter->drawPath(pth);
+ break;
+ }
+ case Style::NoTip:
+ break;
+ }
+ }
if (isSelected()) {
QColor draw;
diff --git a/src/gui/mainmenu.cpp b/src/gui/mainmenu.cpp
index 7b7623b..efd453d 100644
--- a/src/gui/mainmenu.cpp
+++ b/src/gui/mainmenu.cpp
@@ -239,6 +239,24 @@ void MainMenu::on_actionMerge_Nodes_triggered()
tikzit->activeWindow()->tikzScene()->mergeNodes();
}
+void MainMenu::on_actionMake_Path_triggered()
+{
+ if (tikzit->activeWindow() != 0)
+ tikzit->activeWindow()->tikzScene()->makePath(false);
+}
+
+void MainMenu::on_actionMake_Path_as_Background_triggered()
+{
+ if (tikzit->activeWindow() != 0)
+ tikzit->activeWindow()->tikzScene()->makePath(true);
+}
+
+void MainMenu::on_actionSplit_Path_triggered()
+{
+ if (tikzit->activeWindow() != 0)
+ tikzit->activeWindow()->tikzScene()->splitPath();
+}
+
// Tikz
void MainMenu::on_actionParse_triggered()
@@ -333,8 +351,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..d0d73e9 100644
--- a/src/gui/mainmenu.h
+++ b/src/gui/mainmenu.h
@@ -67,6 +67,9 @@ public slots:
void on_actionExtendRight_triggered();
void on_actionReverse_Edge_Direction_triggered();
void on_actionMerge_Nodes_triggered();
+ void on_actionMake_Path_triggered();
+ void on_actionMake_Path_as_Background_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..11778db 100644
--- a/src/gui/mainmenu.ui
+++ b/src/gui/mainmenu.ui
@@ -58,6 +58,14 @@
<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"/>
+ <addaction name="actionMake_Path_as_Background"/>
+ </widget>
<addaction name="actionUndo"/>
<addaction name="actionRedo"/>
<addaction name="separator"/>
@@ -74,6 +82,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 +470,30 @@
<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>
+ <action name="actionMake_Path_as_Background">
+ <property name="text">
+ <string>Make Path as Background</string>
+ </property>
+ <property name="shortcut">
+ <string>Ctrl+B</string>
+ </property>
+ </action>
<addaction name="menuFile"/>
<addaction name="menuEdit"/>
<addaction name="menuView"/>
diff --git a/src/gui/pathitem.cpp b/src/gui/pathitem.cpp
new file mode 100644
index 0000000..b45c0a5
--- /dev/null
+++ b/src/gui/pathitem.cpp
@@ -0,0 +1,68 @@
+#include "pathitem.h"
+#include "tikzit.h"
+
+PathItem::PathItem(Path *path)
+{
+ _path = path;
+ readPos();
+}
+
+void PathItem::readPos()
+{
+ QPainterPath painterPath;
+
+ foreach (Edge *e, _path->edges()) {
+ e->updateControls();
+
+ if (e == _path->edges().first())
+ painterPath.moveTo (toScreen(e->tail()));
+
+ if (e->bend() != 0 || !e->basicBendMode()) {
+ painterPath.cubicTo(toScreen(e->cp1()),
+ toScreen(e->cp2()),
+ toScreen(e->head()));
+ } else {
+ painterPath.lineTo(toScreen(e->head()));
+ }
+ }
+
+ setPainterPath(painterPath);
+}
+
+void PathItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *)
+{
+ Style *st = _path->edges().first()->style();
+ QPen pen = st->pen();
+ QBrush brush = st->brush();
+ QColor c = brush.color();
+ brush.setColor(QColor(c.red(),c.green(),c.blue(),200));
+ painter->setPen(pen);
+ painter->setBrush(brush);
+ painter->drawPath(painterPath());
+}
+
+Path *PathItem::path() const
+{
+ return _path;
+}
+
+QPainterPath PathItem::painterPath() const
+{
+ return _painterPath;
+}
+
+void PathItem::setPainterPath(const QPainterPath &painterPath)
+{
+ prepareGeometryChange();
+
+ _painterPath = painterPath;
+ float r = GLOBAL_SCALEF * 0.1;
+ _boundingRect = _painterPath.boundingRect().adjusted(-r,-r,r,r);
+
+ update();
+}
+
+QRectF PathItem::boundingRect() const
+{
+ return _boundingRect;
+}
diff --git a/src/gui/pathitem.h b/src/gui/pathitem.h
new file mode 100644
index 0000000..7dc9c80
--- /dev/null
+++ b/src/gui/pathitem.h
@@ -0,0 +1,28 @@
+#ifndef PATHITEM_H
+#define PATHITEM_H
+
+#include "path.h"
+
+#include <QGraphicsItem>
+
+class PathItem : public QGraphicsItem
+{
+public:
+ PathItem(Path *path);
+ void readPos();
+ void paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) override;
+
+ Path *path() const;
+
+ QPainterPath painterPath() const;
+ void setPainterPath(const QPainterPath &painterPath);
+
+ QRectF boundingRect() const override;
+
+private:
+ Path *_path;
+ QPainterPath _painterPath;
+ QRectF _boundingRect;
+};
+
+#endif // PATHITEM_H
diff --git a/src/gui/styleeditor.cpp b/src/gui/styleeditor.cpp
index e2ade45..f8972b1 100644
--- a/src/gui/styleeditor.cpp
+++ b/src/gui/styleeditor.cpp
@@ -32,8 +32,8 @@ StyleEditor::StyleEditor(QWidget *parent) :
{
ui->setupUi(this);
_formWidgets << ui->name << ui->category <<
- ui->fillColor << ui->hasTikzitFillColor << ui->tikzitFillColor <<
- ui->drawColor << ui->hasTikzitDrawColor << ui->tikzitDrawColor <<
+ ui->fillColor << ui->noFill << ui->hasTikzitFillColor << ui->tikzitFillColor <<
+ ui->drawColor << ui->noDraw << ui->hasTikzitDrawColor << ui->tikzitDrawColor <<
ui->shape << ui->hasTikzitShape << ui->tikzitShape <<
ui->leftArrow << ui->rightArrow <<
ui->properties;
@@ -283,8 +283,16 @@ void StyleEditor::refreshDisplay()
// draw
QColor realDraw = s->strokeColor(false);
QColor draw = s->strokeColor();
- ui->drawColor->setEnabled(true);
- setColor(ui->drawColor, realDraw);
+
+ ui->noDraw->setEnabled(true);
+ if (s->hasStroke()) {
+ ui->drawColor->setEnabled(true);
+ setColor(ui->drawColor, realDraw);
+ ui->noDraw->setChecked(false);
+ } else {
+ ui->noDraw->setChecked(true);
+ ui->drawColor->setEnabled(false);
+ }
// tikzit draw
bool drawOverride = s->data()->hasProperty("tikzit draw");
@@ -294,6 +302,27 @@ void StyleEditor::refreshDisplay()
ui->tikzitDrawColor->setEnabled(drawOverride);
if (drawOverride) setColor(ui->tikzitDrawColor, draw);
+ // fill
+ QColor realFill = s->fillColor(false);
+ QColor fill = s->fillColor();
+
+ ui->noFill->setEnabled(true);
+ if (s->hasFill()) {
+ ui->fillColor->setEnabled(true);
+ setColor(ui->fillColor, realFill);
+ ui->noFill->setChecked(false);
+ } else {
+ ui->noFill->setChecked(true);
+ ui->fillColor->setEnabled(false);
+ }
+
+ // tikzit fill
+ bool fillOverride = s->data()->hasProperty("tikzit fill");
+ ui->hasTikzitFillColor->setEnabled(true);
+ ui->hasTikzitFillColor->setChecked(fillOverride);
+ ui->tikzitFillColor->setEnabled(fillOverride);
+ if (fillOverride) setColor(ui->tikzitFillColor, fill);
+
if (!s->isEdgeStyle()) {
// qDebug() << "node style update";
// category
@@ -301,19 +330,6 @@ void StyleEditor::refreshDisplay()
ui->category->setCurrentText(
s->propertyWithDefault("tikzit category", "", false));
- // fill
- QColor realFill = s->fillColor(false);
- QColor fill = s->fillColor();
- ui->fillColor->setEnabled(true);
- setColor(ui->fillColor, realFill);
-
- // tikzit fill
- bool fillOverride = s->data()->hasProperty("tikzit fill");
- ui->hasTikzitFillColor->setEnabled(true);
- ui->hasTikzitFillColor->setChecked(fillOverride);
- ui->tikzitFillColor->setEnabled(fillOverride);
- if (fillOverride) setColor(ui->tikzitFillColor, fill);
-
// shape
QString realShape = s->propertyWithDefault("shape", "", false);
QString shape = s->propertyWithDefault("tikzit shape", "", false);
@@ -327,13 +343,6 @@ void StyleEditor::refreshDisplay()
ui->tikzitShape->setEnabled(shapeOverride);
if (shapeOverride) ui->tikzitShape->setCurrentText(shape);
} else {
-// qDebug() << "edge style update";
-
- // set fill to gray (disabled)
- ui->fillColor->setEnabled(false);
- ui->tikzitFillColor->setEnabled(false);
- ui->hasTikzitFillColor->setEnabled(false);
-
ui->shape->setEnabled(false);
ui->tikzitShape->setEnabled(false);
ui->hasTikzitShape->setEnabled(false);
@@ -425,6 +434,30 @@ void StyleEditor::on_hasTikzitDrawColor_stateChanged(int state)
}
}
+void StyleEditor::on_noFill_stateChanged(int state)
+{
+ Style *s = activeStyle();
+ if (s != nullptr) {
+ if (state == Qt::Checked) s->data()->setProperty("fill", "none");
+ else s->data()->setProperty("fill", "white");
+ refreshActiveStyle();
+ refreshDisplay();
+ setDirty(true);
+ }
+}
+
+void StyleEditor::on_noDraw_stateChanged(int state)
+{
+ Style *s = activeStyle();
+ if (s != nullptr) {
+ if (state == Qt::Checked) s->data()->setProperty("draw", "none");
+ else s->data()->setProperty("draw", "black");
+ refreshActiveStyle();
+ refreshDisplay();
+ setDirty(true);
+ }
+}
+
void StyleEditor::on_hasTikzitShape_stateChanged(int state)
{
Style *s = activeStyle();
diff --git a/src/gui/styleeditor.h b/src/gui/styleeditor.h
index 2c35d56..7fc62c0 100644
--- a/src/gui/styleeditor.h
+++ b/src/gui/styleeditor.h
@@ -68,6 +68,8 @@ public slots:
void on_tikzitDrawColor_clicked();
void on_hasTikzitFillColor_stateChanged(int state);
void on_hasTikzitDrawColor_stateChanged(int state);
+ void on_noFill_stateChanged(int state);
+ void on_noDraw_stateChanged(int state);
void on_hasTikzitShape_stateChanged(int state);
void on_tikzitShape_currentIndexChanged(int);
diff --git a/src/gui/styleeditor.ui b/src/gui/styleeditor.ui
index 9c06894..22b04a6 100644
--- a/src/gui/styleeditor.ui
+++ b/src/gui/styleeditor.ui
@@ -822,6 +822,44 @@
<bool>true</bool>
</attribute>
</widget>
+ <widget class="QCheckBox" name="noFill">
+ <property name="geometry">
+ <rect>
+ <x>340</x>
+ <y>110</y>
+ <width>71</width>
+ <height>17</height>
+ </rect>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>9</pointsize>
+ <italic>false</italic>
+ </font>
+ </property>
+ <property name="text">
+ <string>none</string>
+ </property>
+ </widget>
+ <widget class="QCheckBox" name="noDraw">
+ <property name="geometry">
+ <rect>
+ <x>340</x>
+ <y>140</y>
+ <width>71</width>
+ <height>17</height>
+ </rect>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>9</pointsize>
+ <italic>false</italic>
+ </font>
+ </property>
+ <property name="text">
+ <string>none</string>
+ </property>
+ </widget>
</widget>
</widget>
<resources/>
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;
+}
diff --git a/src/gui/tikzscene.h b/src/gui/tikzscene.h
index 2e7baa5..e1d30d2 100644
--- a/src/gui/tikzscene.h
+++ b/src/gui/tikzscene.h
@@ -27,6 +27,7 @@
#include "graph.h"
#include "nodeitem.h"
#include "edgeitem.h"
+#include "pathitem.h"
#include "tikzdocument.h"
#include "toolpalette.h"
#include "stylepalette.h"
@@ -48,6 +49,7 @@ public:
Graph *graph();
QMap<Node*,NodeItem*> &nodeItems();
QMap<Edge*,EdgeItem*> &edgeItems();
+ QMap<Path*,PathItem*> &pathItems();
void refreshAdjacentEdges(QList<Node*> nodes);
// void setBounds(QRectF bounds);
@@ -81,6 +83,8 @@ public:
void reverseSelectedEdges();
+ void makePath(bool duplicateEdges);
+ void splitPath();
void getSelection(QSet<Node*> &selNodes, QSet<Edge*> &selEdges) const;
QSet<Node*> getSelectedNodes() const;
@@ -110,9 +114,11 @@ private:
StylePalette *_styles;
QMap<Node*,NodeItem*> _nodeItems;
QMap<Edge*,EdgeItem*> _edgeItems;
+ QMap<Path*,PathItem*> _pathItems;
QGraphicsLineItem *_drawEdgeItem;
QGraphicsRectItem *_rubberBandItem;
EdgeItem *_modifyEdgeItem;
+ Edge *_selectingEdge;
NodeItem *_edgeStartNodeItem;
NodeItem *_edgeEndNodeItem;
bool _firstControlPoint;
diff --git a/src/gui/tikzview.cpp b/src/gui/tikzview.cpp
index 3615685..eb87002 100644
--- a/src/gui/tikzview.cpp
+++ b/src/gui/tikzview.cpp
@@ -149,6 +149,8 @@ void TikzView::wheelEvent(QWheelEvent *event)
} else if (event->angleDelta().y() < 0) {
zoomOut();
}
+ } else if (event->modifiers() & Qt::ShiftModifier) {
+ horizontalScrollBar()->setValue(horizontalScrollBar()->value() - event->angleDelta().y());
}
}
diff --git a/src/gui/undocommands.cpp b/src/gui/undocommands.cpp
index c5c26af..35345de 100644
--- a/src/gui/undocommands.cpp
+++ b/src/gui/undocommands.cpp
@@ -102,6 +102,9 @@ void EdgeBendCommand::undo()
foreach(EdgeItem *ei, _scene->edgeItems()) {
if (ei->edge() == _edge) {
ei->readPos();
+
+ Path *p = ei->edge()->path();
+ if (p) _scene->pathItems()[p]->readPos();
break;
}
}
@@ -118,6 +121,9 @@ void EdgeBendCommand::redo()
foreach(EdgeItem *ei, _scene->edgeItems()) {
if (ei->edge() == _edge) {
ei->readPos();
+
+ Path *p = ei->edge()->path();
+ if (p) _scene->pathItems()[p]->readPos();
break;
}
}
@@ -287,6 +293,9 @@ void ChangeEdgeModeCommand::undo()
// FIXME: this act strangely sometimes
_edge->setBasicBendMode(!_edge->basicBendMode());
_scene->edgeItems()[_edge]->readPos();
+ Path *p = _edge->path();
+ if (p) _scene->pathItems()[p]->readPos();
+
GraphUpdateCommand::undo();
}
@@ -294,6 +303,9 @@ void ChangeEdgeModeCommand::redo()
{
_edge->setBasicBendMode(!_edge->basicBendMode());
_scene->edgeItems()[_edge]->readPos();
+ Path *p = _edge->path();
+ if (p) _scene->pathItems()[p]->readPos();
+
GraphUpdateCommand::redo();
}
@@ -369,6 +381,16 @@ void PasteCommand::undo()
{
_scene->clearSelection();
+ foreach (Path *p, _graph->paths()) {
+ PathItem *pi = _scene->pathItems()[p];
+ _scene->pathItems().remove(p);
+ _scene->removeItem(pi);
+ delete pi;
+
+ p->removeEdges();
+ _scene->graph()->removePath(p);
+ }
+
foreach (Edge *e, _graph->edges()) {
EdgeItem *ei = _scene->edgeItems()[e];
_scene->edgeItems().remove(e);
@@ -399,6 +421,12 @@ void PasteCommand::redo()
_scene->clearSelection();
_scene->graph()->insertGraph(_graph);
+ foreach (Path *p, _graph->paths()) {
+ PathItem *pi = new PathItem(p);
+ _scene->pathItems().insert(p, pi);
+ _scene->addItem(pi);
+ }
+
foreach (Edge *e, _graph->edges()) {
e->attachStyle(); // in case styles have changed
EdgeItem *ei = new EdgeItem(e);
@@ -488,7 +516,7 @@ void ReflectNodesCommand::undo()
}
}
- _scene->refreshAdjacentEdges(_nodes.toList());
+ _scene->refreshAdjacentEdges(_nodes.values());
GraphUpdateCommand::undo();
}
@@ -501,7 +529,7 @@ void ReflectNodesCommand::redo()
}
}
- _scene->refreshAdjacentEdges(_nodes.toList());
+ _scene->refreshAdjacentEdges(_nodes.values());
GraphUpdateCommand::redo();
}
@@ -520,7 +548,7 @@ void RotateNodesCommand::undo()
}
}
- _scene->refreshAdjacentEdges(_nodes.toList());
+ _scene->refreshAdjacentEdges(_nodes.values());
GraphUpdateCommand::undo();
}
@@ -533,7 +561,7 @@ void RotateNodesCommand::redo()
}
}
- _scene->refreshAdjacentEdges(_nodes.toList());
+ _scene->refreshAdjacentEdges(_nodes.values());
GraphUpdateCommand::redo();
}
@@ -594,3 +622,107 @@ 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();
+
+ PathItem *pi = _scene->pathItems()[p];
+ _scene->removeItem(pi);
+ _scene->pathItems().remove(p);
+ delete pi;
+
+ 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());
+ }
+ }
+
+ _scene->refreshZIndices();
+ 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);
+
+ PathItem *pi = new PathItem(p);
+ _scene->addItem(pi);
+ _scene->pathItems().insert(p, pi);
+ pi->readPos();
+
+ _scene->refreshZIndices();
+ GraphUpdateCommand::redo();
+}
+
+SplitPathCommand::SplitPathCommand(TikzScene *scene,
+ const QSet<Path *> &paths,
+ QUndoCommand *parent) :
+ GraphUpdateCommand(scene, parent), _paths(paths)
+{
+ foreach (Path *p, paths) _edgeLists[p] = p->edges();
+}
+
+void SplitPathCommand::undo()
+{
+ foreach (Path *p, _paths) {
+ foreach (Edge *e, _edgeLists[p]) {
+ p->addEdge(e);
+ }
+
+ _scene->graph()->addPath(p);
+
+ PathItem *pi = new PathItem(p);
+ _scene->addItem(pi);
+ _scene->pathItems().insert(p, pi);
+ pi->readPos();
+ }
+
+ _scene->refreshZIndices();
+ GraphUpdateCommand::undo();
+}
+
+void SplitPathCommand::redo()
+{
+ foreach (Path *p, _paths) {
+ PathItem *pi = _scene->pathItems()[p];
+ _scene->removeItem(pi);
+ _scene->pathItems().remove(p);
+ delete pi;
+
+ p->removeEdges();
+ _scene->graph()->removePath(p);
+ }
+
+ _scene->refreshZIndices();
+ GraphUpdateCommand::redo();
+}
diff --git a/src/gui/undocommands.h b/src/gui/undocommands.h
index 40f0a3b..c10eecc 100644
--- a/src/gui/undocommands.h
+++ b/src/gui/undocommands.h
@@ -255,4 +255,35 @@ 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;
+};
+
+class SplitPathCommand : public GraphUpdateCommand
+{
+public:
+ explicit SplitPathCommand(TikzScene *scene,
+ const QSet<Path*> &paths,
+ QUndoCommand *parent = nullptr);
+ void undo() override;
+ void redo() override;
+private:
+ QSet<Path*> _paths;
+
+ // keep a copy of the edge lists so they can be added back to each path in undo()
+ QMap<Path*,QVector<Edge*>> _edgeLists;
+};
+
#endif // UNDOCOMMANDS_H
diff --git a/src/tikzit.h b/src/tikzit.h
index d27d416..5debdf3 100644
--- a/src/tikzit.h
+++ b/src/tikzit.h
@@ -49,7 +49,7 @@
#ifndef TIKZIT_H
#define TIKZIT_H
-#define TIKZIT_VERSION "2.1.5"
+#define TIKZIT_VERSION "2.1.6"
#include "mainwindow.h"
#include "mainmenu.h"
diff --git a/tex/2cat.tikz b/tex/2cat.tikz
new file mode 100644
index 0000000..7888ad2
--- /dev/null
+++ b/tex/2cat.tikz
@@ -0,0 +1,40 @@
+\begin{tikzpicture}
+ \begin{pgfonlayer}{nodelayer}
+ \node [style=none] (0) at (0, 2.75) {};
+ \node [style=none] (1) at (2, 2.75) {};
+ \node [style=none] (2) at (2, -2.75) {};
+ \node [style=none] (3) at (0, -2.75) {};
+ \node [style=none] (4) at (0, -1.25) {};
+ \node [style=none] (5) at (0, 1.25) {};
+ \node [style=none] (6) at (-2, -2.75) {};
+ \node [style=none] (7) at (-2, 2.75) {};
+ \node [style=white dot] (8) at (0, 1.25) {$\mu$};
+ \node [style=white dot] (9) at (0, -1.25) {$\delta$};
+ \end{pgfonlayer}
+ \begin{pgfonlayer}{edgelayer}
+ \draw [style=region B] (5.center)
+ to (0.center)
+ to (1.center)
+ to (2.center)
+ to (3.center)
+ to (4.center)
+ to [bend right=60, looseness=1.25] cycle;
+ \draw [style=region B] (3.center) to (4.center);
+ \draw [style=region B, bend right=60, looseness=1.25] (4.center) to (5.center);
+ \draw [style=region B] (5.center) to (0.center);
+ \draw [style=region A] (0.center)
+ to (5.center)
+ to [bend right=60, looseness=1.25] (4.center)
+ to (3.center)
+ to (6.center)
+ to (7.center)
+ to cycle;
+ \draw [style=region C] (4.center)
+ to [bend left=60, looseness=1.25] (5.center)
+ to [bend left=60, looseness=1.25] cycle;
+ \draw (3.center) to (4.center);
+ \draw [bend left=60, looseness=1.25] (4.center) to (5.center);
+ \draw (5.center) to (0.center);
+ \draw [bend right=60, looseness=1.25] (4.center) to (5.center);
+ \end{pgfonlayer}
+\end{tikzpicture}
diff --git a/tex/2cat2.tikz b/tex/2cat2.tikz
new file mode 100644
index 0000000..7a9ce93
--- /dev/null
+++ b/tex/2cat2.tikz
@@ -0,0 +1,36 @@
+\begin{tikzpicture}
+ \begin{pgfonlayer}{nodelayer}
+ \node [style=none] (0) at (0, 2) {};
+ \node [style=none] (1) at (0, 0) {};
+ \node [style=none] (2) at (1, -2) {};
+ \node [style=none] (3) at (2, -2) {};
+ \node [style=none] (4) at (2, 2) {};
+ \node [style=none] (5) at (0, 2) {};
+ \node [style=none] (6) at (0, 0) {};
+ \node [style=none] (7) at (-1, -2) {};
+ \node [style=none] (8) at (-2, -2) {};
+ \node [style=none] (9) at (-2, 2) {};
+ \node [style=white dot] (10) at (0, 0) {$\mu$};
+ \end{pgfonlayer}
+ \begin{pgfonlayer}{edgelayer}
+ \draw [style=region B] (3.center)
+ to (4.center)
+ to (0.center)
+ to (1.center)
+ to [bend left] (2.center)
+ to cycle;
+ \draw [style=region A] (8.center)
+ to (9.center)
+ to (5.center)
+ to (6.center)
+ to [bend right] (7.center)
+ to cycle;
+ \draw [style=region C] (6.center)
+ to [bend right] (7.center)
+ to (2.center)
+ to [bend right] cycle;
+ \draw [bend left] (7.center) to (6.center);
+ \draw [bend right] (2.center) to (6.center);
+ \draw (6.center) to (5.center);
+ \end{pgfonlayer}
+\end{tikzpicture}
diff --git a/tex/3cat.tikz b/tex/3cat.tikz
new file mode 100644
index 0000000..221bcd4
--- /dev/null
+++ b/tex/3cat.tikz
@@ -0,0 +1,72 @@
+\begin{tikzpicture}
+ \begin{pgfonlayer}{nodelayer}
+ \node [style=none] (0) at (0, -3.25) {};
+ \node [style=none] (1) at (0, -1.5) {};
+ \node [style=none] (2) at (0, 1.5) {};
+ \node [style=none] (3) at (0, 3.25) {};
+ \node [style=none] (4) at (2.25, -3.25) {};
+ \node [style=none] (5) at (2.25, 3.25) {};
+ \node [style=none] (6) at (-2.25, -3.25) {};
+ \node [style=none] (7) at (-2.25, 3.25) {};
+ \node [style=white dot] (8) at (0, -1.5) {};
+ \node [style=white dot] (9) at (0, 1.5) {};
+ \node [style=none] (10) at (1, -2.5) {};
+ \node [style=none] (11) at (1, -0.75) {};
+ \node [style=none] (12) at (1, 2.25) {};
+ \node [style=none] (13) at (1, 4) {};
+ \node [style=none] (14) at (3.25, -2.5) {};
+ \node [style=none] (15) at (3.25, 4) {};
+ \node [style=none] (16) at (-1.25, -2.5) {};
+ \node [style=none] (17) at (-1.25, 4) {};
+ \node [style=white dot] (18) at (1, -0.75) {};
+ \node [style=white dot] (19) at (1, 2.25) {};
+ \end{pgfonlayer}
+ \begin{pgfonlayer}{edgelayer}
+ \draw [style=region B] (3.center)
+ to (5.center)
+ to (4.center)
+ to (0.center)
+ to (1.center)
+ to [bend right=45, looseness=1.25] (2.center)
+ to cycle;
+ \draw [style=region A] (7.center)
+ to (6.center)
+ to (0.center)
+ to (1.center)
+ to [bend left=45, looseness=1.25] (2.center)
+ to (3.center)
+ to cycle;
+ \draw (0.center) to (1.center);
+ \draw [style=region C] (2.center)
+ to [bend left=45, looseness=1.25] (1.center)
+ to [bend left=45, looseness=1.25] cycle;
+ \draw [bend right=45, looseness=1.25] (1.center) to (2.center);
+ \draw (2.center) to (3.center);
+ \draw [bend left=45, looseness=1.25] (1.center) to (2.center);
+ \draw [style=region C, in=135, out=45, loop] (1.center) to ();
+ \draw [style=region C, in=135, out=45, loop] (1.center) to ();
+ \draw [style=region B] (13.center)
+ to (15.center)
+ to (14.center)
+ to (10.center)
+ to (11.center)
+ to [bend right=45, looseness=1.25] (12.center)
+ to cycle;
+ \draw [style=region A] (17.center)
+ to (16.center)
+ to (10.center)
+ to (11.center)
+ to [bend left=45, looseness=1.25] (12.center)
+ to (13.center)
+ to cycle;
+ \draw (10.center) to (11.center);
+ \draw [style=region C] (12.center)
+ to [bend left=45, looseness=1.25] (11.center)
+ to [bend left=45, looseness=1.25] cycle;
+ \draw [bend right=45, looseness=1.25] (11.center) to (12.center);
+ \draw (12.center) to (13.center);
+ \draw [bend left=45, looseness=1.25] (11.center) to (12.center);
+ \draw [style=region C, in=135, out=45, loop] (11.center) to ();
+ \draw [style=region C, in=135, out=45, loop] (11.center) to ();
+ \end{pgfonlayer}
+\end{tikzpicture}
diff --git a/tex/logo.tikzstyles b/tex/logo.tikzstyles
new file mode 100644
index 0000000..1103b0b
--- /dev/null
+++ b/tex/logo.tikzstyles
@@ -0,0 +1,15 @@
+% TiKZ style file generated by TikZiT. You may edit this file manually,
+% but some things (e.g. comments) may be overwritten. To be readable in
+% TikZiT, the only non-comment lines must be of the form:
+% \tikzstyle{NAME}=[PROPERTY LIST]
+
+% Node styles
+\tikzstyle{white dot}=[fill=white, draw=black, shape=circle]
+
+% Edge styles
+\tikzstyle{bg}=[-, line width=0.5mm, fill={rgb,255: red,217; green,217; blue,217}]
+\tikzstyle{fg}=[-, fill=black]
+\tikzstyle{dir}=[->]
+\tikzstyle{region A}=[-, draw=none, fill={rgb,255: red,255; green,160; blue,162}, opacity=0.8]
+\tikzstyle{region B}=[-, draw=none, fill={rgb,255: red,190; green,185; blue,255}, opacity=0.8]
+\tikzstyle{region C}=[-, fill={rgb,255: red,179; green,255; blue,192}, draw=none, opacity=0.8]
diff --git a/tex/paths.tikz b/tex/paths.tikz
new file mode 100644
index 0000000..ebbf676
--- /dev/null
+++ b/tex/paths.tikz
@@ -0,0 +1,42 @@
+\begin{tikzpicture}
+ \begin{pgfonlayer}{nodelayer}
+ \node [style=none] (0) at (-4, 1) {};
+ \node [style=none] (1) at (-1, 1) {};
+ \node [style=none] (2) at (-2.5, -1.5) {};
+ \node [style=none] (7) at (-3.5, 0.75) {};
+ \node [style=none] (8) at (-0.5, 0.75) {};
+ \node [style=none] (9) at (-2, -1.75) {};
+ \node [style=none] (10) at (-3, 0.5) {};
+ \node [style=none] (11) at (0, 0.5) {};
+ \node [style=none] (12) at (-1.5, -2) {};
+ \node [style=none] (13) at (-2.5, 0.25) {};
+ \node [style=none] (14) at (0.5, 0.25) {};
+ \node [style=none] (15) at (-1, -2.25) {};
+ \node [style=none] (3) at (-0.25, 4.25) {};
+ \node [style=none] (4) at (2, 3.75) {};
+ \node [style=none] (5) at (2, 2) {};
+ \node [style=none] (6) at (3.25, 1.25) {};
+ \end{pgfonlayer}
+ \begin{pgfonlayer}{edgelayer}
+ \draw [style=bg] (2.center)
+ to (0.center)
+ to (1.center)
+ to cycle;
+ \draw [style=bg] (9.center)
+ to (7.center)
+ to (8.center)
+ to cycle;
+ \draw [style=bg] (12.center)
+ to (10.center)
+ to (11.center)
+ to cycle;
+ \draw [style=bg] (15.center)
+ to (13.center)
+ to (14.center)
+ to cycle;
+ \draw [style=dir] (3.center)
+ to (4.center)
+ to (5.center)
+ to (6.center);
+ \end{pgfonlayer}
+\end{tikzpicture}
diff --git a/tex/tikzit-logo.tikz b/tex/tikzit-logo.tikz
new file mode 100755
index 0000000..be1eb66
--- /dev/null
+++ b/tex/tikzit-logo.tikz
@@ -0,0 +1,76 @@
+\begin{tikzpicture}
+ \begin{pgfonlayer}{nodelayer}
+ \node [style=none] (5) at (-0.5, -1.25) {};
+ \node [style=none] (6) at (0.5, 1.25) {};
+ \node [style=none] (7) at (1.25, 0.5) {};
+ \node [style=none] (8) at (1.25, 2) {};
+ \node [style=none] (9) at (2, 1.25) {};
+ \node [style=none] (10) at (2.75, 2) {};
+ \node [style=none] (11) at (2, 2.75) {};
+ \node [style=none] (18) at (1.25, -2) {};
+ \node [style=none] (20) at (2.75, -2) {};
+ \node [style=none] (22) at (2, -2.75) {};
+ \node [style=none] (23) at (2, -1.25) {};
+ \node [style=none] (26) at (-2.75, -2) {};
+ \node [style=none] (28) at (-1.25, -2) {};
+ \node [style=none] (30) at (-2, -2.75) {};
+ \node [style=none] (31) at (-2, -1.25) {};
+ \node [style=none] (32) at (0.5, -1.25) {};
+ \node [style=none] (33) at (-1.25, -0.5) {};
+ \node [style=none] (35) at (1.25, -0.5) {};
+ \node [style=none] (36) at (-0.5, 1.25) {};
+ \node [style=none] (37) at (-1.25, 0.5) {};
+ \node [style=none] (65) at (0, 3) {};
+ \node [style=none] (69) at (0.775, -2.9) {};
+ \node [style=none] (70) at (2, -3.5) {};
+ \node [style=none] (71) at (3.5, -2) {};
+ \node [style=none] (72) at (2.9, -0.775) {};
+ \node [style=none] (80) at (-3, 0) {};
+ \node [style=none] (81) at (-0.775, -2.9) {};
+ \node [style=none] (82) at (-2, -3.5) {};
+ \node [style=none] (83) at (-3.5, -2) {};
+ \node [style=none] (84) at (-2.9, -0.775) {};
+ \node [style=none] (85) at (0.775, 2.9) {};
+ \node [style=none] (86) at (2, 3.5) {};
+ \node [style=none] (87) at (3.5, 2) {};
+ \node [style=none] (88) at (2.9, 0.775) {};
+ \end{pgfonlayer}
+ \begin{pgfonlayer}{edgelayer}
+ \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}
diff --git a/tikzit.pro b/tikzit.pro
index e0f3544..a8305da 100644
--- a/tikzit.pro
+++ b/tikzit.pro
@@ -2,7 +2,7 @@
QT += core gui widgets network
-VERSION = 2.1.5
+VERSION = 2.1.6
test {
CONFIG += testcase
@@ -53,6 +53,8 @@ include(flex.pri)
include(bison.pri)
SOURCES += src/gui/mainwindow.cpp \
+ src/data/path.cpp \
+ src/gui/pathitem.cpp \
src/gui/toolpalette.cpp \
src/gui/tikzscene.cpp \
src/data/graph.cpp \
@@ -85,6 +87,8 @@ SOURCES += src/gui/mainwindow.cpp \
src/gui/preferencedialog.cpp
HEADERS += src/gui/mainwindow.h \
+ src/data/path.h \
+ src/gui/pathitem.h \
src/gui/toolpalette.h \
src/gui/tikzscene.h \
src/data/graph.h \