summaryrefslogtreecommitdiff
path: root/src/data
diff options
context:
space:
mode:
authorGard Spreemann <gspreemann@gmail.com>2018-10-23 16:35:21 +0200
committerGard Spreemann <gspreemann@gmail.com>2018-10-23 16:35:21 +0200
commit30dcb0a5460c0d364907fdcc10beeb8aabc78106 (patch)
tree49feb8753359b41a83e4bbde424ab09f4799838e /src/data
parentc220b6e1264822c6f8d97ab216a319261106e614 (diff)
parent43233c4f76c5a96c08dcb033372294fbafbaf663 (diff)
Merge tag 'v2.0' into debian/sid
Diffstat (limited to 'src/data')
-rw-r--r--src/data/edge.cpp424
-rw-r--r--src/data/edge.h130
-rw-r--r--src/data/graph.cpp383
-rw-r--r--src/data/graph.h130
-rw-r--r--src/data/graphelementdata.cpp264
-rw-r--r--src/data/graphelementdata.h84
-rw-r--r--src/data/graphelementproperty.cpp73
-rw-r--r--src/data/graphelementproperty.h58
-rw-r--r--src/data/node.cpp123
-rw-r--r--src/data/node.h74
-rw-r--r--src/data/style.cpp273
-rw-r--r--src/data/style.h82
-rw-r--r--src/data/stylelist.cpp163
-rw-r--r--src/data/stylelist.h47
-rw-r--r--src/data/tikzassembler.cpp72
-rw-r--r--src/data/tikzassembler.h60
-rw-r--r--src/data/tikzdocument.cpp213
-rw-r--r--src/data/tikzdocument.h69
-rw-r--r--src/data/tikzlexer.l193
-rw-r--r--src/data/tikzparser.y284
-rw-r--r--src/data/tikzparserdefs.h40
-rw-r--r--src/data/tikzstyles.cpp181
-rw-r--r--src/data/tikzstyles.h65
23 files changed, 3485 insertions, 0 deletions
diff --git a/src/data/edge.cpp b/src/data/edge.cpp
new file mode 100644
index 0000000..0ae566b
--- /dev/null
+++ b/src/data/edge.cpp
@@ -0,0 +1,424 @@
+/*
+ TikZiT - a GUI diagram editor for TikZ
+ Copyright (C) 2018 Aleks Kissinger
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+*/
+
+#include "edge.h"
+#include "tikzit.h"
+#include "util.h"
+
+#include <QDebug>
+#include <QPointF>
+
+Edge::Edge(Node *s, Node *t, QObject *parent) :
+ QObject(parent), _source(s), _target(t)
+{
+ _data = new GraphElementData();
+ _edgeNode = 0;
+ _dirty = true;
+
+ if (s != t) {
+ _basicBendMode = true;
+ _bend = 0;
+ _inAngle = 0;
+ _outAngle = 0;
+ _weight = 0.4f;
+ } else {
+ _basicBendMode = false;
+ _bend = 0;
+ _inAngle = 135;
+ _outAngle = 45;
+ _weight = 1.0f;
+ }
+ _style = noneEdgeStyle;
+ updateControls();
+}
+
+Edge::~Edge()
+{
+ delete _data;
+ delete _edgeNode;
+}
+
+/*!
+ * @brief Edge::copy makes a deep copy of an edge.
+ * @param nodeTable is an optional pointer to a table mapping the old source/target
+ * node pointers to their new, copied versions. This is used when making a copy of
+ * an entire (sub)graph.
+ * @return a copy of the edge
+ */
+Edge *Edge::copy(QMap<Node*,Node*> *nodeTable)
+{
+ Edge *e;
+ if (nodeTable == 0) e = new Edge(_source, _target);
+ else e = new Edge(nodeTable->value(_source), nodeTable->value(_target));
+ e->setData(_data->copy());
+ e->setBasicBendMode(_basicBendMode);
+ e->setBend(_bend);
+ e->setInAngle(_inAngle);
+ e->setOutAngle(_outAngle);
+ e->setWeight(_weight);
+ e->attachStyle();
+ e->updateControls();
+ return e;
+}
+
+Node *Edge::source() const
+{
+ return _source;
+}
+
+Node *Edge::target() const
+{
+ return _target;
+}
+
+bool Edge::isSelfLoop()
+{
+ return (_source == _target);
+}
+
+bool Edge::isStraight()
+{
+ return (_basicBendMode && _bend == 0);
+}
+
+GraphElementData *Edge::data() const
+{
+ return _data;
+}
+
+void Edge::setData(GraphElementData *data)
+{
+ delete _data;
+ _data = data;
+ setAttributesFromData();
+}
+
+QString Edge::styleName() const
+{
+ QString nm = _data->property("style");
+ if (nm.isNull()) return "none";
+ else return nm;
+}
+
+void Edge::setStyleName(const QString &styleName)
+{
+ if (!styleName.isNull() && styleName != "none") _data->setProperty("style", styleName);
+ else _data->unsetProperty("style");
+}
+
+QString Edge::sourceAnchor() const
+{
+ return _sourceAnchor;
+}
+
+void Edge::setSourceAnchor(const QString &sourceAnchor)
+{
+ _sourceAnchor = sourceAnchor;
+}
+
+QString Edge::targetAnchor() const
+{
+ return _targetAnchor;
+}
+
+void Edge::setTargetAnchor(const QString &targetAnchor)
+{
+ _targetAnchor = targetAnchor;
+}
+
+Node *Edge::edgeNode() const
+{
+ return _edgeNode;
+}
+
+void Edge::setEdgeNode(Node *edgeNode)
+{
+ if (_edgeNode != 0) delete _edgeNode;
+ _edgeNode = edgeNode;
+}
+
+bool Edge::hasEdgeNode()
+{
+ return _edgeNode != 0;
+}
+
+void Edge::updateControls() {
+ //if (_dirty) {
+ QPointF src = _source->point();
+ QPointF targ = _target->point();
+
+ float dx = (targ.x() - src.x());
+ float dy = (targ.y() - src.y());
+
+ float outAngleR = 0.0f;
+ float inAngleR = 0.0f;
+
+ if (_basicBendMode) {
+ float angle = std::atan2(dy, dx);
+ float bnd = (float)_bend * (M_PI / 180.0f);
+ outAngleR = angle - bnd;
+ inAngleR = M_PI + angle + bnd;
+ _outAngle = outAngleR * (180.f / M_PI);
+ _inAngle = inAngleR * (180.f / M_PI);
+ } else {
+ outAngleR = (float)_outAngle * (M_PI / 180.0f);
+ inAngleR = (float)_inAngle * (M_PI / 180.0f);
+ }
+
+ // TODO: calculate head and tail properly, not just for circles
+ if (_source->style()->isNone()) {
+ _tail = src;
+ } else {
+ _tail = QPointF(src.x() + std::cos(outAngleR) * 0.2,
+ src.y() + std::sin(outAngleR) * 0.2);
+ }
+
+ if (_target->style()->isNone()) {
+ _head = targ;
+ } else {
+ _head = QPointF(targ.x() + std::cos(inAngleR) * 0.2,
+ targ.y() + std::sin(inAngleR) * 0.2);
+ }
+
+ // give a default distance for self-loops
+ _cpDist = (dx==0.0f && dy==0.0f) ? _weight : std::sqrt(dx*dx + dy*dy) * _weight;
+
+ _cp1 = QPointF(src.x() + (_cpDist * std::cos(outAngleR)),
+ src.y() + (_cpDist * std::sin(outAngleR)));
+
+ _cp2 = QPointF(targ.x() + (_cpDist * std::cos(inAngleR)),
+ targ.y() + (_cpDist * std::sin(inAngleR)));
+
+ _mid = bezierInterpolateFull (0.5f, _tail, _cp1, _cp2, _head);
+ _tailTangent = bezierTangent(0.0f, 0.1f);
+ _headTangent = bezierTangent(1.0f, 0.9f);
+}
+
+void Edge::setAttributesFromData()
+{
+ _basicBendMode = true;
+ bool ok = true;
+
+ if (_data->atom("bend left")) {
+ _bend = -30;
+ } else if (_data->atom("bend right")) {
+ _bend = 30;
+ } else if (_data->property("bend left") != 0) {
+ _bend = -_data->property("bend left").toInt(&ok);
+ if (!ok) _bend = -30;
+ } else if (_data->property("bend right") != 0) {
+ _bend = _data->property("bend right").toInt(&ok);
+ if (!ok) _bend = 30;
+ } else {
+ _bend = 0;
+
+ if (_data->property("in") != 0 && _data->property("out") != 0) {
+ _basicBendMode = false;
+ _inAngle = _data->property("in").toInt(&ok);
+ if (!ok) _inAngle = 0;
+ _outAngle = _data->property("out").toInt(&ok);
+ if (!ok) _outAngle = 180;
+ }
+ }
+
+ if (!_data->property("looseness").isNull()) {
+ _weight = _data->property("looseness").toFloat(&ok) / 2.5f;
+ if (!ok) _weight = 0.4f;
+ } else {
+ _weight = (isSelfLoop()) ? 1.0f : 0.4f;
+ }
+
+ //qDebug() << "bend: " << _bend << " in: " << _inAngle << " out: " << _outAngle;
+ _dirty = true;
+}
+
+void Edge::updateData()
+{
+ _data->unsetAtom("loop");
+ _data->unsetProperty("in");
+ _data->unsetProperty("out");
+ _data->unsetAtom("bend left");
+ _data->unsetAtom("bend right");
+ _data->unsetProperty("bend left");
+ _data->unsetProperty("bend right");
+ _data->unsetProperty("looseness");
+
+ // TODO: style handling?
+
+ if (_basicBendMode) {
+ if (_bend != 0) {
+ QString bendKey;
+ int b;
+ if (_bend < 0) {
+ bendKey = "bend left";
+ b = -_bend;
+ } else {
+ bendKey = "bend right";
+ b = _bend;
+ }
+
+ if (b == 30) {
+ _data->setAtom(bendKey);
+ } else {
+ _data->setProperty(bendKey, QString::number(b));
+ }
+ }
+ } else {
+ _data->setProperty("in", QString::number(_inAngle));
+ _data->setProperty("out", QString::number(_outAngle));
+ }
+
+ if (_source == _target) _data->setAtom("loop");
+ if (!isSelfLoop() && !isStraight() && _weight != 0.4f)
+ _data->setProperty("looseness", QString::number(_weight*2.5f, 'f', 2));
+ if (_source->isBlankNode()) _sourceAnchor = "center";
+ else _sourceAnchor = "";
+ if (_target->isBlankNode()) _targetAnchor = "center";
+ else _targetAnchor = "";
+
+}
+
+
+QPointF Edge::head() const
+{
+ return _head;
+}
+
+QPointF Edge::tail() const
+{
+ return _tail;
+}
+
+QPointF Edge::cp1() const
+{
+ return _cp1;
+}
+
+QPointF Edge::cp2() const
+{
+ return _cp2;
+}
+
+int Edge::bend() const
+{
+ return _bend;
+}
+
+int Edge::inAngle() const
+{
+ return _inAngle;
+}
+
+int Edge::outAngle() const
+{
+ return _outAngle;
+}
+
+float Edge::weight() const
+{
+ return _weight;
+}
+
+bool Edge::basicBendMode() const
+{
+ return _basicBendMode;
+}
+
+float Edge::cpDist() const
+{
+ return _cpDist;
+}
+
+void Edge::setBasicBendMode(bool mode)
+{
+ _basicBendMode = mode;
+}
+
+void Edge::setBend(int bend)
+{
+ _bend = bend;
+}
+
+void Edge::setInAngle(int inAngle)
+{
+ _inAngle = inAngle;
+}
+
+void Edge::setOutAngle(int outAngle)
+{
+ _outAngle = outAngle;
+}
+
+void Edge::setWeight(float weight)
+{
+ _weight = weight;
+}
+
+int Edge::tikzLine() const
+{
+ return _tikzLine;
+}
+
+void Edge::setTikzLine(int tikzLine)
+{
+ _tikzLine = tikzLine;
+}
+
+QPointF Edge::mid() const
+{
+ return _mid;
+}
+
+QPointF Edge::headTangent() const
+{
+ return _headTangent;
+}
+
+QPointF Edge::tailTangent() const
+{
+ return _tailTangent;
+}
+
+void Edge::attachStyle()
+{
+ QString nm = styleName();
+ if (nm.isNull()) _style = noneEdgeStyle;
+ else _style = tikzit->styles()->edgeStyle(nm);
+}
+
+Style *Edge::style() const
+{
+ return _style;
+}
+
+QPointF Edge::bezierTangent(float start, float end) const
+{
+ float dx = bezierInterpolate(end, _tail.x(), _cp1.x(), _cp2.x(), _head.x()) -
+ bezierInterpolate(start, _tail.x(), _cp1.x(), _cp2.x(), _head.x());
+ float dy = bezierInterpolate(end, _tail.y(), _cp1.y(), _cp2.y(), _head.y()) -
+ bezierInterpolate(start, _tail.y(), _cp1.y(), _cp2.y(), _head.y());
+
+ // normalise
+ float len = sqrt(dx*dx + dy * dy);
+ if (len != 0) {
+ dx = (dx / len) * 0.1f;
+ dy = (dy / len) * 0.1f;
+ }
+
+ return QPointF(dx, dy);
+}
diff --git a/src/data/edge.h b/src/data/edge.h
new file mode 100644
index 0000000..ad71364
--- /dev/null
+++ b/src/data/edge.h
@@ -0,0 +1,130 @@
+/*
+ TikZiT - a GUI diagram editor for TikZ
+ Copyright (C) 2018 Aleks Kissinger
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+*/
+
+#ifndef EDGE_H
+#define EDGE_H
+
+#include "graphelementdata.h"
+#include "node.h"
+#include "style.h"
+
+#include <QObject>
+#include <QPointF>
+
+class Edge : public QObject
+{
+ Q_OBJECT
+public:
+ explicit Edge(Node *s, Node *t, QObject *parent = 0);
+ ~Edge();
+ Edge *copy(QMap<Node *, Node *> *nodeTable = 0);
+
+ Node *source() const;
+ Node *target() const;
+
+ bool isSelfLoop();
+ bool isStraight();
+
+ GraphElementData *data() const;
+ void setData(GraphElementData *data);
+
+ QString sourceAnchor() const;
+ void setSourceAnchor(const QString &sourceAnchor);
+
+ QString targetAnchor() const;
+ void setTargetAnchor(const QString &targetAnchor);
+
+ Node *edgeNode() const;
+ void setEdgeNode(Node *edgeNode);
+ bool hasEdgeNode();
+
+ void updateControls();
+ void setAttributesFromData();
+ void updateData();
+
+ QPointF head() const;
+ QPointF tail() const;
+ QPointF cp1() const;
+ QPointF cp2() const;
+ QPointF mid() const;
+ QPointF headTangent() const;
+ QPointF tailTangent() const;
+
+ int bend() const;
+ int inAngle() const;
+ int outAngle() const;
+ float weight() const;
+ bool basicBendMode() const;
+ float cpDist() const;
+
+ void setBasicBendMode(bool mode);
+ void setBend(int bend);
+ void setInAngle(int inAngle);
+ void setOutAngle(int outAngle);
+ void setWeight(float weight);
+
+ int tikzLine() const;
+ void setTikzLine(int tikzLine);
+
+
+ void attachStyle();
+ QString styleName() const;
+ void setStyleName(const QString & styleName);
+ Style *style() const;
+
+signals:
+
+public slots:
+
+private:
+ QPointF bezierTangent(float start, float end) const;
+ QString _sourceAnchor;
+ QString _targetAnchor;
+
+ // owned
+ Node *_edgeNode;
+ GraphElementData *_data;
+
+ // referenced
+ Node *_source;
+ Node *_target;
+
+
+ Style *_style;
+
+ bool _dirty;
+ bool _basicBendMode;
+ int _bend;
+ int _inAngle;
+ int _outAngle;
+ float _weight;
+ float _cpDist;
+
+ QPointF _head;
+ QPointF _tail;
+ QPointF _cp1;
+ QPointF _cp2;
+ QPointF _mid;
+
+ QPointF _headTangent;
+ QPointF _tailTangent;
+
+ int _tikzLine;
+};
+
+#endif // EDGE_H
diff --git a/src/data/graph.cpp b/src/data/graph.cpp
new file mode 100644
index 0000000..bba2061
--- /dev/null
+++ b/src/data/graph.cpp
@@ -0,0 +1,383 @@
+/*
+ TikZiT - a GUI diagram editor for TikZ
+ Copyright (C) 2018 Aleks Kissinger
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+*/
+
+#include "graph.h"
+#include "util.h"
+
+#include <QTextStream>
+#include <QSet>
+#include <QtAlgorithms>
+#include <QDebug>
+#include <algorithm>
+
+Graph::Graph(QObject *parent) : QObject(parent)
+{
+ _data = new GraphElementData(this);
+ _bbox = QRectF(0,0,0,0);
+}
+
+Graph::~Graph()
+{
+}
+
+// add a node. The graph claims ownership.
+void Graph::addNode(Node *n) {
+ n->setParent(this);
+ _nodes << n;
+}
+
+void Graph::addNode(Node *n, int index)
+{
+ n->setParent(this);
+ _nodes.insert(index, n);
+}
+
+void Graph::removeNode(Node *n) {
+ // the node itself is not deleted, as it may still be referenced in an undo command. It will
+ // be deleted when graph is, via QObject memory management.
+ _nodes.removeOne(n);
+}
+
+
+void Graph::addEdge(Edge *e)
+{
+ e->setParent(this);
+ _edges << e;
+}
+
+void Graph::addEdge(Edge *e, int index)
+{
+ e->setParent(this);
+ _edges.insert(index, e);
+}
+
+void Graph::removeEdge(Edge *e)
+{
+ // the edge itself is not deleted, as it may still be referenced in an undo command. It will
+ // be deleted when graph is, via QObject memory management.
+ _edges.removeOne(e);
+}
+
+int Graph::maxIntName()
+{
+ int max = -1;
+ int i;
+ bool ok;
+ foreach (Node *n, _nodes) {
+ i = n->name().toInt(&ok);
+ if (ok && i > max) max = i;
+ }
+ return max;
+}
+
+void Graph::reorderNodes(const QVector<Node *> &newOrder)
+{
+ _nodes = newOrder;
+}
+
+void Graph::reorderEdges(const QVector<Edge *> &newOrder)
+{
+ _edges = newOrder;
+}
+
+QRectF Graph::realBbox()
+{
+ //float maxX = 0.0f;
+ QRectF rect = bbox();
+ foreach (Node *n, _nodes) {
+ rect = rect.united(QRectF(n->point().x()-0.5f,
+ n->point().y()-0.5f,
+ 1.0f, 1.0f));
+ }
+
+ return rect;
+}
+
+QRectF Graph::boundsForNodes(QSet<Node*>nds) {
+ QPointF p;
+ QPointF tl;
+ QPointF br;
+ bool hasPoints = false;
+ foreach (Node *n, nds) {
+ p = n->point();
+ if (!hasPoints) {
+ hasPoints = true;
+ tl = p;
+ br = p;
+ } else {
+ if (p.x() < tl.x()) tl.setX(p.x());
+ if (p.y() > tl.y()) tl.setY(p.y());
+ if (p.x() > br.x()) br.setX(p.x());
+ if (p.y() < br.y()) br.setY(p.y());
+ }
+ }
+
+ QRectF rect(tl, br);
+ return rect;
+}
+
+QString Graph::freshNodeName()
+{
+ return QString::number(maxIntName() + 1);
+}
+
+void Graph::renameApart(Graph *graph)
+{
+ int i = graph->maxIntName() + 1;
+ foreach (Node *n, _nodes) {
+ n->setName(QString::number(i));
+ i++;
+ }
+}
+
+GraphElementData *Graph::data() const
+{
+ return _data;
+}
+
+void Graph::setData(GraphElementData *data)
+{
+ delete _data;
+ _data = data;
+}
+
+const QVector<Node*> &Graph::nodes()
+{
+ return _nodes;
+}
+
+const QVector<Edge*> &Graph::edges()
+{
+ return _edges;
+}
+
+QRectF Graph::bbox() const
+{
+ return _bbox;
+}
+
+bool Graph::hasBbox() {
+ return !(_bbox == QRectF(0,0,0,0));
+}
+
+void Graph::clearBbox() {
+ _bbox = QRectF(0,0,0,0);
+}
+
+QString Graph::tikz()
+{
+ QString str;
+ QTextStream code(&str);
+ int line = 0;
+
+ code << "\\begin{tikzpicture}" << _data->tikz() << "\n";
+ line++;
+ if (hasBbox()) {
+ code << "\t\\path [use as bounding box] ("
+ << _bbox.topLeft().x() << "," << _bbox.topLeft().y()
+ << ") rectangle ("
+ << _bbox.bottomRight().x() << "," << _bbox.bottomRight().y()
+ << ");\n";
+ line++;
+ }
+
+ if (!_nodes.isEmpty()) {
+ code << "\t\\begin{pgfonlayer}{nodelayer}\n";
+ line++;
+ }
+
+ Node *n;
+ foreach (n, _nodes) {
+ n->setTikzLine(line);
+ code << "\t\t\\node ";
+
+ if (!n->data()->isEmpty())
+ code << n->data()->tikz() << " ";
+
+ code << "(" << n->name() << ") at ("
+ << floatToString(n->point().x())
+ << ", "
+ << floatToString(n->point().y())
+ << ") {" << n->label() << "};\n";
+ line++;
+ }
+
+ if (!_nodes.isEmpty()) {
+ code << "\t\\end{pgfonlayer}\n";
+ line++;
+ }
+
+ if (!_edges.isEmpty()) {
+ code << "\t\\begin{pgfonlayer}{edgelayer}\n";
+ line++;
+ }
+
+
+ Edge *e;
+ 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() << "} ";
+ }
+
+ if (e->source() == e->target()) {
+ code << "()";
+ } else {
+ code << "(" << e->target()->name();
+ if (e->targetAnchor() != "")
+ code << "." << e->targetAnchor();
+ code << ")";
+ }
+
+ code << ";\n";
+ line++;
+ }
+
+ if (!_edges.isEmpty()) {
+ code << "\t\\end{pgfonlayer}\n";
+ line++;
+ }
+
+ code << "\\end{tikzpicture}\n";
+ line++;
+
+ code.flush();
+ return str;
+}
+
+Graph *Graph::copyOfSubgraphWithNodes(QSet<Node *> nds)
+{
+ Graph *g = new Graph();
+ g->setData(_data->copy());
+ QMap<Node*,Node*> nodeTable;
+ foreach (Node *n, nodes()) {
+ if (nds.contains(n)) {
+ Node *n1 = n->copy();
+ nodeTable.insert(n, n1);
+ g->addNode(n1);
+ }
+ }
+ foreach (Edge *e, edges()) {
+ if (nds.contains(e->source()) && nds.contains(e->target())) {
+ g->addEdge(e->copy(&nodeTable));
+ }
+ }
+
+ return g;
+}
+
+void Graph::insertGraph(Graph *graph)
+{
+ QMap<Node*,Node*> nodeTable;
+ foreach (Node *n, graph->nodes()) addNode(n);
+ foreach (Edge *e, graph->edges()) addEdge(e);
+}
+
+void Graph::reflectNodes(QSet<Node*> nds, bool horizontal)
+{
+ QRectF bds = boundsForNodes(nds);
+ float ctr;
+ if (horizontal) ctr = bds.center().x();
+ else ctr = bds.center().y();
+
+ QPointF p;
+ foreach(Node *n, nds) {
+ p = n->point();
+ if (horizontal) p.setX(2 * ctr - p.x());
+ else p.setY(2 * ctr - p.y());
+ n->setPoint(p);
+ }
+
+ foreach (Edge *e, _edges) {
+ if (nds.contains(e->source()) && nds.contains(e->target())) {
+ if (!e->basicBendMode()) {
+ if (horizontal) {
+ if (e->inAngle() < 0) e->setInAngle(-180 - e->inAngle());
+ else e->setInAngle(180 - e->inAngle());
+
+ if (e->outAngle() < 0) e->setOutAngle(-180 - e->outAngle());
+ else e->setOutAngle(180 - e->outAngle());
+ }
+ else {
+ e->setInAngle(-e->inAngle());
+ e->setOutAngle(-e->outAngle());
+ }
+ }
+ else {
+ e->setBend(-e->bend());
+ }
+ }
+ }
+}
+
+void Graph::rotateNodes(QSet<Node*> nds, bool clockwise)
+{
+ //QRectF bds = boundsForNodes(nds);
+ // QPointF ctr = bds.center();
+ // ctr.setX((float)floor(ctr.x() * 4.0f) / 4.0f);
+ // ctr.setY((float)floor(ctr.y() * 4.0f) / 4.0f);
+ float sign = (clockwise) ? 1.0f : -1.0f;
+
+ QPointF p;
+ // float dx, dy;
+ foreach(Node *n, nds) {
+ p = n->point();
+ // dx = p.x() - ctr.x();
+ // dy = p.y() - ctr.y();
+ n->setPoint(QPointF(sign * p.y(), -sign * p.x()));
+ }
+
+ int newIn, newOut;
+ foreach (Edge *e, _edges) {
+ if (nds.contains(e->source()) && nds.contains(e->target())) {
+ // update angles if necessary. Note that "basic" bends are computed based
+ // on node position, so they don't need to be updated.
+ if (!e->basicBendMode()) {
+ newIn = e->inAngle() - sign * 90;
+ newOut = e->outAngle() - sign * 90;
+
+ // normalise the angle to be within (-180,180]
+ if (newIn > 180) newIn -= 360;
+ else if (newIn <= -180) newIn += 360;
+ if (newOut > 180) newOut -= 360;
+ else if (newOut <= -180) newOut += 360;
+ e->setInAngle(newIn);
+ e->setOutAngle(newOut);
+ }
+ }
+ }
+}
+
+void Graph::setBbox(const QRectF &bbox)
+{
+ _bbox = bbox;
+}
diff --git a/src/data/graph.h b/src/data/graph.h
new file mode 100644
index 0000000..286ccdc
--- /dev/null
+++ b/src/data/graph.h
@@ -0,0 +1,130 @@
+/*
+ TikZiT - a GUI diagram editor for TikZ
+ Copyright (C) 2018 Aleks Kissinger
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+*/
+
+/*!
+ * A graph defined by tikz code.
+ */
+
+#ifndef GRAPH_H
+#define GRAPH_H
+
+#include "node.h"
+#include "edge.h"
+#include "graphelementdata.h"
+
+#include <QObject>
+#include <QVector>
+#include <QMultiHash>
+#include <QRectF>
+#include <QString>
+#include <QMap>
+
+class Graph : public QObject
+{
+ Q_OBJECT
+public:
+ explicit Graph(QObject *parent = 0);
+ ~Graph();
+ void addNode(Node *n);
+ void addNode(Node *n, int index);
+ void removeNode(Node *n);
+ void addEdge(Edge *e);
+ void addEdge(Edge *e, int index);
+ void removeEdge(Edge *e);
+ int maxIntName();
+ void reorderNodes(const QVector<Node*> &newOrder);
+ void reorderEdges(const QVector<Edge*> &newOrder);
+ QRectF boundsForNodes(QSet<Node*> ns);
+ QString freshNodeName();
+
+ /*!
+ * \brief renameApart assigns fresh names to all of the nodes in "this",
+ * with respect to the given graph
+ * \param graph
+ */
+ void renameApart(Graph *graph);
+
+ GraphElementData *data() const;
+ void setData(GraphElementData *data);
+
+ const QVector<Node *> &nodes();
+ const QVector<Edge*> &edges();
+
+ QRectF bbox() const;
+ void setBbox(const QRectF &bbox);
+ bool hasBbox();
+ void clearBbox();
+
+ /*!
+ * \brief realBbox computes the union of the user-defined
+ * bounding box, and the bounding boxes of the graph's
+ * contents.
+ *
+ * \return
+ */
+ QRectF realBbox();
+
+ QString tikz();
+
+ /*!
+ * \brief copyOfSubgraphWithNodes produces a copy of the full subgraph
+ * with the given nodes. Used for cutting and copying to clipboard.
+ * \param nds
+ * \return
+ */
+ Graph *copyOfSubgraphWithNodes(QSet<Node*> nds);
+
+ /*!
+ * \brief insertGraph inserts the given graph into "this". Prior to calling this
+ * method, the node names in the given graph should be made fresh via
+ * "renameApart". Note that the parameter "graph" relinquishes ownership of its
+ * nodes and edges, so it should be not be allowed to exist longer than "this".
+ * \param graph
+ */
+ void insertGraph(Graph *graph);
+
+ /*!
+ * \brief reflectNodes flips the given set of nodes horizontally or vertically,
+ * depending on the value of the second parameter.
+ * \param nds a set of nodes to flip
+ * \param horizontal a boolean determining whether to flip horizontally or
+ * vertically
+ */
+ void reflectNodes(QSet<Node*> nds, bool horizontal);
+
+ /*!
+ * \brief rotateNodes rotates the given set of nodes clockwise or counter-clockwise,
+ * depending on the value of the second parameter.
+ * \param nds a set of nodes to flip
+ * \param clockwose a boolean determining whether to rotate clockwise or counter-clockwise
+ */
+ void rotateNodes(QSet<Node*> nds, bool clockwise);
+signals:
+
+public slots:
+
+private:
+ QVector<Node*> _nodes;
+ QVector<Edge*> _edges;
+ //QMultiHash<Node*,Edge*> inEdges;
+ //QMultiHash<Node*,Edge*> outEdges;
+ GraphElementData *_data;
+ QRectF _bbox;
+};
+
+#endif // GRAPH_H
diff --git a/src/data/graphelementdata.cpp b/src/data/graphelementdata.cpp
new file mode 100644
index 0000000..810ebd6
--- /dev/null
+++ b/src/data/graphelementdata.cpp
@@ -0,0 +1,264 @@
+/*
+ TikZiT - a GUI diagram editor for TikZ
+ Copyright (C) 2018 Aleks Kissinger
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+*/
+
+#include "graphelementdata.h"
+
+#include <QDebug>
+#include <QTextStream>
+
+GraphElementData::GraphElementData(QVector<GraphElementProperty> init, QObject *parent) : QAbstractItemModel(parent)
+{
+ root = new GraphElementProperty();
+ _properties = init;
+}
+
+GraphElementData::GraphElementData(QObject *parent) : QAbstractItemModel(parent) {
+ root = new GraphElementProperty();
+}
+
+GraphElementData::~GraphElementData()
+{
+ delete root;
+}
+
+GraphElementData *GraphElementData::copy()
+{
+ return new GraphElementData(_properties);
+}
+
+void GraphElementData::setProperty(QString key, QString value)
+{
+ int i = indexOfKey(key);
+ if (i != -1) {
+ _properties[i].setValue(value);
+ } else {
+ GraphElementProperty p(key, value);
+ _properties << p;
+ }
+}
+
+void GraphElementData::unsetProperty(QString key)
+{
+ int i = indexOfKey(key);
+ if (i != -1)
+ _properties.remove(i);
+}
+
+void GraphElementData::add(GraphElementProperty p)
+{
+ int i = _properties.size();
+ beginInsertRows(QModelIndex(), i, i);
+ _properties << p;
+ endInsertRows();
+}
+
+void GraphElementData::operator <<(GraphElementProperty p)
+{
+ add(p);
+}
+
+void GraphElementData::setAtom(QString atom)
+{
+ int i = indexOfKey(atom);
+ if (i == -1)
+ _properties << GraphElementProperty(atom);
+}
+
+void GraphElementData::unsetAtom(QString atom)
+{
+ int i = indexOfKey(atom);
+ if (i != -1)
+ _properties.remove(i);
+}
+
+QString GraphElementData::property(QString key)
+{
+ int i = indexOfKey(key);
+ if (i != -1) {
+ return _properties[i].value();
+ } else {
+ return QString(); // null QString
+ }
+}
+
+bool GraphElementData::hasProperty(QString key)
+{
+ return (indexOfKey(key) != -1);
+}
+
+bool GraphElementData::atom(QString atom)
+{
+ return (indexOfKey(atom) != -1);
+}
+
+int GraphElementData::indexOfKey(QString key)
+{
+ for (int i = 0; i < _properties.size(); ++i) {
+ QString key1 = _properties[i].key();
+ if (key1 == key) return i;
+ }
+ return -1;
+}
+
+bool GraphElementData::removeRows(int row, int /*count*/, const QModelIndex &parent)
+{
+ if (row >= 0 && row < _properties.length()) {
+ beginRemoveRows(parent, row, row+1);
+ _properties.remove(row);
+ endRemoveRows();
+ return true;
+ } else {
+ return false;
+ }
+}
+
+bool GraphElementData::moveRows(const QModelIndex &sourceParent,
+ int sourceRow,
+ int /*count*/,
+ const QModelIndex &destinationParent,
+ int destinationRow)
+{
+ if (sourceRow >= 0 && sourceRow < _properties.length() &&
+ destinationRow >= 0 && destinationRow <= _properties.length())
+ {
+ beginMoveRows(sourceParent, sourceRow, sourceRow, destinationParent, destinationRow);
+ GraphElementProperty p = _properties[sourceRow];
+ _properties.remove(sourceRow);
+ if (sourceRow < destinationRow) {
+ _properties.insert(destinationRow - 1, p);
+ } else {
+ _properties.insert(destinationRow, p);
+ }
+ endMoveRows();
+ return true;
+ } else {
+ return false;
+ }
+}
+
+QVariant GraphElementData::data(const QModelIndex &index, int role) const
+{
+ if (role == Qt::DisplayRole || role == Qt::EditRole) {
+ if (index.row() >= 0 && index.row() < _properties.length()) {
+ const GraphElementProperty &p = _properties[index.row()];
+ QString s = (index.column() == 0) ? p.key() : p.value();
+ return QVariant(s);
+ }
+ }
+
+ return QVariant();
+}
+
+QVariant GraphElementData::headerData(int section, Qt::Orientation orientation, int role) const
+{
+ if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
+ if (section == 0) return QVariant("Key/Atom");
+ else return QVariant("Value");
+ }
+
+ return QVariant();
+}
+
+QModelIndex GraphElementData::index(int row, int column, const QModelIndex &) const
+{
+ return createIndex(row, column, (void*)0);
+}
+
+QModelIndex GraphElementData::parent(const QModelIndex &) const
+{
+ // there is no nesting, so always return an invalid index
+ return QModelIndex();
+}
+
+int GraphElementData::rowCount(const QModelIndex &parent) const
+{
+ if (parent.isValid()) {
+ return 0;
+ } else {
+ return _properties.size();
+ }
+}
+
+int GraphElementData::columnCount(const QModelIndex &) const
+{
+ return 2;
+}
+
+Qt::ItemFlags GraphElementData::flags(const QModelIndex &index) const
+{
+ if (index.row() >= 0 && index.row() < _properties.length()) {
+ if (index.column() == 0 ||
+ (!_properties[index.row()].atom() && index.column() == 1))
+ {
+ return QAbstractItemModel::flags(index) | Qt::ItemIsEditable;
+ }
+ }
+ return QAbstractItemModel::flags(index);
+}
+
+bool GraphElementData::setData(const QModelIndex &index, const QVariant &value, int role)
+{
+ bool success = false;
+ if (index.row() >= 0 && index.row() < _properties.length()) {
+ if (index.column() == 0) {
+ _properties[index.row()].setKey(value.toString());
+ success = true;
+ } else if (index.column() == 1 && !_properties[index.row()].atom()) {
+ _properties[index.row()].setValue(value.toString());
+ success = true;
+ }
+ }
+
+ if (success) {
+ QVector<int> roles;
+ roles << role;
+ emit dataChanged(index, index, roles);
+ }
+
+ return success;
+}
+
+QString GraphElementData::tikz() {
+ if (_properties.length() == 0) return "";
+ QString str;
+ QTextStream code(&str);
+ code << "[";
+
+ GraphElementProperty p;
+ bool first = true;
+ foreach(p, _properties) {
+ if (!first) code << ", ";
+ code << p.tikz();
+ first = false;
+ }
+
+ code << "]";
+
+ code.flush();
+ return str;
+}
+
+bool GraphElementData::isEmpty()
+{
+ return _properties.isEmpty();
+}
+
+QVector<GraphElementProperty> GraphElementData::properties() const
+{
+ return _properties;
+}
diff --git a/src/data/graphelementdata.h b/src/data/graphelementdata.h
new file mode 100644
index 0000000..23f0466
--- /dev/null
+++ b/src/data/graphelementdata.h
@@ -0,0 +1,84 @@
+/*
+ TikZiT - a GUI diagram editor for TikZ
+ Copyright (C) 2018 Aleks Kissinger
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+*/
+
+#ifndef GRAPHELEMENTDATA_H
+#define GRAPHELEMENTDATA_H
+
+#include "graphelementproperty.h"
+
+#include <QAbstractItemModel>
+#include <QString>
+#include <QVariant>
+#include <QModelIndex>
+#include <QVector>
+
+class GraphElementData : public QAbstractItemModel
+{
+ Q_OBJECT
+public:
+ explicit GraphElementData(QVector<GraphElementProperty> init,
+ QObject *parent = 0);
+ explicit GraphElementData(QObject *parent = 0);
+ ~GraphElementData();
+ GraphElementData *copy();
+ void setProperty(QString key, QString value);
+ void unsetProperty(QString key);
+ void setAtom(QString atom);
+ void unsetAtom(QString atom);
+ QString property(QString key);
+ bool hasProperty(QString key);
+ bool atom(QString atom);
+ int indexOfKey(QString key);
+ bool removeRows(int row, int count, const QModelIndex &parent) override;
+ bool moveRows(const QModelIndex &sourceParent,
+ int sourceRow, int,
+ const QModelIndex &destinationParent,
+ int destinationRow) override;
+
+ QVariant data(const QModelIndex &index, int role) const override;
+ QVariant headerData(int section, Qt::Orientation orientation,
+ int role = Qt::DisplayRole) const override;
+
+ QModelIndex index(int row, int column, const QModelIndex &) const override;
+ QModelIndex parent(const QModelIndex &) const override;
+
+ int rowCount(const QModelIndex &parent = QModelIndex()) const override;
+ int columnCount(const QModelIndex &) const override;
+
+ Qt::ItemFlags flags(const QModelIndex &index) const override;
+
+ bool setData(const QModelIndex &index, const QVariant &value,
+ int role = Qt::EditRole) override;
+
+ void operator <<(GraphElementProperty p);
+ void add(GraphElementProperty p);
+
+ QString tikz();
+ bool isEmpty();
+ QVector<GraphElementProperty> properties() const;
+
+signals:
+
+public slots:
+
+private:
+ QVector<GraphElementProperty> _properties;
+ GraphElementProperty *root;
+};
+
+#endif // GRAPHELEMENTDATA_H
diff --git a/src/data/graphelementproperty.cpp b/src/data/graphelementproperty.cpp
new file mode 100644
index 0000000..aa1bfc8
--- /dev/null
+++ b/src/data/graphelementproperty.cpp
@@ -0,0 +1,73 @@
+/*
+ TikZiT - a GUI diagram editor for TikZ
+ Copyright (C) 2018 Aleks Kissinger
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+*/
+
+#include "graphelementproperty.h"
+
+#include <QRegExp>
+
+GraphElementProperty::GraphElementProperty ():
+ _key(""), _value(""), _atom(false)
+{}
+
+GraphElementProperty::GraphElementProperty(QString key, QString value, bool atom) :
+ _key(key), _value(value), _atom(atom)
+{}
+
+GraphElementProperty::GraphElementProperty(QString key, QString value) :
+ _key(key), _value(value), _atom(false)
+{}
+
+GraphElementProperty::GraphElementProperty(QString key) :
+ _key(key), _value(""), _atom(true)
+{}
+
+QString GraphElementProperty::key() const
+{ return _key; }
+
+QString GraphElementProperty::value() const
+{ return _value; }
+
+void GraphElementProperty::setValue(const QString &value)
+{ _value = value; }
+
+bool GraphElementProperty::atom() const
+{ return _atom; }
+
+
+bool GraphElementProperty::operator==(const GraphElementProperty &p)
+{
+ if (_atom) return p.atom() && p.key() == _key;
+ else return !p.atom() && p.key() == _key && p.value() == _value;
+}
+
+QString GraphElementProperty::tikzEscape(QString str)
+{
+ QRegExp re("[0-9a-zA-Z<> \\-'.]*");
+ if (re.exactMatch(str)) return str;
+ else return "{" + str + "}";
+}
+
+QString GraphElementProperty::tikz() {
+ if (_atom) return tikzEscape(_key);
+ return tikzEscape(_key) + "=" + tikzEscape(_value);
+}
+
+void GraphElementProperty::setKey(const QString &key)
+{
+ _key = key;
+}
diff --git a/src/data/graphelementproperty.h b/src/data/graphelementproperty.h
new file mode 100644
index 0000000..4ebe104
--- /dev/null
+++ b/src/data/graphelementproperty.h
@@ -0,0 +1,58 @@
+/*
+ TikZiT - a GUI diagram editor for TikZ
+ Copyright (C) 2018 Aleks Kissinger
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+*/
+
+#ifndef GRAPHELEMENTPROPERTY_H
+#define GRAPHELEMENTPROPERTY_H
+
+#include <QObject>
+
+class GraphElementProperty
+{
+public:
+ GraphElementProperty();
+
+ // full constructor
+ GraphElementProperty(QString key, QString value, bool atom);
+
+ // construct a proper property
+ GraphElementProperty(QString key, QString value);
+
+ // construct an atom
+ GraphElementProperty(QString key);
+
+ QString key() const;
+ void setKey(const QString &key);
+ QString value() const;
+ void setValue(const QString &value);
+ bool atom() const;
+ bool operator==(const GraphElementProperty &p);
+
+ static QString tikzEscape(QString str);
+ QString tikz();
+
+signals:
+
+public slots:
+
+private:
+ QString _key;
+ QString _value;
+ bool _atom;
+};
+
+#endif // GRAPHELEMENTPROPERTY_H
diff --git a/src/data/node.cpp b/src/data/node.cpp
new file mode 100644
index 0000000..75acd00
--- /dev/null
+++ b/src/data/node.cpp
@@ -0,0 +1,123 @@
+/*
+ TikZiT - a GUI diagram editor for TikZ
+ Copyright (C) 2018 Aleks Kissinger
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+*/
+
+#include "node.h"
+#include "tikzit.h"
+
+#include <QDebug>
+
+Node::Node(QObject *parent) : QObject(parent), _tikzLine(-1)
+{
+ _data = new GraphElementData();
+ _style = noneStyle;
+ _data->setProperty("style", "none");
+}
+
+Node::~Node()
+{
+ delete _data;
+}
+
+Node *Node::copy() {
+ Node *n1 = new Node();
+ n1->setName(name());
+ n1->setData(data()->copy());
+ n1->setPoint(point());
+ n1->setLabel(label());
+ n1->attachStyle();
+ n1->setTikzLine(tikzLine());
+ return n1;
+}
+
+QPointF Node::point() const
+{
+ return _point;
+}
+
+void Node::setPoint(const QPointF &point)
+{
+ _point = point;
+}
+
+QString Node::name() const
+{
+ return _name;
+}
+
+void Node::setName(const QString &name)
+{
+ _name = name;
+}
+
+QString Node::label() const
+{
+ return _label;
+}
+
+void Node::setLabel(const QString &label)
+{
+ _label = label;
+}
+
+GraphElementData *Node::data() const
+{
+ return _data;
+}
+
+void Node::setData(GraphElementData *data)
+{
+ delete _data;
+ _data = data;
+}
+
+QString Node::styleName() const
+{
+ return _data->property("style");
+}
+
+void Node::setStyleName(const QString &styleName)
+{
+ _data->setProperty("style", styleName);
+}
+
+void Node::attachStyle()
+{
+ QString nm = styleName();
+ if (nm == "none") _style = noneStyle;
+ else _style = tikzit->styles()->nodeStyle(nm);
+}
+
+Style *Node::style() const
+{
+ return _style;
+}
+
+bool Node::isBlankNode()
+{
+ return styleName() == "none";
+}
+
+int Node::tikzLine() const
+{
+ return _tikzLine;
+}
+
+void Node::setTikzLine(int tikzLine)
+{
+ _tikzLine = tikzLine;
+}
diff --git a/src/data/node.h b/src/data/node.h
new file mode 100644
index 0000000..490393d
--- /dev/null
+++ b/src/data/node.h
@@ -0,0 +1,74 @@
+/*
+ TikZiT - a GUI diagram editor for TikZ
+ Copyright (C) 2018 Aleks Kissinger
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+*/
+
+#ifndef NODE_H
+#define NODE_H
+
+#include "graphelementdata.h"
+#include "style.h"
+
+#include <QObject>
+#include <QPointF>
+#include <QString>
+
+class Node : public QObject
+{
+ Q_OBJECT
+public:
+ explicit Node(QObject *parent = 0);
+ ~Node();
+
+ Node *copy();
+
+ QPointF point() const;
+ void setPoint(const QPointF &point);
+
+ QString name() const;
+ void setName(const QString &name);
+
+ QString label() const;
+ void setLabel(const QString &label);
+
+ GraphElementData *data() const;
+ void setData(GraphElementData *data);
+
+ QString styleName() const;
+ void setStyleName(const QString &styleName);
+
+ void attachStyle();
+ Style *style() const;
+
+ bool isBlankNode();
+
+ int tikzLine() const;
+ void setTikzLine(int tikzLine);
+
+signals:
+
+public slots:
+
+private:
+ QPointF _point;
+ QString _name;
+ QString _label;
+ Style *_style;
+ GraphElementData *_data;
+ int _tikzLine;
+};
+
+#endif // NODE_H
diff --git a/src/data/style.cpp b/src/data/style.cpp
new file mode 100644
index 0000000..d0f011d
--- /dev/null
+++ b/src/data/style.cpp
@@ -0,0 +1,273 @@
+/*
+ TikZiT - a GUI diagram editor for TikZ
+ Copyright (C) 2018 Aleks Kissinger
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+*/
+
+#include "style.h"
+#include "tikzit.h"
+
+Style *noneStyle = new Style("none", new GraphElementData());
+Style *unknownStyle = new Style("unknown", new GraphElementData({GraphElementProperty("tikzit fill", "blue")}));
+Style *noneEdgeStyle = new Style("none", new GraphElementData({GraphElementProperty("-")}));
+
+Style::Style() : _name("none")
+{
+ _data = new GraphElementData(this);
+}
+
+Style::Style(QString name, GraphElementData *data) : _name(name), _data(data)
+{
+ _data->setParent(this);
+}
+
+bool Style::isNone() const
+{
+ return _name == "none";
+}
+
+GraphElementData *Style::data() const
+{
+ return _data;
+}
+
+QString Style::name() const
+{
+ return _name;
+}
+
+QColor Style::strokeColor(bool tikzitOverride) const
+{
+ QString col = propertyWithDefault("draw", "black", tikzitOverride);
+ return tikzit->colorByName(col);
+}
+
+QColor Style::fillColor(bool tikzitOverride) const
+{
+ QString col = propertyWithDefault("fill", "white", tikzitOverride);
+ return tikzit->colorByName(col);
+}
+
+QBrush Style::brush() const
+{
+ return QBrush(fillColor());
+}
+
+QString Style::shape(bool tikzitOverride) const
+{
+ return propertyWithDefault("shape", "circle", tikzitOverride);
+}
+
+
+// TODO
+int Style::strokeThickness() const
+{
+ return 1;
+}
+
+bool Style::isEdgeStyle() const
+{
+ if (_data->atom("-") || _data->atom("->") || _data->atom("-|") ||
+ _data->atom("<-") || _data->atom("<->") || _data->atom("<-|") ||
+ _data->atom("|-") || _data->atom("|->") || _data->atom("|-|")) return true;
+ else return false;
+}
+
+
+
+QString Style::propertyWithDefault(QString prop, QString def, bool tikzitOverride) const
+{
+ if (_data == 0) return def;
+ QString val;
+ if (tikzitOverride) {
+ val = _data->property("tikzit " + prop);
+ if (val.isNull()) val = _data->property(prop);
+ } else {
+ val = _data->property(prop);
+ }
+ if (val.isNull()) val = def;
+ return val;
+}
+
+QString Style::tikz() const
+{
+ return "\\tikzstyle{" + _name + "}=" + _data->tikz();
+}
+
+void Style::setArrowAtom(QString atom)
+{
+ _data->unsetAtom("-");
+ _data->unsetAtom("->");
+ _data->unsetAtom("-|");
+
+ _data->unsetAtom("<-");
+ _data->unsetAtom("<->");
+ _data->unsetAtom("<-|");
+
+ _data->unsetAtom("|-");
+ _data->unsetAtom("|->");
+ _data->unsetAtom("|-|");
+
+ _data->setAtom(atom);
+}
+
+void Style::setName(const QString &name)
+{
+ _name = name;
+}
+
+Style::ArrowTipStyle Style::arrowHead() const
+{
+ if (_data->atom("->") || _data->atom("<->") || _data->atom("|->")) return Pointer;
+ if (_data->atom("-|") || _data->atom("<-|") || _data->atom("|-|")) return Flat;
+ return NoTip;
+}
+
+Style::ArrowTipStyle Style::arrowTail() const
+{
+ if (_data->atom("<-") || _data->atom("<->") || _data->atom("<-|")) return Pointer;
+ if (_data->atom("|-") || _data->atom("|->") || _data->atom("|-|")) return Flat;
+ return NoTip;
+}
+
+Style::DrawStyle Style::drawStyle() const
+{
+ if (_data->atom("dashed")) return Dashed;
+ if (_data->atom("dotted")) return Dotted;
+ return Solid;
+}
+
+
+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;
+ }
+
+ return p;
+}
+
+QPainterPath Style::path() const
+{
+ QPainterPath pth;
+ QString sh = shape();
+
+ if (sh == "rectangle") {
+ pth.addRect(-30.0f, -30.0f, 60.0f, 60.0f);
+ } else { // default is 'circle'
+ pth.addEllipse(QPointF(0.0f,0.0f), 30.0f, 30.0f);
+ }
+ return pth;
+}
+
+QIcon Style::icon() const
+{
+ if (!isEdgeStyle()) {
+ // draw an icon matching the style
+ QImage px(100,100,QImage::Format_ARGB32_Premultiplied);
+ px.fill(Qt::transparent);
+
+
+ QPainter painter(&px);
+ painter.setRenderHint(QPainter::Antialiasing);
+ QPainterPath pth = path();
+ pth.translate(50.0f, 50.0f);
+
+ if (isNone()) {
+ QColor c(180,180,200);
+ painter.setPen(QPen(c));
+ painter.setBrush(QBrush(c));
+ painter.drawEllipse(QPointF(50.0f,50.0f), 3,3);
+
+ QPen pen(QColor(180,180,220));
+ pen.setWidth(3);
+ QVector<qreal> p;
+ p << 2.0 << 2.0;
+ pen.setDashPattern(p);
+ painter.setPen(pen);
+ painter.setBrush(Qt::NoBrush);
+ painter.drawPath(pth);
+ } else {
+ painter.setPen(pen());
+ painter.setBrush(brush());
+ painter.drawPath(pth);
+ }
+
+ return QIcon(QPixmap::fromImage(px));
+ } else {
+ // draw an icon matching the style
+ QPixmap px(100,100);
+ px.fill(Qt::transparent);
+ QPainter painter(&px);
+
+ if (_data == 0) {
+ QPen pen(Qt::black);
+ pen.setWidth(3);
+ } else {
+ painter.setPen(pen());
+ }
+
+ 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);
+ painter.drawLine(90,50,80,60);
+ break;
+ case Flat:
+ painter.drawLine(90,40,90,60);
+ break;
+ case NoTip:
+ break;
+ }
+
+ switch (arrowTail()) {
+ case Pointer:
+ painter.drawLine(10,50,20,40);
+ painter.drawLine(10,50,20,60);
+ break;
+ case Flat:
+ painter.drawLine(10,40,10,60);
+ break;
+ case NoTip:
+ break;
+ }
+
+
+ return QIcon(px);
+ }
+}
+
+QString Style::category() const
+{
+ return propertyWithDefault("tikzit category", "", false);
+}
diff --git a/src/data/style.h b/src/data/style.h
new file mode 100644
index 0000000..78e11dc
--- /dev/null
+++ b/src/data/style.h
@@ -0,0 +1,82 @@
+/*
+ TikZiT - a GUI diagram editor for TikZ
+ Copyright (C) 2018 Aleks Kissinger
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+*/
+
+#ifndef STYLE_H
+#define STYLE_H
+
+
+#include "graphelementdata.h"
+
+#include <QObject>
+#include <QColor>
+#include <QPen>
+#include <QBrush>
+#include <QPainterPath>
+#include <QIcon>
+
+class Style : public QObject
+{
+ Q_OBJECT
+public:
+ enum ArrowTipStyle {
+ Flat, Pointer, NoTip
+ };
+
+ enum DrawStyle {
+ Solid, Dotted, Dashed
+ };
+
+ Style();
+ Style(QString name, GraphElementData *data);
+ bool isNone() const;
+ bool isEdgeStyle() const;
+
+ // for node and edge styles
+ GraphElementData *data() const;
+ QString name() const;
+ QColor strokeColor(bool tikzitOverride=true) const;
+ int strokeThickness() const;
+ QPen pen() const;
+ QPainterPath path() const;
+ QIcon icon() const;
+ 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;
+ QString shape(bool tikzitOverride=true) const;
+
+ // only relevant for edge styles
+ Style::ArrowTipStyle arrowHead() const;
+ Style::ArrowTipStyle arrowTail() const;
+ Style::DrawStyle drawStyle() const;
+ QString category() const;
+
+protected:
+ QString _name;
+ GraphElementData *_data;
+};
+
+extern Style *noneStyle;
+extern Style *unknownStyle;
+extern Style *noneEdgeStyle;
+
+#endif // STYLE_H
diff --git a/src/data/stylelist.cpp b/src/data/stylelist.cpp
new file mode 100644
index 0000000..bc2ef37
--- /dev/null
+++ b/src/data/stylelist.cpp
@@ -0,0 +1,163 @@
+#include "stylelist.h"
+
+#include <QTextStream>
+
+StyleList::StyleList(bool edgeStyles, QObject *parent) : QAbstractListModel(parent), _edgeStyles(edgeStyles)
+{
+ if (edgeStyles) {
+ _styles << noneEdgeStyle;
+ } else {
+ _styles << noneStyle;
+ }
+}
+
+Style *StyleList::style(QString name)
+{
+ foreach (Style *s, _styles)
+ if (s->name() == name) return s;
+ return nullptr;
+}
+
+Style *StyleList::style(int i)
+{
+ return _styles[i];
+}
+
+int StyleList::length() const
+{
+ return _styles.length();
+}
+
+void StyleList::addStyle(Style *s)
+{
+ s->setParent(this);
+ if (s->category() == _category) {
+ int n = numInCategory();
+ beginInsertRows(QModelIndex(), n, n);
+ _styles << s;
+ endInsertRows();
+ } else {
+ _styles << s;
+ }
+}
+
+void StyleList::removeNthStyle(int n)
+{
+ beginRemoveRows(QModelIndex(), n, n);
+ _styles.remove(nthInCategory(n));
+ endRemoveRows();
+}
+
+void StyleList::clear()
+{
+ int n = numInCategory();
+ if (n > 1) {
+ beginRemoveRows(QModelIndex(), 1, n - 1);
+ _styles.clear();
+ if (_edgeStyles) _styles << noneEdgeStyle;
+ else _styles << noneStyle;
+ endRemoveRows();
+ } else {
+ _styles.clear();
+ if (_edgeStyles) _styles << noneEdgeStyle;
+ else _styles << noneStyle;
+ }
+
+ _category = "";
+}
+
+QString StyleList::tikz()
+{
+ QString str;
+ QTextStream code(&str);
+ for (int i = 1; i < _styles.length(); ++i)
+ code << _styles[i]->tikz() << "\n";
+ code.flush();
+ return str;
+}
+
+int StyleList::numInCategory() const
+{
+ int c = 0;
+ foreach (Style *s, _styles) {
+ if (_category == "" || s->isNone() || s->category() == _category) {
+ ++c;
+ }
+ }
+ return c;
+}
+
+int StyleList::nthInCategory(int n) const
+{
+ int c = 0;
+ for (int j = 0; j < _styles.length(); ++j) {
+ if (_category == "" || _styles[j]->isNone() || _styles[j]->category() == _category) {
+ if (c == n) return j;
+ else ++c;
+ }
+ }
+ return -1;
+}
+
+Style *StyleList::styleInCategory(int n) const
+{
+ return _styles[nthInCategory(n)];
+}
+
+QVariant StyleList::data(const QModelIndex &index, int role) const
+{
+ if (role == Qt::DisplayRole) {
+ return QVariant(styleInCategory(index.row())->name());
+ } else if (role == Qt::DecorationRole) {
+ return QVariant(styleInCategory(index.row())->icon());
+ } else {
+ return QVariant();
+ }
+}
+
+int StyleList::rowCount(const QModelIndex &/*parent*/) const
+{
+ return numInCategory();
+}
+
+bool StyleList::moveRows(const QModelIndex &sourceParent,
+ int sourceRow,
+ int /*count*/,
+ const QModelIndex &destinationParent,
+ int destinationRow)
+{
+ if (sourceRow >= 1 && sourceRow < numInCategory() &&
+ destinationRow >= 1 && destinationRow <= numInCategory())
+ {
+ beginMoveRows(sourceParent, sourceRow, sourceRow, destinationParent, destinationRow);
+ int sourceIndex = nthInCategory(sourceRow);
+ int destinationIndex = nthInCategory(destinationRow);
+ if (destinationIndex == -1)
+ destinationIndex = _styles.length();
+ Style *s = _styles[sourceIndex];
+ _styles.remove(sourceIndex);
+ if (sourceIndex < destinationIndex) {
+ _styles.insert(destinationIndex - 1, s);
+ } else {
+ _styles.insert(destinationIndex, s);
+ }
+ endMoveRows();
+ return true;
+ } else {
+ return false;
+ }
+}
+
+QString StyleList::category() const
+{
+ return _category;
+}
+
+void StyleList::setCategory(const QString &category)
+{
+ if (category != _category) {
+ beginResetModel();
+ _category = category;
+ endResetModel();
+ }
+}
diff --git a/src/data/stylelist.h b/src/data/stylelist.h
new file mode 100644
index 0000000..cf86c06
--- /dev/null
+++ b/src/data/stylelist.h
@@ -0,0 +1,47 @@
+#ifndef NODESTYLELIST_H
+#define NODESTYLELIST_H
+
+#include "style.h"
+
+#include <QAbstractListModel>
+
+class StyleList : public QAbstractListModel
+{
+ Q_OBJECT
+public:
+ explicit StyleList(bool edgeStyles = false, QObject *parent = nullptr);
+ Style *style(QString name);
+ Style *style(int i);
+ int length() const;
+ void addStyle(Style *s);
+ void removeNthStyle(int n);
+ void clear();
+ QString tikz();
+
+ int numInCategory() const;
+ int nthInCategory(int n) const;
+ Style *styleInCategory(int n) const;
+
+ QVariant data(const QModelIndex &index, int role) const override;
+ int rowCount(const QModelIndex &/*parent*/) const override;
+ bool moveRows(const QModelIndex &sourceParent,
+ int sourceRow,
+ int /*count*/,
+ const QModelIndex &destinationParent,
+ int destinationChild) override;
+
+
+ QString category() const;
+ void setCategory(const QString &category);
+
+signals:
+
+public slots:
+
+private:
+ QVector<Style*> _styles;
+ QString _category;
+ bool _edgeStyles;
+};
+
+#endif // NODESTYLELIST_H
diff --git a/src/data/tikzassembler.cpp b/src/data/tikzassembler.cpp
new file mode 100644
index 0000000..cd0b517
--- /dev/null
+++ b/src/data/tikzassembler.cpp
@@ -0,0 +1,72 @@
+/*
+ TikZiT - a GUI diagram editor for TikZ
+ Copyright (C) 2018 Aleks Kissinger
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+*/
+
+#include "tikzassembler.h"
+
+#include "tikzparserdefs.h"
+#include "tikzparser.parser.hpp"
+#include "tikzlexer.h"
+
+int yyparse(void *scanner);
+
+TikzAssembler::TikzAssembler(Graph *graph, QObject *parent) :
+ QObject(parent), _graph(graph), _tikzStyles(0)
+{
+ yylex_init(&scanner);
+ yyset_extra(this, scanner);
+}
+
+TikzAssembler::TikzAssembler(TikzStyles *tikzStyles, QObject *parent) :
+ QObject(parent), _graph(0), _tikzStyles(tikzStyles)
+{
+ yylex_init(&scanner);
+ yyset_extra(this, scanner);
+}
+
+void TikzAssembler::addNodeToMap(Node *n) { _nodeMap.insert(n->name(), n); }
+Node *TikzAssembler::nodeWithName(QString name) { return _nodeMap[name]; }
+
+bool TikzAssembler::parse(const QString &tikz)
+{
+ yy_scan_string(tikz.toLatin1().data(), scanner);
+ int result = yyparse(scanner);
+
+ if (result == 0) return true;
+ else return false;
+}
+
+Graph *TikzAssembler::graph() const
+{
+ return _graph;
+}
+
+TikzStyles *TikzAssembler::tikzStyles() const
+{
+ return _tikzStyles;
+}
+
+bool TikzAssembler::isGraph() const
+{
+ return _graph != 0;
+}
+
+bool TikzAssembler::isTikzStyles() const
+{
+ return _tikzStyles != 0;
+}
+
diff --git a/src/data/tikzassembler.h b/src/data/tikzassembler.h
new file mode 100644
index 0000000..7b32224
--- /dev/null
+++ b/src/data/tikzassembler.h
@@ -0,0 +1,60 @@
+/*
+ TikZiT - a GUI diagram editor for TikZ
+ Copyright (C) 2018 Aleks Kissinger
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+*/
+
+/*!
+ * Convenience class to hold the parser state while loading tikz graphs or projects.
+ */
+
+#ifndef TIKZASSEMBLER_H
+#define TIKZASSEMBLER_H
+
+#include "node.h"
+#include "graph.h"
+#include "tikzstyles.h"
+
+#include <QObject>
+#include <QHash>
+
+class TikzAssembler : public QObject
+{
+ Q_OBJECT
+public:
+ explicit TikzAssembler(Graph *graph, QObject *parent = 0);
+ explicit TikzAssembler(TikzStyles *tikzStyles, QObject *parent = 0);
+ void addNodeToMap(Node *n);
+ Node *nodeWithName(QString name);
+ bool parse(const QString &tikz);
+
+ Graph *graph() const;
+ TikzStyles *tikzStyles() const;
+ bool isGraph() const;
+ bool isTikzStyles() const;
+
+
+signals:
+
+public slots:
+
+private:
+ QHash<QString,Node*> _nodeMap;
+ Graph *_graph;
+ TikzStyles *_tikzStyles;
+ void *scanner;
+};
+
+#endif // TIKZASSEMBLER_H
diff --git a/src/data/tikzdocument.cpp b/src/data/tikzdocument.cpp
new file mode 100644
index 0000000..24a793b
--- /dev/null
+++ b/src/data/tikzdocument.cpp
@@ -0,0 +1,213 @@
+/*
+ TikZiT - a GUI diagram editor for TikZ
+ Copyright (C) 2018 Aleks Kissinger
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+*/
+
+#include <QFile>
+#include <QFileInfo>
+#include <QSettings>
+#include <QTextStream>
+#include <QMessageBox>
+#include <QFileDialog>
+
+#include "tikzit.h"
+#include "tikzdocument.h"
+#include "tikzassembler.h"
+#include "mainwindow.h"
+
+TikzDocument::TikzDocument(QObject *parent) : QObject(parent)
+{
+ _graph = new Graph(this);
+ _parseSuccess = true;
+ _fileName = "";
+ _shortName = "";
+ _undoStack = new QUndoStack();
+ _undoStack->setClean();
+}
+
+TikzDocument::~TikzDocument()
+{
+ delete _graph;
+ delete _undoStack;
+}
+
+QUndoStack *TikzDocument::undoStack() const
+{
+ return _undoStack;
+}
+
+Graph *TikzDocument::graph() const
+{
+ return _graph;
+}
+
+QString TikzDocument::tikz() const
+{
+ return _tikz;
+}
+
+void TikzDocument::open(QString fileName)
+{
+ _fileName = fileName;
+ QFile file(fileName);
+ QFileInfo fi(file);
+ _shortName = fi.fileName();
+ QSettings settings("tikzit", "tikzit");
+ settings.setValue("previous-file-path", fi.absolutePath());
+
+ if (!file.open(QIODevice::ReadOnly)) {
+// QMessageBox::critical(this, tr("Error"),
+// tr("Could not open file"));
+ _parseSuccess = false;
+ return;
+ }
+
+ QTextStream in(&file);
+ _tikz = in.readAll();
+ file.close();
+
+ Graph *newGraph = new Graph(this);
+ TikzAssembler ass(newGraph);
+ if (ass.parse(_tikz)) {
+ delete _graph;
+ _graph = newGraph;
+ foreach (Node *n, _graph->nodes()) n->attachStyle();
+ foreach (Edge *e, _graph->edges()) {
+ e->attachStyle();
+ e->updateControls();
+ }
+ _parseSuccess = true;
+ refreshTikz();
+ setClean();
+ } else {
+ delete newGraph;
+ _parseSuccess = false;
+ }
+}
+
+bool TikzDocument::save() {
+ if (_fileName == "") {
+ return saveAs();
+ } else {
+ MainWindow *win = tikzit->activeWindow();
+ if (win != 0 && !win->tikzScene()->enabled()) {
+ win->tikzScene()->parseTikz(win->tikzSource());
+ if (!win->tikzScene()->enabled()) {
+ auto resp = QMessageBox::question(0,
+ tr("Tikz failed to parse"),
+ tr("Cannot save file with invalid TiKZ source. Revert changes and save?"));
+ if (resp == QMessageBox::Yes) win->tikzScene()->setEnabled(true);
+ else return false; // ABORT the save
+ }
+ }
+
+ refreshTikz();
+ QFile file(_fileName);
+ QFileInfo fi(file);
+ _shortName = fi.fileName();
+ QSettings settings("tikzit", "tikzit");
+ settings.setValue("previous-file-path", fi.absolutePath());
+
+ if (file.open(QIODevice::WriteOnly)) {
+ QTextStream stream(&file);
+ stream << _tikz;
+ file.close();
+ setClean();
+ return true;
+ } else {
+ QMessageBox::warning(0, "Save Failed", "Could not open file: '" + _fileName + "' for writing.");
+ }
+ }
+
+ return false;
+}
+
+bool TikzDocument::isClean() const
+{
+ return _undoStack->isClean();
+}
+
+void TikzDocument::setClean()
+{
+ _undoStack->setClean();
+}
+
+void TikzDocument::setGraph(Graph *graph)
+{
+ _graph = graph;
+ refreshTikz();
+}
+
+bool TikzDocument::saveAs() {
+ MainWindow *win = tikzit->activeWindow();
+ if (win != 0 && !win->tikzScene()->enabled()) {
+ win->tikzScene()->parseTikz(win->tikzSource());
+ if (!win->tikzScene()->enabled()) {
+ auto resp = QMessageBox::question(0,
+ tr("Tikz failed to parse"),
+ tr("Cannot save file with invalid TiKZ source. Revert changes and save?"));
+ if (resp == QMessageBox::Yes) win->tikzScene()->setEnabled(true);
+ else return false; // ABORT the save
+ }
+ }
+
+ QSettings settings("tikzit", "tikzit");
+
+ QFileDialog dialog;
+ dialog.setDefaultSuffix("tikz");
+ dialog.setWindowTitle(tr("Save File As"));
+ dialog.setAcceptMode(QFileDialog::AcceptSave);
+ dialog.setNameFilter(tr("TiKZ Files (*.tikz)"));
+ dialog.setFileMode(QFileDialog::AnyFile);
+ dialog.setDirectory(settings.value("previous-file-path").toString());
+ dialog.setOption(QFileDialog::DontUseNativeDialog);
+
+// QString fileName = QFileDialog::getSaveFileName(tikzit->activeWindow(),
+// tr("Save File As"),
+// settings.value("previous-file-path").toString(),
+// tr("TiKZ Files (*.tikz)"),
+// nullptr,
+// QFileDialog::DontUseNativeDialog);
+
+ if (dialog.exec() && !dialog.selectedFiles().isEmpty()) {
+ QString fileName = dialog.selectedFiles()[0];
+ _fileName = fileName;
+ if (save()) {
+ // clean state might not change, so update title bar manually
+ tikzit->activeWindow()->updateFileName();
+ return true;
+ }
+ }
+
+ return false;
+}
+
+QString TikzDocument::shortName() const
+{
+ return _shortName;
+}
+
+bool TikzDocument::parseSuccess() const
+{
+ return _parseSuccess;
+}
+
+void TikzDocument::refreshTikz()
+{
+ _tikz = _graph->tikz();
+ if (MainWindow *w = dynamic_cast<MainWindow*>(parent()))
+ w->refreshTikz();
+}
diff --git a/src/data/tikzdocument.h b/src/data/tikzdocument.h
new file mode 100644
index 0000000..fca5434
--- /dev/null
+++ b/src/data/tikzdocument.h
@@ -0,0 +1,69 @@
+/*
+ TikZiT - a GUI diagram editor for TikZ
+ Copyright (C) 2018 Aleks Kissinger
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+*/
+
+/*!
+ * This class contains a tikz Graph, source code, file info, and undo stack. It serves as the model
+ * in the MVC triple (TikzDocument, TikzView, TikzScene).
+ */
+
+#ifndef TIKZDOCUMENT_H
+#define TIKZDOCUMENT_H
+
+#include "graph.h"
+
+#include <QObject>
+#include <QUndoStack>
+
+class TikzDocument : public QObject
+{
+ Q_OBJECT
+public:
+ explicit TikzDocument(QObject *parent = 0);
+ ~TikzDocument();
+
+ Graph *graph() const;
+ void setGraph(Graph *graph);
+ QString tikz() const;
+ QUndoStack *undoStack() const;
+ bool parseSuccess() const;
+ void refreshTikz();
+
+ void open(QString fileName);
+
+ QString shortName() const;
+
+ bool saveAs();
+ bool save();
+
+ bool isClean() const;
+ void setClean();
+
+private:
+ Graph *_graph;
+ QString _tikz;
+ QString _fileName;
+ QString _shortName;
+ QUndoStack *_undoStack;
+ bool _parseSuccess;
+
+signals:
+
+public slots:
+};
+
+#endif // TIKZDOCUMENT_H
diff --git a/src/data/tikzlexer.l b/src/data/tikzlexer.l
new file mode 100644
index 0000000..0d80467
--- /dev/null
+++ b/src/data/tikzlexer.l
@@ -0,0 +1,193 @@
+%{
+/*
+ TikZiT - a GUI diagram editor for TikZ
+ Copyright (C) 2018 Aleks Kissinger, Chris Heunen,
+ K. Johan Paulsson, Alex Merry
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+*/
+
+
+/*!
+ * \file tikzlexer.l
+ *
+ * The lexer for tikz input.
+ */
+
+#include "tikzparserdefs.h"
+#include "tikzparser.parser.hpp"
+
+#include <sstream>
+
+
+#define YY_USER_ACTION \
+ yylloc->first_line = yylloc->last_line; \
+ yylloc->first_column = yylloc->last_column + 1; \
+ yylloc->last_column = yylloc->first_column + yyleng - 1;
+
+%}
+
+%option reentrant bison-bridge bison-locations 8bit
+%option bison-locations 8bit
+%option nounput
+%option yylineno
+%option noyywrap
+%option header-file="tikzlexer.h"
+%option extra-type="TikzAssembler *"
+
+%s props
+%s xcoord
+%s ycoord
+%s noderef
+
+FLOAT \-?[0-9]*(\.[0-9]+)?
+
+%%
+
+ /* whitespace is ignored, except for position counting; we don't
+ count formfeed and vtab as whitespace, because it's not obvious
+ how they should be dealt with and no-one actually uses them */
+
+ /* lex will take the longest-matching string */
+<INITIAL,xcoord,ycoord,props,noderef>\r\n|\r|\n {
+ yylloc->first_line += 1;
+ yylloc->last_line = yylloc->first_line;
+ yylloc->first_column = yylloc->last_column = 0;
+}
+<INITIAL,xcoord,ycoord,props,noderef>[\t ]+ { }
+<INITIAL,xcoord,ycoord,props,noderef>%.*$ { }
+
+\\begin\{tikzpicture\} { return BEGIN_TIKZPICTURE_CMD; }
+\\end\{tikzpicture\} { return END_TIKZPICTURE_CMD; }
+\\tikzstyle { return TIKZSTYLE_CMD; }
+\\begin\{pgfonlayer\} { return BEGIN_PGFONLAYER_CMD; }
+\\end\{pgfonlayer\} { return END_PGFONLAYER_CMD; }
+\\draw { return DRAW_CMD; }
+\\node { return NODE_CMD; }
+\\path { return PATH_CMD; }
+; { return SEMICOLON; }
+= { return EQUALS; }
+<INITIAL>rectangle { return RECTANGLE; }
+<INITIAL>node { return NODE; }
+<INITIAL>at { return AT; }
+<INITIAL>to { return TO; }
+
+\([ ]*{FLOAT}[ ]*,[ ]*{FLOAT}[ ]*\) {
+ yylloc->last_column = yylloc->first_column + 1;
+ yyless(1);
+ BEGIN(xcoord);
+}
+<xcoord>{FLOAT} {
+ yylval->pt = new QPointF();
+ QString s(yytext);
+ yylval->pt->setX(s.toDouble());
+ BEGIN(ycoord);
+}
+<ycoord>, { }
+<ycoord>{FLOAT} {
+ QString s(yytext);
+ yylval->pt->setY(s.toDouble());
+}
+<ycoord>\) {
+ BEGIN(INITIAL);
+ return TCOORD;
+}
+
+ /* when we see "[", change parsing mode */
+\[ /*syntaxhlfix]*/ {
+ BEGIN(props);
+ return LEFTBRACKET;
+}
+<props>= { return EQUALS; }
+<props>, { return COMMA; }
+ /* technically, it is possible to have newlines in the middle of
+ property names or values, but in practice this is unlikely and
+ screws up our line counting */
+<props>[^=,\{\] \t\n]([^=,\{\]\n]*[^=,\{\] \t\n])? {
+ char *str = (char*)malloc(sizeof(char)*yyleng + 1);
+ strncpy(str, yytext, yyleng + 1);
+ yylval->str = str;
+ return PROPSTRING;
+}
+<props>\] {
+ BEGIN(INITIAL);
+ return RIGHTBRACKET;
+}
+
+\( {
+ BEGIN(noderef);
+ return LEFTPARENTHESIS;
+}
+<noderef>\. {
+ return FULLSTOP;
+}
+ /* we assume node names (and anchor names) never contain
+ newlines */
+<noderef>[^\.\{\)\n]+ {
+ //qDebug() << "nodename: " << yytext << " size: " << strlen(yytext);
+ char *str = (char*)malloc(sizeof(char)*yyleng + 1);
+ strncpy(str, yytext, yyleng+1);
+ yylval->str = str;
+ return REFSTRING;
+}
+<noderef>\) {
+ BEGIN(INITIAL);
+ return RIGHTPARENTHESIS;
+}
+
+<INITIAL,props>\{ {
+ std::stringstream buf;
+ unsigned int brace_depth = 1;
+ unsigned int escape = 0;
+ while (1) {
+ char c = yyinput(yyscanner);
+ // eof reached before closing brace
+ if (c == '\0' || c == EOF) {
+ return UNCLOSED_DELIM_STR;
+ }
+
+ yylloc->last_column += 1;
+ yyleng += 1;
+ if (escape) {
+ escape = 0;
+ } else if (c == '\\') {
+ escape = 1;
+ } else if (c == '{') {
+ brace_depth++;
+ } else if (c == '}') {
+ brace_depth--;
+ if (brace_depth == 0) break;
+ } else if (c == '\n') {
+ yylloc->last_line += 1;
+ yylloc->last_column = 0;
+ }
+ buf << c;
+ }
+
+ char *str = (char*)malloc(sizeof(char) * yyleng + 1);
+ strncpy(str, buf.str().c_str(), yyleng + 1);
+ //str[len] = 0;
+ yylval->str = str;
+ //qDebug() << "got delim string: " << str;
+ return DELIMITEDSTRING;
+}
+
+\\begin { return UNKNOWN_BEGIN_CMD; }
+\\end { return UNKNOWN_END_CMD; }
+\\[a-zA-Z0-9]+ { return UNKNOWN_CMD; }
+[a-zA-Z0-9]+ { return UNKNOWN_STR; }
+<INITIAL,xcoord,ycoord,props,noderef>. { return UNKNOWN_STR; }
+
+ /* vi:ft=lex:noet:ts=4:sts=4:sw=4:
+ */
diff --git a/src/data/tikzparser.y b/src/data/tikzparser.y
new file mode 100644
index 0000000..4473107
--- /dev/null
+++ b/src/data/tikzparser.y
@@ -0,0 +1,284 @@
+%{
+/*!
+ * \file tikzparser.y
+ *
+ * The parser for tikz input.
+ */
+
+/*
+ * Copyright 2010 Chris Heunen
+ * Copyright 2010-2017 Aleks Kissinger
+ * Copyright 2013 K. Johan Paulsson
+ * Copyright 2013 Alex Merry <dev@randomguy3.me.uk>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "tikzparserdefs.h"
+%}
+
+/* we use features added to bison 2.4 */
+%require "2.3"
+
+%error-verbose
+/* enable maintaining locations for better error messages */
+%locations
+/* the name of the header file */
+/*%defines "common/tikzparser.h"*/
+/* make it re-entrant (no global variables) */
+%pure-parser
+/* We use a pure (re-entrant) lexer. This means yylex
+ will take a void* (opaque) type to maintain its state */
+%lex-param {void *scanner}
+/* Since this parser is also pure, yyparse needs to take
+ that lexer state as an argument */
+%parse-param {void *scanner}
+
+/* possible data types for semantic values */
+%union {
+ char *str;
+ GraphElementProperty *prop;
+ GraphElementData *data;
+ Node *node;
+ QPointF *pt;
+ struct noderef noderef;
+}
+
+%{
+#include "node.h"
+#include "edge.h"
+#include "graphelementdata.h"
+#include "graphelementproperty.h"
+
+#include "tikzlexer.h"
+#include "tikzassembler.h"
+/* the assembler (used by this parser) is stored in the lexer
+ state as "extra" data */
+#define assembler yyget_extra(scanner)
+
+/* pass errors off to the assembler */
+void yyerror(YYLTYPE *yylloc, void * /*scanner*/, const char *str) {
+ // TODO: implement reportError()
+ //assembler->reportError(str, yylloc);
+ qDebug() << "\nparse error: " << str << " line:" << yylloc->first_line;
+}
+%}
+
+/* yyloc is set up with first_column = last_column = 1 by default;
+ however, it makes more sense to think of us being "before the
+ start of the line" before we parse anything */
+%initial-action {
+ yylloc.first_column = yylloc.last_column = 0;
+}
+
+
+%token BEGIN_TIKZPICTURE_CMD "\\begin{tikzpicture}"
+%token END_TIKZPICTURE_CMD "\\end{tikzpicture}"
+%token TIKZSTYLE_CMD "\\tikzstyle"
+%token BEGIN_PGFONLAYER_CMD "\\begin{pgfonlayer}"
+%token END_PGFONLAYER_CMD "\\end{pgfonlayer}"
+%token DRAW_CMD "\\draw"
+%token NODE_CMD "\\node"
+%token PATH_CMD "\\path"
+%token RECTANGLE "rectangle"
+%token NODE "node"
+%token AT "at"
+%token TO "to"
+%token SEMICOLON ";"
+%token COMMA ","
+
+%token LEFTPARENTHESIS "("
+%token RIGHTPARENTHESIS ")"
+%token LEFTBRACKET "["
+%token RIGHTBRACKET "]"
+%token FULLSTOP "."
+%token EQUALS "="
+%token <pt> TCOORD "coordinate"
+%token <str> PROPSTRING "key/value string"
+%token <str> REFSTRING "string"
+%token <str> DELIMITEDSTRING "{-delimited string"
+
+%token UNKNOWN_BEGIN_CMD "unknown \\begin command"
+%token UNKNOWN_END_CMD "unknown \\end command"
+%token UNKNOWN_CMD "unknown latex command"
+%token UNKNOWN_STR "unknown string"
+%token UNCLOSED_DELIM_STR "unclosed {-delimited string"
+
+%type<str> nodename
+%type<str> optanchor
+%type<str> val
+%type<prop> property
+%type<data> extraproperties
+%type<data> properties
+%type<data> optproperties
+%type<node> optedgenode
+%type<noderef> noderef
+%type<noderef> optnoderef
+
+%%
+
+
+tikz: tikzstyles | tikzpicture;
+
+tikzstyles: tikzstyles tikzstyle | ;
+tikzstyle: "\\tikzstyle" DELIMITEDSTRING "=" "[" properties "]"
+ {
+ if (assembler->isTikzStyles()) {
+ assembler->tikzStyles()->addStyle(QString($2), $5);
+ }
+ }
+
+tikzpicture: "\\begin{tikzpicture}" optproperties tikzcmds "\\end{tikzpicture}"
+ {
+ if (assembler->isGraph() && $2) {
+ assembler->graph()->setData($2);
+ }
+ };
+tikzcmds: tikzcmds tikzcmd | ;
+tikzcmd: node | edge | boundingbox | ignore;
+
+ignore: "\\begin{pgfonlayer}" DELIMITEDSTRING | "\\end{pgfonlayer}";
+
+optproperties:
+ "[" "]"
+ { $$ = 0; }
+ | "[" properties "]"
+ { $$ = $2; }
+ | { $$ = 0; };
+properties: extraproperties property
+ {
+ $1->add(*$2);
+ delete $2;
+ $$ = $1;
+ };
+extraproperties:
+ extraproperties property ","
+ {
+ $1->add(*$2);
+ delete $2;
+ $$ = $1;
+ }
+ | { $$ = new GraphElementData(); };
+property:
+ val "=" val
+ {
+ GraphElementProperty *p = new GraphElementProperty(QString($1),QString($3));
+ free($1);
+ free($3);
+ $$ = p;
+ }
+ | val
+ {
+ GraphElementProperty *a = new GraphElementProperty(QString($1));
+ free($1);
+ $$ = a;
+ };
+val: PROPSTRING { $$ = $1; } | DELIMITEDSTRING { $$ = $1; };
+
+nodename: "(" REFSTRING ")" { $$ = $2; };
+node: "\\node" optproperties nodename "at" TCOORD DELIMITEDSTRING ";"
+ {
+ Node *node = new Node();
+
+ if ($2) {
+ node->setData($2);
+ }
+ //qDebug() << "node name: " << $3;
+ node->setName(QString($3));
+ node->setLabel(QString($6));
+ free($3);
+ free($6);
+
+ node->setPoint(*$5);
+ delete $5;
+
+ assembler->graph()->addNode(node);
+ assembler->addNodeToMap(node);
+ };
+
+optanchor: { $$ = 0; } | "." REFSTRING { $$ = $2; };
+noderef: "(" REFSTRING optanchor ")"
+ {
+ $$.node = assembler->nodeWithName(QString($2));
+ free($2);
+ $$.anchor = $3;
+ };
+optnoderef:
+ noderef { $$ = $1; }
+ | "(" ")" { $$.node = 0; $$.anchor = 0; }
+optedgenode:
+ { $$ = 0; }
+ | "node" optproperties DELIMITEDSTRING
+ {
+ $$ = new Node();
+ if ($2)
+ $$->setData($2);
+ $$->setLabel(QString($3));
+ free($3);
+ }
+edge: "\\draw" optproperties noderef "to" optedgenode optnoderef ";"
+ {
+ Node *s;
+ Node *t;
+
+ s = $3.node;
+
+ if ($6.node) {
+ t = $6.node;
+ } else {
+ t = s;
+ }
+
+ // 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 ($5)
+ edge->setEdgeNode($5);
+ if ($3.anchor) {
+ edge->setSourceAnchor(QString($3.anchor));
+ free($3.anchor);
+ }
+
+ if ($6.node) {
+ if ($6.anchor) {
+ edge->setTargetAnchor(QString($6.anchor));
+ free($6.anchor);
+ }
+ } else {
+ edge->setTargetAnchor(edge->sourceAnchor());
+ }
+
+ assembler->graph()->addEdge(edge);
+ }
+ };
+
+ignoreprop: val | val "=" val;
+ignoreprops: ignoreprop ignoreprops | ;
+optignoreprops: "[" ignoreprops "]";
+boundingbox:
+ "\\path" optignoreprops TCOORD "rectangle" TCOORD ";"
+ {
+ assembler->graph()->setBbox(QRectF(*$3, *$5));
+ delete $3;
+ delete $5;
+ };
+
+/* vi:ft=yacc:noet:ts=4:sts=4:sw=4
+*/
diff --git a/src/data/tikzparserdefs.h b/src/data/tikzparserdefs.h
new file mode 100644
index 0000000..02743fe
--- /dev/null
+++ b/src/data/tikzparserdefs.h
@@ -0,0 +1,40 @@
+/*
+ TikZiT - a GUI diagram editor for TikZ
+ Copyright (C) 2018 Aleks Kissinger
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+*/
+
+#ifndef TIKZPARSERDEFS_H
+#define TIKZPARSERDEFS_H
+
+#define YY_NO_UNISTD_H 1
+
+#include "graphelementproperty.h"
+#include "graphelementdata.h"
+#include "node.h"
+#include "tikzassembler.h"
+
+#include <QString>
+#include <QRectF>
+#include <QDebug>
+
+struct noderef {
+ Node *node;
+ char *anchor;
+};
+
+inline int isatty(int) { return 0; }
+
+#endif // TIKZPARSERDEFS_H
diff --git a/src/data/tikzstyles.cpp b/src/data/tikzstyles.cpp
new file mode 100644
index 0000000..522b3f5
--- /dev/null
+++ b/src/data/tikzstyles.cpp
@@ -0,0 +1,181 @@
+/*
+ TikZiT - a GUI diagram editor for TikZ
+ Copyright (C) 2018 Aleks Kissinger
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+*/
+
+#include "tikzstyles.h"
+#include "tikzassembler.h"
+
+#include <QDebug>
+#include <QColorDialog>
+#include <QFile>
+#include <QFileInfo>
+
+TikzStyles::TikzStyles(QObject *parent) : QObject(parent)
+{
+ _nodeStyles = new StyleList(false, this);
+ _edgeStyles = new StyleList(true, this);
+}
+
+Style *TikzStyles::nodeStyle(QString name) const
+{
+ Style *s = _nodeStyles->style(name);
+
+ return (s == nullptr) ? unknownStyle : s;
+}
+
+Style *TikzStyles::edgeStyle(QString name) const
+{
+ Style *s = _edgeStyles->style(name);
+ return (s == nullptr) ? noneEdgeStyle : s;
+}
+
+
+void TikzStyles::clear()
+{
+ _nodeStyles->clear();
+ _edgeStyles->clear();
+}
+
+bool TikzStyles::loadStyles(QString fileName)
+{
+ QFile file(fileName);
+ if (file.open(QIODevice::ReadOnly)) {
+ QTextStream in(&file);
+ QString styleTikz = in.readAll();
+ file.close();
+
+ clear();
+ TikzAssembler ass(this);
+ return ass.parse(styleTikz);
+ } else {
+ return false;
+ }
+}
+
+bool TikzStyles::saveStyles(QString fileName)
+{
+ QFile file(fileName);
+ if (file.open(QIODevice::WriteOnly)) {
+ QTextStream stream(&file);
+ stream << tikz();
+ file.close();
+ return true;
+ }
+ return false;
+}
+
+void TikzStyles::refreshModels(QStandardItemModel *nodeModel, QStandardItemModel *edgeModel, QString category, bool includeNone)
+{
+ nodeModel->clear();
+ edgeModel->clear();
+
+
+ //QString f = tikzit->styleFile();
+ //ui->styleFile->setText(f);
+
+ QStandardItem *it;
+
+ if (includeNone) {
+ it = new QStandardItem(noneStyle->icon(), noneStyle->name());
+ it->setEditable(false);
+ it->setData(noneStyle->name());
+ nodeModel->appendRow(it);
+ it->setTextAlignment(Qt::AlignCenter);
+ it->setSizeHint(QSize(48,48));
+ }
+
+ Style *s;
+ for (int i = 0; i < _nodeStyles->length(); ++i) {
+ s = _nodeStyles->style(i);
+ if (category == "" || category == s->propertyWithDefault("tikzit category", "", false))
+ {
+ it = new QStandardItem(s->icon(), s->name());
+ it->setEditable(false);
+ it->setData(s->name());
+ it->setSizeHint(QSize(48,48));
+ nodeModel->appendRow(it);
+ }
+ }
+
+ if (includeNone) {
+ it = new QStandardItem(noneEdgeStyle->icon(), noneEdgeStyle->name());
+ it->setEditable(false);
+ it->setData(noneEdgeStyle->name());
+ edgeModel->appendRow(it);
+ }
+
+ for (int i = 0; i < _edgeStyles->length(); ++i) {
+ s = _edgeStyles->style(i);
+ it = new QStandardItem(s->icon(), s->name());
+ it->setEditable(false);
+ it->setData(s->name());
+ it->setSizeHint(QSize(48,48));
+ edgeModel->appendRow(it);
+ }
+}
+
+StyleList *TikzStyles::nodeStyles() const
+{
+ return _nodeStyles;
+}
+
+StyleList *TikzStyles::edgeStyles() const
+{
+ return _edgeStyles;
+}
+
+QStringList TikzStyles::categories() const
+{
+ QMap<QString,bool> cats; // use a QMap to keep keys sorted
+ cats.insert("", true);
+ Style *ns;
+ for (int i = 0; i < _nodeStyles->length(); ++i) {
+ ns = _nodeStyles->style(i);
+ cats.insert(ns->propertyWithDefault("tikzit category", "", false), true);
+ }
+ //foreach (EdgeStyle *s, _edgeStyles) cats << s->propertyWithDefault("tikzit category", "", false);
+ return QStringList(cats.keys());
+}
+
+QString TikzStyles::tikz() const
+{
+ QString str;
+ QTextStream code(&str);
+
+ code << "% TiKZ style file generated by TikZiT. You may edit this file manually,\n";
+ code << "% but some things (e.g. comments) may be overwritten. To be readable in\n";
+ code << "% TikZiT, the only non-comment lines must be of the form:\n";
+ code << "% \\tikzstyle{NAME}=[PROPERTY LIST]\n\n";
+
+ code << "% Node styles\n";
+ code << _nodeStyles->tikz();
+
+ code << "\n% Edge styles\n";
+ code << _edgeStyles->tikz();
+
+ code.flush();
+ return str;
+}
+
+void TikzStyles::addStyle(QString name, GraphElementData *data)
+{
+ Style *s = new Style(name, data);
+ if (s->isEdgeStyle()) _edgeStyles->addStyle(s);
+ else _nodeStyles->addStyle(s);
+}
+
+
diff --git a/src/data/tikzstyles.h b/src/data/tikzstyles.h
new file mode 100644
index 0000000..5f372ab
--- /dev/null
+++ b/src/data/tikzstyles.h
@@ -0,0 +1,65 @@
+/*
+ TikZiT - a GUI diagram editor for TikZ
+ Copyright (C) 2018 Aleks Kissinger
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+*/
+
+#ifndef PROJECT_H
+#define PROJECT_H
+
+#include "graphelementdata.h"
+#include "stylelist.h"
+#include "style.h"
+
+#include <QObject>
+#include <QString>
+#include <QColor>
+#include <QStandardItemModel>
+
+class TikzStyles : public QObject
+{
+ Q_OBJECT
+public:
+ explicit TikzStyles(QObject *parent = 0);
+ void addStyle(QString name, GraphElementData *data);
+
+ Style *nodeStyle(QString name) const;
+ Style *edgeStyle(QString name) const;
+ QStringList categories() const;
+ QString tikz() const;
+ void clear();
+
+ bool loadStyles(QString fileName);
+ bool saveStyles(QString fileName);
+ void refreshModels(QStandardItemModel *nodeModel,
+ QStandardItemModel *edgeModel,
+ QString category="",
+ bool includeNone=true);
+
+ StyleList *nodeStyles() const;
+ StyleList *edgeStyles() const;
+
+signals:
+
+public slots:
+
+private:
+ StyleList *_nodeStyles;
+ StyleList* _edgeStyles;
+ QStringList _colNames;
+ QVector<QColor> _cols;
+};
+
+#endif // PROJECT_H