summaryrefslogtreecommitdiff
path: root/src/data
diff options
context:
space:
mode:
Diffstat (limited to 'src/data')
-rw-r--r--src/data/delimitedstringvalidator.cpp58
-rw-r--r--src/data/delimitedstringvalidator.h57
-rw-r--r--src/data/edge.cpp90
-rw-r--r--src/data/edge.h21
-rw-r--r--src/data/graph.cpp4
-rw-r--r--src/data/graphelementdata.cpp9
-rw-r--r--src/data/graphelementdata.h8
-rw-r--r--src/data/graphelementproperty.h37
-rw-r--r--src/data/node.cpp9
-rw-r--r--src/data/node.h1
-rw-r--r--src/data/pdfdocument.cpp134
-rw-r--r--src/data/pdfdocument.h28
-rw-r--r--src/data/tikzdocument.cpp62
-rw-r--r--src/data/tikzdocument.h6
14 files changed, 425 insertions, 99 deletions
diff --git a/src/data/delimitedstringvalidator.cpp b/src/data/delimitedstringvalidator.cpp
new file mode 100644
index 0000000..9f1057e
--- /dev/null
+++ b/src/data/delimitedstringvalidator.cpp
@@ -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/>.
+*/
+
+#include "delimitedstringvalidator.h"
+
+DelimitedStringValidator::DelimitedStringValidator(QObject *parent) : QValidator(parent)
+{
+}
+
+QValidator::State DelimitedStringValidator::validate(QString &input, int &/*pos*/) const
+{
+ int depth = braceDepth(input);
+ if (depth == 0) return Acceptable;
+ else if (depth > 0) return Intermediate;
+ else return Invalid;
+}
+
+void DelimitedStringValidator::fixup(QString &input) const
+{
+ int depth = braceDepth(input);
+ if (depth > 0) input.append(QString("}").repeated(depth));
+}
+
+int DelimitedStringValidator::braceDepth(QString input) const
+{
+ int depth = 0;
+ bool escape = false;
+ for (int i = 0; i < input.length(); ++i) {
+ QCharRef c = input[i];
+ if (escape) {
+ escape = false;
+ } else if (c == '\\') {
+ escape = true;
+ } else if (c == '{') {
+ depth++;
+ } else if (c == '}') {
+ depth--;
+ if (depth < 0) return -1;
+ }
+ }
+
+ return depth;
+}
diff --git a/src/data/delimitedstringvalidator.h b/src/data/delimitedstringvalidator.h
new file mode 100644
index 0000000..60c2513
--- /dev/null
+++ b/src/data/delimitedstringvalidator.h
@@ -0,0 +1,57 @@
+/*
+ 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 string validator which keeps curly braces matched. Used in various places
+ * to ensure the user doesn't make non-parseable .tikz or .tikzstyles files.
+ *
+ * Its validation function will return Acceptable if all curly braces are matched
+ * properly, Intermediate if all braces are matched except for possibly some opening
+ * curly braces, and Invalid if there are unmatched closing curly braces.
+ */
+
+#ifndef DELIMITEDSTRINGVALIDATOR_H
+#define DELIMITEDSTRINGVALIDATOR_H
+
+
+#include <QObject>
+#include <QValidator>
+
+
+class DelimitedStringValidator : public QValidator
+{
+ Q_OBJECT
+public:
+ DelimitedStringValidator(QObject *parent);
+ QValidator::State validate(QString &input, int &/*pos*/) const override;
+
+ /*!
+ * \brief fixup adds curly braces until all braces are matched (if possible)
+ * \param input
+ */
+ void fixup(QString &input) const override;
+private:
+ /*!
+ * \brief braceDepth computes the final (non-escaped) curly-brace depth of a given string
+ * \param input a string
+ * \return the final brace depth, or -1 if the depth *ever* drops below 0
+ */
+ int braceDepth(QString input) const;
+};
+
+#endif // DELIMITEDSTRINGVALIDATOR_H
diff --git a/src/data/edge.cpp b/src/data/edge.cpp
index 0ae566b..864d5ed 100644
--- a/src/data/edge.cpp
+++ b/src/data/edge.cpp
@@ -26,8 +26,8 @@
Edge::Edge(Node *s, Node *t, QObject *parent) :
QObject(parent), _source(s), _target(t)
{
- _data = new GraphElementData();
- _edgeNode = 0;
+ _data = new GraphElementData(this);
+ _edgeNode = nullptr;
_dirty = true;
if (s != t) {
@@ -35,24 +35,18 @@ Edge::Edge(Node *s, Node *t, QObject *parent) :
_bend = 0;
_inAngle = 0;
_outAngle = 0;
- _weight = 0.4f;
+ _weight = 0.4;
} else {
_basicBendMode = false;
_bend = 0;
_inAngle = 135;
_outAngle = 45;
- _weight = 1.0f;
+ _weight = 1.0;
}
_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
@@ -63,7 +57,7 @@ Edge::~Edge()
Edge *Edge::copy(QMap<Node*,Node*> *nodeTable)
{
Edge *e;
- if (nodeTable == 0) e = new Edge(_source, _target);
+ if (nodeTable == nullptr) e = new Edge(_source, _target);
else e = new Edge(nodeTable->value(_source), nodeTable->value(_target));
e->setData(_data->copy());
e->setBasicBendMode(_basicBendMode);
@@ -103,8 +97,9 @@ GraphElementData *Edge::data() const
void Edge::setData(GraphElementData *data)
{
- delete _data;
+ GraphElementData *oldData = _data;
_data = data;
+ oldData->deleteLater();
setAttributesFromData();
}
@@ -148,13 +143,14 @@ Node *Edge::edgeNode() const
void Edge::setEdgeNode(Node *edgeNode)
{
- if (_edgeNode != 0) delete _edgeNode;
+ Node *oldEdgeNode = _edgeNode;
_edgeNode = edgeNode;
+ if (oldEdgeNode != nullptr) oldEdgeNode->deleteLater();
}
bool Edge::hasEdgeNode()
{
- return _edgeNode != 0;
+ return _edgeNode != nullptr;
}
void Edge::updateControls() {
@@ -162,22 +158,22 @@ void Edge::updateControls() {
QPointF src = _source->point();
QPointF targ = _target->point();
- float dx = (targ.x() - src.x());
- float dy = (targ.y() - src.y());
+ qreal dx = (targ.x() - src.x());
+ qreal dy = (targ.y() - src.y());
- float outAngleR = 0.0f;
- float inAngleR = 0.0f;
+ qreal outAngleR = 0.0;
+ qreal inAngleR = 0.0;
if (_basicBendMode) {
- float angle = std::atan2(dy, dx);
- float bnd = (float)_bend * (M_PI / 180.0f);
+ qreal angle = std::atan2(dy, dx);
+ qreal bnd = static_cast<qreal>(_bend) * (M_PI / 180.0);
outAngleR = angle - bnd;
inAngleR = M_PI + angle + bnd;
- _outAngle = outAngleR * (180.f / M_PI);
- _inAngle = inAngleR * (180.f / M_PI);
+ _outAngle = static_cast<int>(round(outAngleR * (180.0 / M_PI)));
+ _inAngle = static_cast<int>(round(inAngleR * (180.0 / M_PI)));
} else {
- outAngleR = (float)_outAngle * (M_PI / 180.0f);
- inAngleR = (float)_inAngle * (M_PI / 180.0f);
+ outAngleR = static_cast<qreal>(_outAngle) * (M_PI / 180.0);
+ inAngleR = static_cast<qreal>(_inAngle) * (M_PI / 180.0);
}
// TODO: calculate head and tail properly, not just for circles
@@ -196,7 +192,7 @@ void Edge::updateControls() {
}
// give a default distance for self-loops
- _cpDist = (dx==0.0f && dy==0.0f) ? _weight : std::sqrt(dx*dx + dy*dy) * _weight;
+ _cpDist = (almostZero(dx) && almostZero(dy)) ? _weight : std::sqrt(dx*dx + dy*dy) * _weight;
_cp1 = QPointF(src.x() + (_cpDist * std::cos(outAngleR)),
src.y() + (_cpDist * std::sin(outAngleR)));
@@ -204,9 +200,9 @@ void Edge::updateControls() {
_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);
+ _mid = bezierInterpolateFull (0.5, _tail, _cp1, _cp2, _head);
+ _tailTangent = bezierTangent(0.0, 0.1);
+ _headTangent = bezierTangent(1.0, 0.9);
}
void Edge::setAttributesFromData()
@@ -218,16 +214,16 @@ void Edge::setAttributesFromData()
_bend = -30;
} else if (_data->atom("bend right")) {
_bend = 30;
- } else if (_data->property("bend left") != 0) {
+ } else if (_data->property("bend left") != nullptr) {
_bend = -_data->property("bend left").toInt(&ok);
if (!ok) _bend = -30;
- } else if (_data->property("bend right") != 0) {
+ } else if (_data->property("bend right") != nullptr) {
_bend = _data->property("bend right").toInt(&ok);
if (!ok) _bend = 30;
} else {
_bend = 0;
- if (_data->property("in") != 0 && _data->property("out") != 0) {
+ if (_data->property("in") != nullptr && _data->property("out") != nullptr) {
_basicBendMode = false;
_inAngle = _data->property("in").toInt(&ok);
if (!ok) _inAngle = 0;
@@ -237,10 +233,10 @@ void Edge::setAttributesFromData()
}
if (!_data->property("looseness").isNull()) {
- _weight = _data->property("looseness").toFloat(&ok) / 2.5f;
- if (!ok) _weight = 0.4f;
+ _weight = _data->property("looseness").toDouble(&ok) / 2.5;
+ if (!ok) _weight = 0.4;
} else {
- _weight = (isSelfLoop()) ? 1.0f : 0.4f;
+ _weight = (isSelfLoop()) ? 1.0 : 0.4;
}
//qDebug() << "bend: " << _bend << " in: " << _inAngle << " out: " << _outAngle;
@@ -258,8 +254,6 @@ void Edge::updateData()
_data->unsetProperty("bend right");
_data->unsetProperty("looseness");
- // TODO: style handling?
-
if (_basicBendMode) {
if (_bend != 0) {
QString bendKey;
@@ -284,8 +278,8 @@ void Edge::updateData()
}
if (_source == _target) _data->setAtom("loop");
- if (!isSelfLoop() && !isStraight() && _weight != 0.4f)
- _data->setProperty("looseness", QString::number(_weight*2.5f, 'f', 2));
+ if (!isSelfLoop() && !isStraight() && !almostEqual(_weight, 0.4))
+ _data->setProperty("looseness", QString::number(_weight*2.5, 'f', 2));
if (_source->isBlankNode()) _sourceAnchor = "center";
else _sourceAnchor = "";
if (_target->isBlankNode()) _targetAnchor = "center";
@@ -329,7 +323,7 @@ int Edge::outAngle() const
return _outAngle;
}
-float Edge::weight() const
+qreal Edge::weight() const
{
return _weight;
}
@@ -339,7 +333,7 @@ bool Edge::basicBendMode() const
return _basicBendMode;
}
-float Edge::cpDist() const
+qreal Edge::cpDist() const
{
return _cpDist;
}
@@ -364,7 +358,7 @@ void Edge::setOutAngle(int outAngle)
_outAngle = outAngle;
}
-void Edge::setWeight(float weight)
+void Edge::setWeight(qreal weight)
{
_weight = weight;
}
@@ -406,18 +400,18 @@ Style *Edge::style() const
return _style;
}
-QPointF Edge::bezierTangent(float start, float end) const
+QPointF Edge::bezierTangent(qreal start, qreal end) const
{
- float dx = bezierInterpolate(end, _tail.x(), _cp1.x(), _cp2.x(), _head.x()) -
+ qreal 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()) -
+ qreal 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;
+ qreal len = sqrt(dx*dx + dy*dy);
+ if (almostZero(len)) {
+ dx = (dx / len) * 0.1;
+ dy = (dy / len) * 0.1;
}
return QPointF(dx, dy);
diff --git a/src/data/edge.h b/src/data/edge.h
index ad71364..909824b 100644
--- a/src/data/edge.h
+++ b/src/data/edge.h
@@ -16,6 +16,10 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
+/*!
+ * Class representing an edge in a Graph.
+ */
+
#ifndef EDGE_H
#define EDGE_H
@@ -30,9 +34,8 @@ class Edge : public QObject
{
Q_OBJECT
public:
- explicit Edge(Node *s, Node *t, QObject *parent = 0);
- ~Edge();
- Edge *copy(QMap<Node *, Node *> *nodeTable = 0);
+ explicit Edge(Node *s, Node *t, QObject *parent = nullptr);
+ Edge *copy(QMap<Node *, Node *> *nodeTable = nullptr);
Node *source() const;
Node *target() const;
@@ -68,15 +71,15 @@ public:
int bend() const;
int inAngle() const;
int outAngle() const;
- float weight() const;
+ qreal weight() const;
bool basicBendMode() const;
- float cpDist() const;
+ qreal cpDist() const;
void setBasicBendMode(bool mode);
void setBend(int bend);
void setInAngle(int inAngle);
void setOutAngle(int outAngle);
- void setWeight(float weight);
+ void setWeight(qreal weight);
int tikzLine() const;
void setTikzLine(int tikzLine);
@@ -92,7 +95,7 @@ signals:
public slots:
private:
- QPointF bezierTangent(float start, float end) const;
+ QPointF bezierTangent(qreal start, qreal end) const;
QString _sourceAnchor;
QString _targetAnchor;
@@ -112,8 +115,8 @@ private:
int _bend;
int _inAngle;
int _outAngle;
- float _weight;
- float _cpDist;
+ qreal _weight;
+ qreal _cpDist;
QPointF _head;
QPointF _tail;
diff --git a/src/data/graph.cpp b/src/data/graph.cpp
index bba2061..1dd5574 100644
--- a/src/data/graph.cpp
+++ b/src/data/graph.cpp
@@ -152,8 +152,9 @@ GraphElementData *Graph::data() const
void Graph::setData(GraphElementData *data)
{
- delete _data;
+ GraphElementData *oldData = _data;
_data = data;
+ oldData->deleteLater();
}
const QVector<Node*> &Graph::nodes()
@@ -278,6 +279,7 @@ Graph *Graph::copyOfSubgraphWithNodes(QSet<Node *> nds)
{
Graph *g = new Graph();
g->setData(_data->copy());
+ g->data()->setAtom("tikzfig");
QMap<Node*,Node*> nodeTable;
foreach (Node *n, nodes()) {
if (nds.contains(n)) {
diff --git a/src/data/graphelementdata.cpp b/src/data/graphelementdata.cpp
index 810ebd6..cd09a6d 100644
--- a/src/data/graphelementdata.cpp
+++ b/src/data/graphelementdata.cpp
@@ -23,18 +23,12 @@
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()
{
@@ -103,7 +97,8 @@ bool GraphElementData::hasProperty(QString key)
bool GraphElementData::atom(QString atom)
{
- return (indexOfKey(atom) != -1);
+ int idx = indexOfKey(atom);
+ return (idx != -1 && _properties[idx].atom());
}
int GraphElementData::indexOfKey(QString key)
diff --git a/src/data/graphelementdata.h b/src/data/graphelementdata.h
index 23f0466..dce0d46 100644
--- a/src/data/graphelementdata.h
+++ b/src/data/graphelementdata.h
@@ -16,6 +16,12 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
+/*!
+ * A list of GraphElementProperty objects, which convenience methods
+ * for lookup, deletion, re-ordering, etc. It inherits QAbstractItemModel
+ * so it can be used as the model for a QTreeView in the StyleEditor.
+ */
+
#ifndef GRAPHELEMENTDATA_H
#define GRAPHELEMENTDATA_H
@@ -34,7 +40,6 @@ 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);
@@ -78,7 +83,6 @@ public slots:
private:
QVector<GraphElementProperty> _properties;
- GraphElementProperty *root;
};
#endif // GRAPHELEMENTDATA_H
diff --git a/src/data/graphelementproperty.h b/src/data/graphelementproperty.h
index 4ebe104..5777c18 100644
--- a/src/data/graphelementproperty.h
+++ b/src/data/graphelementproperty.h
@@ -16,6 +16,11 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
+/*!
+ * A class which holds either a single key/value pair (i.e. a proper property)
+ * or simply a key with no value (i.e. an atom).
+ */
+
#ifndef GRAPHELEMENTPROPERTY_H
#define GRAPHELEMENTPROPERTY_H
@@ -26,13 +31,19 @@ class GraphElementProperty
public:
GraphElementProperty();
- // full constructor
GraphElementProperty(QString key, QString value, bool atom);
- // construct a proper property
+ /*!
+ * \brief GraphElementProperty constructs a proper property with the given key/value
+ * \param key
+ * \param value
+ */
GraphElementProperty(QString key, QString value);
- // construct an atom
+ /*!
+ * \brief GraphElementProperty constructs an atom with the given key
+ * \param key
+ */
GraphElementProperty(QString key);
QString key() const;
@@ -40,9 +51,29 @@ public:
QString value() const;
void setValue(const QString &value);
bool atom() const;
+
+ /*!
+ * \brief operator == returns true for atoms if the keys match and for properties
+ * if the keys and values match. Note a property is never equal to an atom.
+ * \param p
+ * \return
+ */
bool operator==(const GraphElementProperty &p);
+ /*!
+ * \brief tikzEscape prepares a property key or value for export to tikz code. If
+ * the property only contains numbers, letters, whitespace, or the characters (<,>,-)
+ * this method does nothing. Otherwise, wrap the property in curly braces.
+ * \param str
+ * \return
+ */
static QString tikzEscape(QString str);
+
+ /*!
+ * \brief tikz escapes the key/value of a propery or atom and outputs it as "key=value"
+ * for properties and "key" for atoms.
+ * \return
+ */
QString tikz();
signals:
diff --git a/src/data/node.cpp b/src/data/node.cpp
index 75acd00..8ec5e9b 100644
--- a/src/data/node.cpp
+++ b/src/data/node.cpp
@@ -23,15 +23,11 @@
Node::Node(QObject *parent) : QObject(parent), _tikzLine(-1)
{
- _data = new GraphElementData();
+ _data = new GraphElementData(this);
_style = noneStyle;
_data->setProperty("style", "none");
}
-Node::~Node()
-{
- delete _data;
-}
Node *Node::copy() {
Node *n1 = new Node();
@@ -81,8 +77,9 @@ GraphElementData *Node::data() const
void Node::setData(GraphElementData *data)
{
- delete _data;
+ GraphElementData *oldData = _data;
_data = data;
+ oldData->deleteLater();
}
QString Node::styleName() const
diff --git a/src/data/node.h b/src/data/node.h
index 490393d..c40627b 100644
--- a/src/data/node.h
+++ b/src/data/node.h
@@ -31,7 +31,6 @@ class Node : public QObject
Q_OBJECT
public:
explicit Node(QObject *parent = 0);
- ~Node();
Node *copy();
diff --git a/src/data/pdfdocument.cpp b/src/data/pdfdocument.cpp
new file mode 100644
index 0000000..c9574b8
--- /dev/null
+++ b/src/data/pdfdocument.cpp
@@ -0,0 +1,134 @@
+#include "pdfdocument.h"
+
+#include <QFile>
+#include <QByteArray>
+#include <QDebug>
+#include <QApplication>
+#include <QClipboard>
+
+PdfDocument::PdfDocument(QString file, QObject *parent) : QObject(parent)
+{
+ // use loadFromData to avoid holding a lock on the PDF file in windows
+ QFile f(file);
+ if (f.open(QFile::ReadOnly)) {
+ QByteArray data = f.readAll();
+ f.close();
+ _doc = Poppler::Document::loadFromData(data);
+ } else {
+ _doc = nullptr;
+ }
+
+ if (!_doc) {
+ _doc = nullptr;
+ _page = nullptr;
+ } else {
+ _doc->setRenderHint(Poppler::Document::Antialiasing);
+ _doc->setRenderHint(Poppler::Document::TextAntialiasing);
+ _doc->setRenderHint(Poppler::Document::TextHinting);
+ _page = _doc->page(0);
+ }
+}
+
+void PdfDocument::renderTo(QLabel *label, QRect rect)
+{
+ if (!isValid()) return;
+
+ QSizeF pageSize = _page->pageSizeF();
+
+ qreal ratio = label->devicePixelRatioF();
+ //QRect rect = ui->scrollArea->visibleRegion().boundingRect();
+ int w = static_cast<int>(ratio * (rect.width() - 20));
+ int h = static_cast<int>(ratio * (rect.height() - 20));
+
+ // not all platforms have fmin, compute the min by hand
+ qreal hscale = static_cast<qreal>(w) / pageSize.width();
+ qreal vscale = static_cast<qreal>(h) / pageSize.height();
+ qreal scale = (hscale < vscale) ? hscale : vscale;
+
+ int dpi = static_cast<int>(scale * 72.0);
+ int w1 = static_cast<int>(scale * pageSize.width());
+ int h1 = static_cast<int>(scale * pageSize.height());
+
+ //qDebug() << "hidpi ratio:" << ratio;
+ //qDebug() << "visible width:" << w;
+ //qDebug() << "visible height:" << h;
+ //qDebug() << "doc width:" << pageSize.width();
+ //qDebug() << "doc height:" << pageSize.height();
+ //qDebug() << "scale:" << scale;
+ //qDebug() << "dpi:" << dpi;
+
+ QPixmap pm = QPixmap::fromImage(_page->renderToImage(dpi, dpi, (w1 - w)/2, (h1 - h)/2, w, h));
+ pm.setDevicePixelRatio(ratio);
+ label->setPixmap(pm);
+}
+
+bool PdfDocument::isValid()
+{
+ return (_page != nullptr);
+}
+
+bool PdfDocument::exportImage(QString file, const char *format, QSize outputSize)
+{
+ QImage img = asImage(outputSize);
+ if (!img.isNull()) return img.save(file, format);
+ else return false;
+}
+
+bool PdfDocument::exportPdf(QString file)
+{
+ if (!isValid()) return false;
+ Poppler::PDFConverter *conv = _doc->pdfConverter();
+ conv->setOutputFileName(file);
+ bool success = conv->convert();
+ delete conv;
+ return success;
+}
+
+void PdfDocument::copyImageToClipboard(QSize outputSize)
+{
+ QImage img = asImage(outputSize);
+ if (!img.isNull()) {
+ QApplication::clipboard()->setImage(img, QClipboard::Clipboard);
+ }
+}
+
+QImage PdfDocument::asImage(QSize outputSize)
+{
+ if (!isValid()) return QImage();
+ if (outputSize.isNull()) outputSize = size();
+ QSize pageSize = _page->pageSize();
+ int dpix = (72 * outputSize.width()) / pageSize.width();
+ int dpiy = (72 * outputSize.width()) / pageSize.width();
+ QImage img = _page->renderToImage(dpix, dpiy, 0, 0,
+ outputSize.width(), outputSize.height());
+ return img;
+}
+
+// CRASHES TikZiT when figures contain text, due to limitations of Arthur backend
+//void PdfDocument::exportToSvg(QString file, QSize size) {
+// QSvgGenerator gen;
+// gen.setFileName(file);
+// gen.setSize(size);
+// gen.setViewBox(QRect(0,0,size.width(),size.height()));
+// gen.setDescription("SVG generated from PDF by TikZiT");
+// QPainter painter;
+
+// // set the backend to Qt for renderToPainter() support
+// Poppler::Document::RenderBackend backend = _doc->renderBackend();
+// _doc->setRenderBackend(Poppler::Document::ArthurBackend);
+// painter.begin(&gen);
+// _page->renderToPainter(&painter);
+// painter.end();
+// _doc->setRenderBackend(backend);
+//}
+
+QSize PdfDocument::size()
+{
+ if (isValid()) {
+ return _page->pageSize();
+ } else {
+ return QSize();
+ }
+}
+
+
diff --git a/src/data/pdfdocument.h b/src/data/pdfdocument.h
new file mode 100644
index 0000000..ebd33e9
--- /dev/null
+++ b/src/data/pdfdocument.h
@@ -0,0 +1,28 @@
+#ifndef PDFDOCUMENT_H
+#define PDFDOCUMENT_H
+
+#include <QObject>
+#include <QString>
+#include <QLabel>
+
+#include <poppler/qt5/poppler-qt5.h>
+
+class PdfDocument : public QObject
+{
+ Q_OBJECT
+public:
+ explicit PdfDocument(QString file, QObject *parent = nullptr);
+ void renderTo(QLabel *label, QRect rect);
+ bool isValid();
+// void exportToSvg(QString file, QSize size);
+ bool exportImage(QString file, const char *format, QSize outputSize=QSize());
+ bool exportPdf(QString file);
+ void copyImageToClipboard(QSize outputSize=QSize());
+ QImage asImage(QSize outputSize=QSize());
+ QSize size();
+private:
+ Poppler::Document *_doc;
+ Poppler::Page *_page;
+};
+
+#endif // PDFDOCUMENT_H
diff --git a/src/data/tikzdocument.cpp b/src/data/tikzdocument.cpp
index 24a793b..1099779 100644
--- a/src/data/tikzdocument.cpp
+++ b/src/data/tikzdocument.cpp
@@ -34,16 +34,10 @@ TikzDocument::TikzDocument(QObject *parent) : QObject(parent)
_parseSuccess = true;
_fileName = "";
_shortName = "";
- _undoStack = new QUndoStack();
+ _undoStack = new QUndoStack(this);
_undoStack->setClean();
}
-TikzDocument::~TikzDocument()
-{
- delete _graph;
- delete _undoStack;
-}
-
QUndoStack *TikzDocument::undoStack() const
{
return _undoStack;
@@ -75,15 +69,18 @@ void TikzDocument::open(QString fileName)
return;
}
+ addToRecentFiles();
+
QTextStream in(&file);
_tikz = in.readAll();
file.close();
+ Graph *oldGraph = _graph;
Graph *newGraph = new Graph(this);
TikzAssembler ass(newGraph);
if (ass.parse(_tikz)) {
- delete _graph;
_graph = newGraph;
+ oldGraph->deleteLater();
foreach (Node *n, _graph->nodes()) n->attachStyle();
foreach (Edge *e, _graph->edges()) {
e->attachStyle();
@@ -93,7 +90,7 @@ void TikzDocument::open(QString fileName)
refreshTikz();
setClean();
} else {
- delete newGraph;
+ newGraph->deleteLater();
_parseSuccess = false;
}
}
@@ -103,10 +100,10 @@ bool TikzDocument::save() {
return saveAs();
} else {
MainWindow *win = tikzit->activeWindow();
- if (win != 0 && !win->tikzScene()->enabled()) {
+ if (win != nullptr && !win->tikzScene()->enabled()) {
win->tikzScene()->parseTikz(win->tikzSource());
if (!win->tikzScene()->enabled()) {
- auto resp = QMessageBox::question(0,
+ auto resp = QMessageBox::question(nullptr,
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);
@@ -128,7 +125,8 @@ bool TikzDocument::save() {
setClean();
return true;
} else {
- QMessageBox::warning(0, "Save Failed", "Could not open file: '" + _fileName + "' for writing.");
+ QMessageBox::warning(nullptr,
+ "Save Failed", "Could not open file: '" + _fileName + "' for writing.");
}
}
@@ -145,6 +143,34 @@ void TikzDocument::setClean()
_undoStack->setClean();
}
+QString TikzDocument::fileName() const
+{
+ return _fileName;
+}
+
+bool TikzDocument::isEmpty()
+{
+ return _graph->nodes().isEmpty();
+}
+
+void TikzDocument::addToRecentFiles()
+{
+ QSettings settings("tikzit", "tikzit");
+ if (!_fileName.isEmpty()) {
+ QStringList recentFiles = settings.value("recent-files").toStringList();
+
+ // if the file is in the list already, shift it to the top. Otherwise, add it.
+ recentFiles.removeAll(_fileName);
+ recentFiles.prepend(_fileName);
+
+ // keep max 10 files
+ while (recentFiles.size() > 10) recentFiles.removeLast();
+
+ settings.setValue("recent-files", recentFiles);
+ tikzit->updateRecentFiles();
+ }
+}
+
void TikzDocument::setGraph(Graph *graph)
{
_graph = graph;
@@ -153,10 +179,10 @@ void TikzDocument::setGraph(Graph *graph)
bool TikzDocument::saveAs() {
MainWindow *win = tikzit->activeWindow();
- if (win != 0 && !win->tikzScene()->enabled()) {
+ if (win != nullptr && !win->tikzScene()->enabled()) {
win->tikzScene()->parseTikz(win->tikzSource());
if (!win->tikzScene()->enabled()) {
- auto resp = QMessageBox::question(0,
+ auto resp = QMessageBox::question(nullptr,
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);
@@ -175,19 +201,13 @@ bool TikzDocument::saveAs() {
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();
+ addToRecentFiles();
return true;
}
}
diff --git a/src/data/tikzdocument.h b/src/data/tikzdocument.h
index fca5434..ad5499f 100644
--- a/src/data/tikzdocument.h
+++ b/src/data/tikzdocument.h
@@ -34,7 +34,6 @@ class TikzDocument : public QObject
Q_OBJECT
public:
explicit TikzDocument(QObject *parent = 0);
- ~TikzDocument();
Graph *graph() const;
void setGraph(Graph *graph);
@@ -53,6 +52,10 @@ public:
bool isClean() const;
void setClean();
+ QString fileName() const;
+
+ bool isEmpty();
+
private:
Graph *_graph;
QString _tikz;
@@ -60,6 +63,7 @@ private:
QString _shortName;
QUndoStack *_undoStack;
bool _parseSuccess;
+ void addToRecentFiles();
signals: