diff options
Diffstat (limited to 'src')
42 files changed, 2435 insertions, 322 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:
diff --git a/src/gui/delimitedstringitemdelegate.cpp b/src/gui/delimitedstringitemdelegate.cpp new file mode 100644 index 0000000..7b6c58e --- /dev/null +++ b/src/gui/delimitedstringitemdelegate.cpp @@ -0,0 +1,44 @@ +/* + 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 "delimitedstringitemdelegate.h" + +#include <QLineEdit> + +DelimitedStringItemDelegate::DelimitedStringItemDelegate(QObject *parent) : QItemDelegate (parent) +{ + _validator = new DelimitedStringValidator(this); +} + +DelimitedStringItemDelegate::~DelimitedStringItemDelegate() +{ +} + +QWidget *DelimitedStringItemDelegate::createEditor( + QWidget *parent, + const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + QWidget *editor = QItemDelegate::createEditor(parent, option, index); + + if (QLineEdit *lineEdit = dynamic_cast<QLineEdit*>(editor)) { + lineEdit->setValidator(_validator); + } + + return editor; +} diff --git a/src/gui/delimitedstringitemdelegate.h b/src/gui/delimitedstringitemdelegate.h new file mode 100644 index 0000000..bd1a856 --- /dev/null +++ b/src/gui/delimitedstringitemdelegate.h @@ -0,0 +1,42 @@ +/* + 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 QItemDelete that attaches a DelimitedStringValidator to any QLineEdit + */ + +#ifndef DELIMITEDSTRINGITEMDELEGATE_H +#define DELIMITEDSTRINGITEMDELEGATE_H + +#include "delimitedstringvalidator.h" + +#include <QWidget> +#include <QItemDelegate> + +class DelimitedStringItemDelegate : public QItemDelegate +{ + Q_OBJECT +public: + DelimitedStringItemDelegate(QObject *parent=nullptr); + virtual ~DelimitedStringItemDelegate() override; + QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override; +private: + DelimitedStringValidator *_validator; +}; + +#endif // DELIMITEDSTRINGITEMDELEGATE_H diff --git a/src/gui/exportdialog.cpp b/src/gui/exportdialog.cpp new file mode 100644 index 0000000..bd1ef53 --- /dev/null +++ b/src/gui/exportdialog.cpp @@ -0,0 +1,194 @@ +/* + 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 "exportdialog.h" +#include "ui_exportdialog.h" + +#include "tikzit.h" + +#include <QFileDialog> +#include <QSettings> +#include <QStandardPaths> + +ExportDialog::ExportDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::ExportDialog) +{ + QSettings settings("tikzit", "tikzit"); + ui->setupUi(this); + + QIntValidator *v = new QIntValidator(this); + v->setBottom(1); + ui->width->setValidator(v); + ui->height->setValidator(v); + connect(ui->width, SIGNAL(editingFinished()), + this, SLOT(setHeightFromWidth())); + connect(ui->height, SIGNAL(editingFinished()), + this, SLOT(setWidthFromHeight())); + + PdfDocument *doc = tikzit->previewWindow()->doc(); + if (doc) { + QSize size = doc->size() * 4; + ui->width->blockSignals(true); + ui->height->blockSignals(true); + ui->width->setText(QString::number(size.width())); + ui->height->setText(QString::number(size.height())); + ui->width->blockSignals(false); + ui->height->blockSignals(false); + } + + if (!settings.value("previous-export-file-format").isNull()) { + ui->fileFormat->setCurrentIndex(settings.value("previous-export-file-format").toInt()); + } + + // set a default export file + QString path = (!settings.value("previous-export-file-path").isNull()) ? + settings.value("previous-export-file-path").toString() : + QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); + + QString suffix; + switch (ui->fileFormat->currentIndex()) { + case PNG: suffix = ".png"; break; + case JPG: suffix = ".jpg"; break; + case PDF: suffix = ".pdf"; break; + } + + QString fileName; + int i = 0; + bool exists = true; + while (exists) { + fileName = path + "/tikzit_image" + QString::number(i) + suffix; + exists = QFileInfo::exists(fileName); + ++i; + } + ui->filePath->setText(QDir::toNativeSeparators(fileName)); +} + +ExportDialog::~ExportDialog() +{ + delete ui; +} + +QString ExportDialog::filePath() +{ + return ui->filePath->text(); +} + +QSize ExportDialog::size() +{ + return QSize(ui->width->text().toInt(), + ui->height->text().toInt()); +} + +ExportDialog::Format ExportDialog::fileFormat() +{ + return static_cast<Format>(ui->fileFormat->currentIndex()); +} + +void ExportDialog::accept() +{ + QSettings settings("tikzit", "tikzit"); + QFileInfo fi(filePath()); + settings.setValue("previous-export-file-path", fi.absolutePath()); + settings.setValue("previous-export-file-format", fileFormat()); + QDialog::accept(); +} + +void ExportDialog::setHeightFromWidth() +{ + if (ui->keepAspect->isChecked()) { + PdfDocument *doc = tikzit->previewWindow()->doc(); + if (doc == nullptr || doc->size().width() == 0 || doc->size().height() == 0) return; + int w = ui->width->text().toInt(); + int h = (w * doc->size().height()) / doc->size().width(); + + ui->height->blockSignals(true); + ui->height->setText(QString::number(h)); + ui->height->blockSignals(false); + } +} + +void ExportDialog::setWidthFromHeight() +{ + if (ui->keepAspect->isChecked()) { + PdfDocument *doc = tikzit->previewWindow()->doc(); + if (doc == nullptr || doc->size().width() == 0 || doc->size().height() == 0) return; + int h = ui->height->text().toInt(); + int w = (h * doc->size().width()) / doc->size().height(); + + ui->width->blockSignals(true); + ui->width->setText(QString::number(w)); + ui->width->blockSignals(false); + } +} + +void ExportDialog::on_keepAspect_stateChanged(int state) +{ + if (state == Qt::Checked) setHeightFromWidth(); +} + +void ExportDialog::on_browseButton_clicked() +{ + QSettings settings("tikzit", "tikzit"); + + QString suffix; + switch (ui->fileFormat->currentIndex()) { + case PNG: suffix = "png"; break; + case JPG: suffix = "jpg"; break; + case PDF: suffix = "pdf"; break; + } + + QFileDialog dialog; + dialog.setDefaultSuffix(suffix); + dialog.setWindowTitle(tr("Export File Path")); + dialog.setAcceptMode(QFileDialog::AcceptSave); + dialog.setNameFilter(ui->fileFormat->currentText()); + dialog.setFileMode(QFileDialog::AnyFile); + dialog.setLabelText(QFileDialog::Accept, "Select"); + + QFileInfo fi(ui->filePath->text()); + if (!fi.absolutePath().isEmpty()) { + dialog.setDirectory(fi.absolutePath()); + dialog.selectFile(fi.baseName()); + } + + dialog.setOption(QFileDialog::DontUseNativeDialog); + + if (dialog.exec()) { + ui->filePath->setText(QDir::toNativeSeparators(dialog.selectedFiles()[0])); + } +} + +void ExportDialog::on_fileFormat_currentIndexChanged(int f) +{ + ui->width->setEnabled(f != PDF); + ui->height->setEnabled(f != PDF); + ui->keepAspect->setEnabled(f != PDF); + + QString path = ui->filePath->text(); + if (!path.isEmpty()) { + QRegularExpression re("\\.[^.]*$"); + switch (f) { + case PNG: path.replace(re, ".png"); break; + case JPG: path.replace(re, ".jpg"); break; + case PDF: path.replace(re, ".pdf"); break; + } + + ui->filePath->setText(path); + } +} diff --git a/src/gui/exportdialog.h b/src/gui/exportdialog.h new file mode 100644 index 0000000..bcb6879 --- /dev/null +++ b/src/gui/exportdialog.h @@ -0,0 +1,61 @@ +/* + 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 dialog for exporting a LaTeX-generated preview to PNG, JPG, or PDF. + */ + +#ifndef EXPORTDIALOG_H +#define EXPORTDIALOG_H + +#include <QDialog> + +namespace Ui { +class ExportDialog; +} + +class ExportDialog : public QDialog +{ + Q_OBJECT + +public: + explicit ExportDialog(QWidget *parent = nullptr); + ~ExportDialog() override; + enum Format { + PNG = 0, + JPG = 1, + PDF = 2 + }; + QString filePath(); + QSize size(); + Format fileFormat(); +public slots: + void accept() override; + +protected slots: + void setHeightFromWidth(); + void setWidthFromHeight(); + void on_keepAspect_stateChanged(int state); + void on_browseButton_clicked(); + void on_fileFormat_currentIndexChanged(int f); + +private: + Ui::ExportDialog *ui; +}; + +#endif // EXPORTDIALOG_H diff --git a/src/gui/exportdialog.ui b/src/gui/exportdialog.ui new file mode 100644 index 0000000..82cefdc --- /dev/null +++ b/src/gui/exportdialog.ui @@ -0,0 +1,162 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ExportDialog</class> + <widget class="QDialog" name="ExportDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>394</width> + <height>119</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="windowTitle"> + <string>Export Image</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <layout class="QFormLayout" name="formLayout"> + <item row="0" column="0"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>File</string> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Format</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <widget class="QLineEdit" name="filePath"/> + </item> + <item> + <widget class="QToolButton" name="browseButton"> + <property name="text"> + <string>...</string> + </property> + </widget> + </item> + </layout> + </item> + <item row="1" column="1"> + <widget class="QComboBox" name="fileFormat"> + <item> + <property name="text"> + <string>Portable Network Graphics (*.png)</string> + </property> + </item> + <item> + <property name="text"> + <string>Jpeg Image (*.jpg)</string> + </property> + </item> + <item> + <property name="text"> + <string>Original (*.pdf)</string> + </property> + </item> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>Dimensions</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <layout class="QHBoxLayout" name="horizontalLayout_3"> + <item> + <widget class="QLineEdit" name="width"/> + </item> + <item> + <widget class="QLabel" name="label_4"> + <property name="text"> + <string> X </string> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="height"/> + </item> + </layout> + </item> + <item row="3" column="0"> + <widget class="QLabel" name="label_5"> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item row="3" column="1"> + <widget class="QCheckBox" name="keepAspect"> + <property name="text"> + <string>Keep aspect ratio</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Save</set> + </property> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>ExportDialog</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>248</x> + <y>254</y> + </hint> + <hint type="destinationlabel"> + <x>157</x> + <y>274</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>ExportDialog</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>316</x> + <y>260</y> + </hint> + <hint type="destinationlabel"> + <x>286</x> + <y>274</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/src/gui/latexprocess.cpp b/src/gui/latexprocess.cpp new file mode 100644 index 0000000..59db9ea --- /dev/null +++ b/src/gui/latexprocess.cpp @@ -0,0 +1,169 @@ +/* + 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 "latexprocess.h" +#include "tikzit.h" + +#include <QDebug> +#include <QStandardPaths> +#include <QTemporaryDir> +#include <QStringList> +#include <QSettings> + +LatexProcess::LatexProcess(PreviewWindow *preview, QObject *parent) : QObject(parent) +{ + _preview = preview; + _output = preview->outputTextEdit(); + + _proc = new QProcess(this); + _proc->setProcessChannelMode(QProcess::MergedChannels); + _proc->setWorkingDirectory(_workingDir.path()); + + connect(_proc, SIGNAL(readyReadStandardOutput()), this, SLOT(readyReadStandardOutput())); + connect(_proc, SIGNAL(finished(int)), this, SLOT(finished(int))); + + // for debug purposes + //_workingDir.setAutoRemove(false); +} + +void LatexProcess::makePreview(QString tikz) +{ + QSettings settings("tikzit", "tikzit"); + _preview->setStatus(PreviewWindow::Running); + _output->clear(); + + if (!_workingDir.isValid()) { + _output->appendPlainText("COULD NOT WRITE TO TEMP DIR: " + _workingDir.path() + "\n"); + return; + } + + _output->appendPlainText("USING TEMP DIR: " + _workingDir.path() + "\n"); + + QString pdflatex; + + if (settings.value("auto-detect-pdflatex", true).toBool()) { + _output->appendPlainText("SEARCHING FOR pdflatex IN:"); + _output->appendPlainText(qgetenv("PATH")); + _output->appendPlainText("\n"); + pdflatex = QStandardPaths::findExecutable("pdflatex"); + if (pdflatex.isEmpty()) { + // if pdflatex is not in PATH, we are probably on mac or windows, so try common + // install directories. + _output->appendPlainText("NOT FOUND IN PATH, TRYING:"); + + QStringList texDirs; + // common macOS tex directories: + texDirs << "/Library/TeX/texbin"; + texDirs << "/usr/texbin"; + texDirs << "/usr/local/bin"; + texDirs << "/sw/bin"; + + // common windows tex directories + texDirs << "C:\\Program Files\\MiKTeX 2.9\\miktex\\bin"; + texDirs << "C:\\Program Files\\MiKTeX 2.9\\miktex\\bin\\x64"; + texDirs << "C:\\Program Files\\MiKTeX 2.8\\miktex\\bin"; + texDirs << "C:\\Program Files\\MiKTeX 2.8\\miktex\\bin\\x64"; + texDirs << "C:\\Program Files\\MiKTeX 2.7\\miktex\\bin"; + texDirs << "C:\\Program Files\\MiKTeX 2.7\\miktex\\bin\\x64"; + + _output->appendPlainText(texDirs.join(":")); + pdflatex = QStandardPaths::findExecutable("pdflatex", texDirs); + } + + if (pdflatex.isEmpty()) { + _output->appendPlainText("pdflatex NOT FOUND, ABORTING.\n"); + _preview->setStatus(PreviewWindow::Failed); + return; + } else { + _output->appendPlainText("FOUND: " + pdflatex + "\n"); + } + } else { + _output->appendPlainText("USING pdflatex:\n"); + pdflatex = settings.value("pdflatex-path", "/usr/bin/pdflatex").toString(); + _output->appendPlainText(pdflatex + "\n"); + } + + // copy tikzit.sty to preview dir + QFile::copy(":/tex/sample/tikzit.sty", _workingDir.path() + "/tikzit.sty"); + + // write out the file containing the tikz picture + QFile f(_workingDir.path() + "/preview.tex"); + f.open(QIODevice::WriteOnly); + QTextStream tex(&f); + tex << "\\documentclass{article}\n"; + tex << "\\usepackage{tikzit}\n"; + tex << "\\tikzstyle{every picture}=[tikzfig]\n"; + tex << "\\usepackage[graphics,active,tightpage]{preview}\n"; + tex << "\\PreviewEnvironment{tikzpicture}\n"; + + // copy active *.tikzstyles file to preview dir + if (!tikzit->styleFile().isEmpty() && QFile::exists(tikzit->styleFilePath())) { + QFile::copy(tikzit->styleFilePath(), _workingDir.path() + "/" + tikzit->styleFile()); + tex << "\\input{" + tikzit->styleFile() + "}\n"; + + // if there is a *.tikzdefs file with the same basename, copy and include it as well + QFileInfo fi(tikzit->styleFilePath()); + QString defFile = fi.baseName() + ".tikzdefs"; + QString defFilePath = fi.absolutePath() + "/" + defFile; + if (QFile::exists(defFilePath)) { + QFile::copy(defFilePath, _workingDir.path() + "/" + defFile); + tex << "\\input{" + defFile + "}\n"; + } + } + + tex << "\\begin{document}\n\n"; + tex << tikz; + tex << "\n\n\\end{document}\n"; + + f.close(); + _proc->start(pdflatex, + QStringList() + << "-interaction=nonstopmode" + << "-halt-on-error" + << "preview.tex"); + +} + +void LatexProcess::kill() +{ + if (_proc->state() == QProcess::Running) _proc->kill(); +} + +void LatexProcess::readyReadStandardOutput() +{ + QByteArray s = _proc->readAllStandardOutput(); + _output->appendPlainText(s); +} + +void LatexProcess::finished(int exitCode) +{ + QByteArray s = _proc->readAllStandardOutput(); + _output->appendPlainText(s); + + if (exitCode == 0) { + QString pdf = _workingDir.path() + "/preview.pdf"; + _output->appendPlainText("\n\nSUCCESSFULLY GENERATED: " + pdf + "\n"); + _preview->setPdf(pdf); + _preview->setStatus(PreviewWindow::Success); + emit previewFinished(); + } else { + _output->appendPlainText("\n\npdflatex RETURNED AN ERROR\n"); + _preview->setStatus(PreviewWindow::Failed); + emit previewFinished(); + } +} diff --git a/src/gui/latexprocess.h b/src/gui/latexprocess.h new file mode 100644 index 0000000..9853883 --- /dev/null +++ b/src/gui/latexprocess.h @@ -0,0 +1,56 @@ +/* + 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/>. +*/ + +/*! + * Run pdflatex and dump its output to the appropriate tab of + * the PreviewWindow. + */ + +#ifndef LATEXPROCESS_H +#define LATEXPROCESS_H + +#include "previewwindow.h" + +#include <QObject> +#include <QProcess> +#include <QTemporaryDir> +#include <QPlainTextEdit> + +class LatexProcess : public QObject +{ + Q_OBJECT +public: + explicit LatexProcess(PreviewWindow *preview, QObject *parent = nullptr); + void makePreview(QString tikz); + void kill(); + +private: + QTemporaryDir _workingDir; + PreviewWindow *_preview; + QPlainTextEdit *_output; + QProcess *_proc; + +public slots: + void readyReadStandardOutput(); + void finished(int exitCode); + +signals: + void previewFinished(); +}; + +#endif // LATEXPROCESS_H diff --git a/src/gui/mainmenu.cpp b/src/gui/mainmenu.cpp index 8166c59..6f4f8db 100644 --- a/src/gui/mainmenu.cpp +++ b/src/gui/mainmenu.cpp @@ -17,6 +17,7 @@ */ #include "mainmenu.h" +#include "preferencedialog.h" #include "tikzit.h" #include <QDebug> @@ -33,6 +34,8 @@ MainMenu::MainMenu() ui.actionCheck_for_updates_automatically->setChecked(settings.value("check-for-updates").toBool()); ui.actionCheck_for_updates_automatically->blockSignals(false); } + + updateRecentFiles(); } void MainMenu::addDocks(QMenu *m) @@ -48,6 +51,31 @@ QAction *MainMenu::updatesAction() return ui.actionCheck_for_updates_automatically; } +void MainMenu::updateRecentFiles() +{ + QSettings settings("tikzit", "tikzit"); + ui.menuOpen_Recent->clear(); + + QStringList recentFiles = settings.value("recent-files").toStringList(); + //qDebug() << "update:" << recentFiles; + + QAction *action; + foreach (QString f, recentFiles) { + QFileInfo fi(f); + action = new QAction(fi.fileName(), ui.menuOpen_Recent); + action->setData(f); + ui.menuOpen_Recent->addAction(action); + connect(action, SIGNAL(triggered()), + this, SLOT(openRecent())); + } + + ui.menuOpen_Recent->addSeparator(); + action = new QAction("Clear List", ui.menuOpen_Recent); + connect(action, SIGNAL(triggered()), + tikzit, SLOT(clearRecentFiles())); + ui.menuOpen_Recent->addAction(action); +} + // File void MainMenu::on_actionNew_triggered() { @@ -82,6 +110,15 @@ void MainMenu::on_actionExit_triggered() tikzit->quit(); } +void MainMenu::openRecent() +{ + if (sender() != nullptr) { + if (QAction *action = dynamic_cast<QAction*>(sender())) { + tikzit->open(action->data().toString()); + } + } +} + // Edit void MainMenu::on_actionUndo_triggered() @@ -228,6 +265,18 @@ void MainMenu::on_actionJump_to_Selection_triggered() } } +void MainMenu::on_actionRun_LaTeX_triggered() +{ + tikzit->makePreview(); +} + +void MainMenu::on_actionPreferences_triggered() +{ + PreferenceDialog *d = new PreferenceDialog(this); + d->exec(); + d->deleteLater(); +} + // View void MainMenu::on_actionZoom_In_triggered() @@ -260,5 +309,5 @@ void MainMenu::on_actionCheck_for_updates_automatically_triggered() void MainMenu::on_actionCheck_now_triggered() { - tikzit->checkForUpdates(); + tikzit->checkForUpdates(true); } diff --git a/src/gui/mainmenu.h b/src/gui/mainmenu.h index c14a284..4d672cd 100644 --- a/src/gui/mainmenu.h +++ b/src/gui/mainmenu.h @@ -30,6 +30,7 @@ public: MainMenu(); void addDocks(QMenu *m); QAction *updatesAction(); + void updateRecentFiles(); private: Ui::MainMenu ui; @@ -43,6 +44,8 @@ public slots: void on_actionSave_As_triggered(); void on_actionExit_triggered(); + void openRecent(); + // Edit void on_actionUndo_triggered(); void on_actionRedo_triggered(); @@ -63,10 +66,12 @@ public slots: void on_actionExtendLeft_triggered(); void on_actionExtendRight_triggered(); - // Tikz + // Tools void on_actionParse_triggered(); void on_actionRevert_triggered(); void on_actionJump_to_Selection_triggered(); + void on_actionRun_LaTeX_triggered(); + void on_actionPreferences_triggered(); // View void on_actionZoom_In_triggered(); diff --git a/src/gui/mainmenu.ui b/src/gui/mainmenu.ui index 0481c1d..08067aa 100644 --- a/src/gui/mainmenu.ui +++ b/src/gui/mainmenu.ui @@ -14,8 +14,14 @@ <property name="title"> <string>File</string> </property> + <widget class="QMenu" name="menuOpen_Recent"> + <property name="title"> + <string>Open Recent</string> + </property> + </widget> <addaction name="actionNew"/> <addaction name="actionOpen"/> + <addaction name="menuOpen_Recent"/> <addaction name="separator"/> <addaction name="actionClose"/> <addaction name="actionSave"/> @@ -69,11 +75,14 @@ </widget> <widget class="QMenu" name="menuTikz"> <property name="title"> - <string>Tikz</string> + <string>Tools</string> </property> <addaction name="actionParse"/> <addaction name="actionRevert"/> <addaction name="actionJump_to_Selection"/> + <addaction name="actionRun_LaTeX"/> + <addaction name="separator"/> + <addaction name="actionPreferences"/> </widget> <widget class="QMenu" name="menuView"> <property name="title"> @@ -197,7 +206,7 @@ </action> <action name="actionParse"> <property name="text"> - <string>Parse Tikz</string> + <string>Parse TikZ</string> </property> <property name="shortcut"> <string>Ctrl+T</string> @@ -226,7 +235,10 @@ </action> <action name="actionRevert"> <property name="text"> - <string>Revert Tikz</string> + <string>Revert TikZ</string> + </property> + <property name="shortcut"> + <string>Ctrl+Alt+T</string> </property> </action> <action name="actionJump_to_Selection"> @@ -335,6 +347,24 @@ <string>About</string> </property> </action> + <action name="actionRun_LaTeX"> + <property name="text"> + <string>Make Preview</string> + </property> + <property name="shortcut"> + <string>Ctrl+R</string> + </property> + </action> + <action name="actionClear_Menu"> + <property name="text"> + <string>Clear Menu</string> + </property> + </action> + <action name="actionPreferences"> + <property name="text"> + <string>Preferences...</string> + </property> + </action> <addaction name="menuFile"/> <addaction name="menuEdit"/> <addaction name="menuView"/> diff --git a/src/gui/preferencedialog.cpp b/src/gui/preferencedialog.cpp new file mode 100644 index 0000000..06159af --- /dev/null +++ b/src/gui/preferencedialog.cpp @@ -0,0 +1,111 @@ +#include "preferencedialog.h" +#include "ui_preferencedialog.h" + +#include <QColorDialog> +#include <QFileDialog> +#include <QSettings> + +PreferenceDialog::PreferenceDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::PreferenceDialog) +{ + ui->setupUi(this); + QSettings settings("tikzit", "tikzit"); + ui->autoPdflatex->setChecked(true); + + if (!settings.value("auto-detect-pdflatex").isNull()) + ui->autoPdflatex->setChecked(settings.value("auto-detect-pdflatex").toBool()); + if (!settings.value("pdflatex-path").isNull()) + ui->pdflatexPath->setText(settings.value("pdflatex-path").toString()); + + + setColor(ui->axesColor, settings.value("grid-color-axes", + QColor(220,220,240)).value<QColor>()); + setColor(ui->majorColor, settings.value("grid-color-major", + QColor(240,240,250)).value<QColor>()); + setColor(ui->minorColor, settings.value("grid-color-minor", + QColor(250,250,255)).value<QColor>()); + + + connect(ui->axesColor, SIGNAL(clicked()), this, SLOT(colorClick())); + connect(ui->majorColor, SIGNAL(clicked()), this, SLOT(colorClick())); + connect(ui->minorColor, SIGNAL(clicked()), this, SLOT(colorClick())); +} + +PreferenceDialog::~PreferenceDialog() +{ + delete ui; +} + +void PreferenceDialog::accept() +{ + QSettings settings("tikzit", "tikzit"); + settings.setValue("auto-detect-pdflatex", ui->autoPdflatex->isChecked()); + settings.setValue("pdflatex-path", ui->pdflatexPath->text()); + settings.setValue("grid-color-axes", color(ui->axesColor)); + settings.setValue("grid-color-major", color(ui->majorColor)); + settings.setValue("grid-color-minor", color(ui->minorColor)); + QDialog::accept(); +} + +void PreferenceDialog::on_resetColors_clicked() +{ + setColor(ui->axesColor, QColor(220,220,240)); + setColor(ui->majorColor, QColor(240,240,250)); + setColor(ui->minorColor, QColor(250,250,255)); +} + +void PreferenceDialog::colorClick() +{ + if (QPushButton *btn = dynamic_cast<QPushButton*>(sender())) { + QColor col = QColorDialog::getColor( + color(btn), + this, + "Set color", + QColorDialog::DontUseNativeDialog); + if (col.isValid()) setColor(btn, col); + } +} + +void PreferenceDialog::on_autoPdflatex_stateChanged(int state) +{ + ui->pdflatexPath->setEnabled(state != Qt::Checked); + ui->browsePdflatex->setEnabled(state != Qt::Checked); +} + +void PreferenceDialog::on_browsePdflatex_clicked() +{ + QSettings settings("tikzit", "tikzit"); + + QFileDialog dialog; + dialog.setWindowTitle(tr("pdflatex Path")); + dialog.setAcceptMode(QFileDialog::AcceptOpen); + dialog.setFileMode(QFileDialog::ExistingFile); + dialog.setLabelText(QFileDialog::Accept, "Select"); + + QFileInfo fi(ui->pdflatexPath->text()); + if (!fi.absolutePath().isEmpty()) { + dialog.setDirectory(fi.absolutePath()); + dialog.selectFile(fi.baseName()); + } + + dialog.setOption(QFileDialog::DontUseNativeDialog); + + if (dialog.exec()) { + ui->pdflatexPath->setText(QDir::toNativeSeparators(dialog.selectedFiles()[0])); + } +} + +void PreferenceDialog::setColor(QPushButton *btn, QColor col) +{ + QPalette pal = btn->palette(); + pal.setColor(QPalette::Button, col); + btn->setPalette(pal); + btn->update(); +} + +QColor PreferenceDialog::color(QPushButton *btn) +{ + QPalette pal = btn->palette(); + return pal.color(QPalette::Button); +} diff --git a/src/gui/preferencedialog.h b/src/gui/preferencedialog.h new file mode 100644 index 0000000..9da8ae6 --- /dev/null +++ b/src/gui/preferencedialog.h @@ -0,0 +1,31 @@ +#ifndef PREFERENCEDIALOG_H +#define PREFERENCEDIALOG_H + +#include <QDialog> + +namespace Ui { +class PreferenceDialog; +} + +class PreferenceDialog : public QDialog +{ + Q_OBJECT + +public: + explicit PreferenceDialog(QWidget *parent = nullptr); + ~PreferenceDialog() override; + +protected slots: + void accept() override; + void colorClick(); + void on_resetColors_clicked(); + void on_autoPdflatex_stateChanged(int state); + void on_browsePdflatex_clicked(); + +private: + Ui::PreferenceDialog *ui; + QColor color(QPushButton *btn); + void setColor(QPushButton *btn, QColor col); +}; + +#endif // PREFERENCEDIALOG_H diff --git a/src/gui/preferencedialog.ui b/src/gui/preferencedialog.ui new file mode 100644 index 0000000..9a32e7d --- /dev/null +++ b/src/gui/preferencedialog.ui @@ -0,0 +1,272 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>PreferenceDialog</class> + <widget class="QDialog" name="PreferenceDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>345</width> + <height>176</height> + </rect> + </property> + <property name="windowTitle"> + <string>Dialog</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <layout class="QFormLayout" name="formLayout"> + <item row="0" column="0"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>pdflatex Location</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QLineEdit" name="pdflatexPath"/> + </item> + <item> + <widget class="QToolButton" name="browsePdflatex"> + <property name="text"> + <string>...</string> + </property> + </widget> + </item> + </layout> + </item> + <item row="1" column="1"> + <widget class="QCheckBox" name="autoPdflatex"> + <property name="text"> + <string>Automatically detect pdflatex</string> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>Grid colors</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <widget class="QFrame" name="frame"> + <property name="frameShape"> + <enum>QFrame::Box</enum> + </property> + <property name="frameShadow"> + <enum>QFrame::Plain</enum> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_3"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QPushButton" name="axesColor"> + <property name="autoFillBackground"> + <bool>true</bool> + </property> + <property name="text"> + <string/> + </property> + <property name="flat"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QLabel" name="label_4"> + <property name="text"> + <string>Axes</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QFrame" name="frame_2"> + <property name="frameShape"> + <enum>QFrame::Box</enum> + </property> + <property name="frameShadow"> + <enum>QFrame::Plain</enum> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_4"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QPushButton" name="majorColor"> + <property name="autoFillBackground"> + <bool>true</bool> + </property> + <property name="text"> + <string/> + </property> + <property name="flat"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QLabel" name="label_5"> + <property name="text"> + <string>Major</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QFrame" name="frame_3"> + <property name="frameShape"> + <enum>QFrame::Box</enum> + </property> + <property name="frameShadow"> + <enum>QFrame::Plain</enum> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_5"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QPushButton" name="minorColor"> + <property name="autoFillBackground"> + <bool>true</bool> + </property> + <property name="text"> + <string/> + </property> + <property name="flat"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QLabel" name="label_6"> + <property name="text"> + <string>Minor</string> + </property> + </widget> + </item> + </layout> + </item> + <item row="3" column="1"> + <widget class="QPushButton" name="resetColors"> + <property name="text"> + <string>Reset colors</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>PreferenceDialog</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>248</x> + <y>254</y> + </hint> + <hint type="destinationlabel"> + <x>157</x> + <y>274</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>PreferenceDialog</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>316</x> + <y>260</y> + </hint> + <hint type="destinationlabel"> + <x>286</x> + <y>274</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/src/gui/previewwindow.cpp b/src/gui/previewwindow.cpp new file mode 100644 index 0000000..2f47efd --- /dev/null +++ b/src/gui/previewwindow.cpp @@ -0,0 +1,208 @@ +/* + 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 "previewwindow.h" +#include "ui_previewwindow.h" + +#include "tikzit.h" +#include "latexprocess.h" +#include "exportdialog.h" + +#include <QLabel> +#include <QImage> +#include <QPixmap> +#include <QDebug> +#include <QSettings> +#include <QTemporaryDir> +#include <QFile> +#include <QTextStream> +#include <QStandardPaths> +#include <QMessageBox> +#include <cmath> +#include <QMovie> +#include <QAction> + +PreviewWindow::PreviewWindow(QWidget *parent) : + QDialog(parent), + ui(new Ui::PreviewWindow) +{ + QSettings settings("tikzit", "tikzit"); + ui->setupUi(this); + + QVariant geom = settings.value("geometry-preview"); + + if (geom.isValid()) { + restoreGeometry(geom.toByteArray()); + } + + _doc = nullptr; + + _loader = new QLabel(ui->tabWidget->tabBar()); + _loader->setMinimumSize(QSize(16,16)); + _loader->setMaximumSize(QSize(16,16)); + _loader->setAutoFillBackground(false); + ui->tabWidget->tabBar()->setTabButton(1, QTabBar::RightSide, _loader); + + connect(ui->tabWidget, SIGNAL(currentChanged(int)), + this, SLOT(render())); + + render(); +} + +void PreviewWindow::contextMenuEvent(QContextMenuEvent *event) +{ + QMenu menu(this); + QAction *act; + + act = new QAction("Export Image..."); + connect(act, SIGNAL(triggered()), this, SLOT(exportImage())); + menu.addAction(act); + + act = new QAction("Copy to Clipboard"); + connect(act, SIGNAL(triggered()), this, SLOT(copyImageToClipboard())); + menu.addAction(act); + + menu.exec(event->globalPos()); +} + +PdfDocument *PreviewWindow::doc() const +{ + return _doc; +} + +PreviewWindow::~PreviewWindow() +{ + delete ui; +} + +void PreviewWindow::setPdf(QString file) +{ + // use loadFromData to avoid holding a lock on the PDF file in windows + //QFile f(file); + //f.open(QFile::ReadOnly); + //QByteArray data = f.readAll(); + //f.close(); + PdfDocument *newDoc = new PdfDocument(file, this); + + if (newDoc->isValid()) { + PdfDocument *oldDoc = _doc; + _doc = newDoc; + if (oldDoc != nullptr) delete oldDoc; + render(); + } else { + QMessageBox::warning(nullptr, + "Could not read PDF", + "Could not read: '" + file + "'."); + delete newDoc; + } +} + +QPlainTextEdit *PreviewWindow::outputTextEdit() +{ + return ui->output; +} + +void PreviewWindow::setStatus(PreviewWindow::Status status) +{ + QMovie *oldMovie = _loader->movie(); + if (status == PreviewWindow::Running) { + // loader.gif and loader@2x.gif derived from: + // https://commons.wikimedia.org/wiki/Throbbers#/media/File:Linux_Ubuntu_Loader.gif + // licensed GNU Free Documentation License v1.2 + QMovie *movie = new QMovie( + (devicePixelRatioF() > 1.0) ? ":images/loader@2x.gif" : ":images/loader.gif", + QByteArray(), _loader); + _loader->setPixmap(QPixmap()); + _loader->setMovie(movie); + movie->start(); + } else if (status == PreviewWindow::Success) { + _loader->setMovie(nullptr); + QIcon accept(":images/dialog-accept.svg"); + //accept.setDevicePixelRatio(devicePixelRatio()); + _loader->setPixmap(accept.pixmap(QSize(16,16))); + } else if (status == PreviewWindow::Failed) { + _loader->setMovie(nullptr); + QIcon error(":images/dialog-error.svg"); + //error.setDevicePixelRatio(devicePixelRatio()); + _loader->setPixmap(error.pixmap(QSize(16,16))); + } + + if (oldMovie != nullptr) oldMovie->deleteLater(); + + + _loader->repaint(); +} + +void PreviewWindow::closeEvent(QCloseEvent *e) { + QSettings settings("tikzit", "tikzit"); + settings.setValue("geometry-preview", saveGeometry()); + QDialog::closeEvent(e); +} + +void PreviewWindow::resizeEvent(QResizeEvent *e) { + render(); + QDialog::resizeEvent(e); +} + +void PreviewWindow::showEvent(QShowEvent *e) { + render(); + QDialog::showEvent(e); +} + +void PreviewWindow::render() { + if (_doc != nullptr) { + _doc->renderTo(ui->pdf, + ui->scrollArea->visibleRegion().boundingRect()); + ui->pdf->repaint(); + } +} + +void PreviewWindow::exportImage() +{ + QSettings settings("tikzit", "tikzit"); + if (_doc == nullptr) return; + ExportDialog *d = new ExportDialog(this); + int ret = d->exec(); + if (ret == QDialog::Accepted) { + bool success; + if (d->fileFormat() == ExportDialog::PDF) { + success = _doc->exportPdf(d->filePath()); + } else { + success = _doc->exportImage( + d->filePath(), + (d->fileFormat() == ExportDialog::PNG) ? "PNG" : "JPG", + d->size()); + } + + if (!success) { + QMessageBox::warning(this, + "Error", + "Could not write to: '" + d->filePath() + + "'. Check file permissions or choose a new location."); + } + } +} + +void PreviewWindow::copyImageToClipboard() +{ + if (_doc != nullptr) { + _doc->copyImageToClipboard(_doc->size() * 4); + } +} + + diff --git a/src/gui/previewwindow.h b/src/gui/previewwindow.h new file mode 100644 index 0000000..a14303b --- /dev/null +++ b/src/gui/previewwindow.h @@ -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/>. +*/ + +/*! + * Displays a LaTeX-generated PDF preview using Poppler. The right-click + * menu has options for exporting to file or clipboard. + */ + +#ifndef PREVIEWWINDOW_H +#define PREVIEWWINDOW_H + +#include "pdfdocument.h" + +#include <QDialog> +#include <QLabel> +#include <QPlainTextEdit> +#include <QContextMenuEvent> +#include <poppler/qt5/poppler-qt5.h> + +namespace Ui { +class PreviewWindow; +} + +class PreviewWindow : public QDialog +{ + Q_OBJECT + +public: + enum Status { + Running, Success, Failed + }; + explicit PreviewWindow(QWidget *parent = nullptr); + ~PreviewWindow() override; + void setPdf(QString file); + QString preparePreview(QString tikz); + QPlainTextEdit *outputTextEdit(); + void setStatus(Status status); + + PdfDocument *doc() const; + +public slots: + void render(); + void exportImage(); + void copyImageToClipboard(); + +protected: + void resizeEvent(QResizeEvent *e) override; + void showEvent(QShowEvent *e) override; + void closeEvent(QCloseEvent *e) override; + void contextMenuEvent(QContextMenuEvent *event) override; +private: + Ui::PreviewWindow *ui; + PdfDocument *_doc; + QLabel *_loader; +}; + +#endif // PREVIEWWINDOW_H diff --git a/src/gui/previewwindow.ui b/src/gui/previewwindow.ui new file mode 100644 index 0000000..aa49980 --- /dev/null +++ b/src/gui/previewwindow.ui @@ -0,0 +1,107 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>PreviewWindow</class> + <widget class="QDialog" name="PreviewWindow"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>603</width> + <height>480</height> + </rect> + </property> + <property name="windowTitle"> + <string>Preview</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <property name="leftMargin"> + <number>3</number> + </property> + <property name="topMargin"> + <number>3</number> + </property> + <property name="rightMargin"> + <number>3</number> + </property> + <property name="bottomMargin"> + <number>3</number> + </property> + <item> + <widget class="QTabWidget" name="tabWidget"> + <property name="currentIndex"> + <number>0</number> + </property> + <property name="documentMode"> + <bool>true</bool> + </property> + <property name="tabsClosable"> + <bool>false</bool> + </property> + <widget class="QWidget" name="pdfTab"> + <attribute name="title"> + <string>PDF</string> + </attribute> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QScrollArea" name="scrollArea"> + <property name="verticalScrollBarPolicy"> + <enum>Qt::ScrollBarAlwaysOff</enum> + </property> + <property name="horizontalScrollBarPolicy"> + <enum>Qt::ScrollBarAlwaysOff</enum> + </property> + <property name="widgetResizable"> + <bool>true</bool> + </property> + <widget class="QWidget" name="scrollAreaWidgetContents"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>595</width> + <height>451</height> + </rect> + </property> + <layout class="QVBoxLayout" name="verticalLayout_4"> + <item> + <widget class="QLabel" name="pdf"> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </widget> + </widget> + </item> + </layout> + </widget> + <widget class="QWidget" name="outputTab"> + <attribute name="title"> + <string>Output</string> + </attribute> + <layout class="QVBoxLayout" name="verticalLayout_3"> + <item> + <widget class="QPlainTextEdit" name="output"/> + </item> + </layout> + </widget> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/src/gui/styleeditor.cpp b/src/gui/styleeditor.cpp index 29192d6..e2ade45 100644 --- a/src/gui/styleeditor.cpp +++ b/src/gui/styleeditor.cpp @@ -1,10 +1,30 @@ +/* + 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 <QColorDialog> #include <QDebug> #include <QMessageBox> #include "tikzit.h" #include "styleeditor.h" +#include "delimitedstringvalidator.h" #include "ui_styleeditor.h" +#include "delimitedstringitemdelegate.h" StyleEditor::StyleEditor(QWidget *parent) : QMainWindow(parent), @@ -18,6 +38,14 @@ StyleEditor::StyleEditor(QWidget *parent) : ui->leftArrow << ui->rightArrow << ui->properties; + DelimitedStringValidator *v = new DelimitedStringValidator(this); + ui->name->setValidator(v); + ui->category->lineEdit()->setValidator(v); + ui->shape->lineEdit()->setValidator(v); + + DelimitedStringItemDelegate *delegate = new DelimitedStringItemDelegate(ui->properties); + ui->properties->setItemDelegate(delegate); + setWindowIcon(QIcon(":/images/tikzit.png")); _styles = nullptr; _activeStyle = nullptr; @@ -44,40 +72,6 @@ StyleEditor::StyleEditor(QWidget *parent) : SIGNAL(currentIndexChanged(int)), this, SLOT(shapeChanged())); - // setup the color dialog to display only the named colors that tikzit/xcolor knows - // about as "standard colors". - for (int i = 0; i < 48; ++i) { - QColorDialog::setStandardColor(i, QColor(Qt::white)); - } - - // grayscale in column 1 - int pos = 0; - for (int i=0; i < 5; ++i) { - QColorDialog::setStandardColor(pos, tikzit->colorByIndex(i)); - pos += 1; - } - - // rainbow in column 2 - pos = 6; - for (int i=5; i < 11; ++i) { - QColorDialog::setStandardColor(pos, tikzit->colorByIndex(i)); - pos += 1; - } - - // brown/green/teal spectrum in column 3 - pos = 12; - for (int i=11; i < 16; ++i) { - QColorDialog::setStandardColor(pos, tikzit->colorByIndex(i)); - pos += 1; - } - - // pinks in column 4 - pos = 18; - for (int i=16; i < 19; ++i) { - QColorDialog::setStandardColor(pos, tikzit->colorByIndex(i)); - pos += 1; - } - refreshDisplay(); } diff --git a/src/gui/styleeditor.h b/src/gui/styleeditor.h index 4bae7db..2c35d56 100644 --- a/src/gui/styleeditor.h +++ b/src/gui/styleeditor.h @@ -1,3 +1,25 @@ +/* + 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 GUI editor for .tikzstyles files. + */ + #ifndef STYLEEDITOR_H #define STYLEEDITOR_H @@ -17,8 +39,8 @@ class StyleEditor : public QMainWindow Q_OBJECT public: - explicit StyleEditor(QWidget *parent = 0); - ~StyleEditor(); + explicit StyleEditor(QWidget *parent = nullptr); + ~StyleEditor() override; void open(); void save(); diff --git a/src/gui/tikzscene.cpp b/src/gui/tikzscene.cpp index c061221..31d5bf6 100644 --- a/src/gui/tikzscene.cpp +++ b/src/gui/tikzscene.cpp @@ -28,14 +28,15 @@ #include <QClipboard> #include <QInputDialog> #include <cmath> +#include <delimitedstringvalidator.h> TikzScene::TikzScene(TikzDocument *tikzDocument, ToolPalette *tools, StylePalette *styles, QObject *parent) : QGraphicsScene(parent), _tikzDocument(tikzDocument), _tools(tools), _styles(styles) { - _modifyEdgeItem = 0; - _edgeStartNodeItem = 0; + _modifyEdgeItem = nullptr; + _edgeStartNodeItem = nullptr; _drawEdgeItem = new QGraphicsLineItem(); _rubberBandItem = new QGraphicsRectItem(); _enabled = true; @@ -43,7 +44,7 @@ TikzScene::TikzScene(TikzDocument *tikzDocument, ToolPalette *tools, setSceneRect(-1000,-1000,2000,2000); QPen pen; - pen.setColor(QColor::fromRgbF(0.5f, 0.0f, 0.5f)); + pen.setColor(QColor::fromRgbF(0.5, 0.0, 0.5)); //pen.setWidth(3.0f); pen.setCosmetic(true); _drawEdgeItem->setPen(pen); @@ -51,7 +52,7 @@ TikzScene::TikzScene(TikzDocument *tikzDocument, ToolPalette *tools, _drawEdgeItem->setVisible(false); addItem(_drawEdgeItem); - pen.setColor(QColor::fromRgbF(0.6f, 0.6f, 0.8f)); + pen.setColor(QColor::fromRgbF(0.6, 0.6, 0.8)); //pen.setWidth(3.0f); //QVector<qreal> dash; //dash << 4.0 << 4.0; @@ -110,7 +111,7 @@ void TikzScene::graphReplaced() void TikzScene::extendSelectionUp() { bool found = false; - float m = 0.0f; + qreal m = 0.0; foreach (Node *n, getSelectedNodes()) { if (!found) { m = n->point().y(); @@ -128,7 +129,7 @@ void TikzScene::extendSelectionUp() void TikzScene::extendSelectionDown() { bool found = false; - float m = 0.0f; + qreal m = 0.0; foreach (Node *n, getSelectedNodes()) { if (!found) { m = n->point().y(); @@ -146,7 +147,7 @@ void TikzScene::extendSelectionDown() void TikzScene::extendSelectionLeft() { bool found = false; - float m = 0.0f; + qreal m = 0.0; foreach (Node *n, getSelectedNodes()) { if (!found) { m = n->point().x(); @@ -164,7 +165,7 @@ void TikzScene::extendSelectionLeft() void TikzScene::extendSelectionRight() { bool found = false; - float m = 0.0f; + qreal m = 0.0; foreach (Node *n, getSelectedNodes()) { if (!found) { m = n->point().x(); @@ -266,7 +267,7 @@ void TikzScene::mousePressEvent(QGraphicsSceneMouseEvent *event) } } - if (_modifyEdgeItem != 0) { + if (_modifyEdgeItem != nullptr) { // store for undo purposes Edge *e = _modifyEdgeItem->edge(); _oldBend = e->bend(); @@ -337,15 +338,15 @@ void TikzScene::mouseMoveEvent(QGraphicsSceneMouseEvent *event) switch (_tools->currentTool()) { case ToolPalette::SELECT: - if (_modifyEdgeItem != 0) { + if (_modifyEdgeItem != nullptr) { Edge *e = _modifyEdgeItem->edge(); // dragging a control point QPointF src = toScreen(e->source()->point()); QPointF targ = toScreen(e->target()->point()); - float dx1 = targ.x() - src.x(); - float dy1 = targ.y() - src.y(); - float dx2, dy2; + qreal dx1 = targ.x() - src.x(); + qreal dy1 = targ.y() - src.y(); + qreal dx2, dy2; if (_firstControlPoint) { dx2 = mousePos.x() - src.x(); dy2 = mousePos.y() - src.y(); @@ -354,25 +355,26 @@ void TikzScene::mouseMoveEvent(QGraphicsSceneMouseEvent *event) dy2 = mousePos.y() - targ.y(); } - float baseDist = sqrt(dx1*dx1 + dy1*dy1); - float handleDist = sqrt(dx2*dx2 + dy2*dy2); - float wcoarseness = 0.1f; + qreal baseDist = sqrt(dx1*dx1 + dy1*dy1); + qreal handleDist = sqrt(dx2*dx2 + dy2*dy2); + qreal wcoarseness = 0.1; if (!e->isSelfLoop()) { - if (baseDist != 0) { + if (baseDist != 0.0) { e->setWeight(roundToNearest(wcoarseness, handleDist/baseDist)); } else { e->setWeight(roundToNearest(wcoarseness, handleDist/GLOBAL_SCALEF)); } } - float control_angle = atan2(-dy2, dx2); + qreal control_angle = atan2(-dy2, dx2); int bcoarseness = 15; + qreal bcoarsenessi = 1.0/15.0; if(e->basicBendMode()) { - float bnd; - float base_angle = atan2(-dy1, dx1); + qreal bnd; + qreal base_angle = atan2(-dy1, dx1); if (_firstControlPoint) { bnd = base_angle - control_angle; } else { @@ -380,12 +382,10 @@ void TikzScene::mouseMoveEvent(QGraphicsSceneMouseEvent *event) if (bnd > M_PI) bnd -= 2*M_PI; } - e->setBend(round(bnd * (180.0f / M_PI) * (1.0f / (float)bcoarseness)) * bcoarseness); + e->setBend(static_cast<int>(round(bnd * (180.0 / M_PI) * bcoarsenessi)) * bcoarseness); } else { - int bnd = round(control_angle * (180.0f / M_PI) * - (1.0f / (float)bcoarseness)) * - bcoarseness; + int bnd = static_cast<int>(round(control_angle * (180.0 / M_PI) * bcoarsenessi)) * bcoarseness; if (_firstControlPoint) { // TODO: enable moving both control points // if ([theEvent modifierFlags] & NSAlternateKeyMask) { @@ -428,7 +428,7 @@ void TikzScene::mouseMoveEvent(QGraphicsSceneMouseEvent *event) NodeItem *ni = _nodeItems[n]; // in (rare) cases, the graph can change while we are dragging - if (ni != 0) { + if (ni != nullptr) { ni->setPos(toScreen(_oldNodePositions[n]) + shift); ni->writePos(); } @@ -454,7 +454,7 @@ void TikzScene::mouseMoveEvent(QGraphicsSceneMouseEvent *event) break; case ToolPalette::EDGE: if (_drawEdgeItem->isVisible()) { - _edgeEndNodeItem = 0; + _edgeEndNodeItem = nullptr; foreach (QGraphicsItem *gi, items(mousePos)) { if (NodeItem *ni = dynamic_cast<NodeItem*>(gi)){ _edgeEndNodeItem = ni; @@ -462,7 +462,7 @@ void TikzScene::mouseMoveEvent(QGraphicsSceneMouseEvent *event) } } QPointF p1 = _drawEdgeItem->line().p1(); - QPointF p2 = (_edgeEndNodeItem != 0) ? toScreen(_edgeEndNodeItem->node()->point()) : mousePos; + QPointF p2 = (_edgeEndNodeItem != nullptr) ? toScreen(_edgeEndNodeItem->node()->point()) : mousePos; QLineF line(p1, p2); _drawEdgeItem->setLine(line); @@ -482,11 +482,11 @@ void TikzScene::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) switch (_tools->currentTool()) { case ToolPalette::SELECT: - if (_modifyEdgeItem != 0) { + if (_modifyEdgeItem != nullptr) { // finished dragging a control point Edge *e = _modifyEdgeItem->edge(); - if (_oldWeight != e->weight() || + if (!almostEqual(_oldWeight, e->weight()) || _oldBend != e->bend() || _oldInAngle != e->inAngle() || _oldOutAngle != e->outAngle()) @@ -495,7 +495,7 @@ void TikzScene::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) _tikzDocument->undoStack()->push(cmd); } - _modifyEdgeItem = 0; + _modifyEdgeItem = nullptr; } else { // otherwise, process mouse move normally QGraphicsScene::mouseReleaseEvent(event); @@ -517,7 +517,7 @@ void TikzScene::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) QPointF shift = mousePos - _mouseDownPos; shift = QPointF(round(shift.x()/GRID_SEP)*GRID_SEP, round(shift.y()/GRID_SEP)*GRID_SEP); - if (shift.x() != 0 || shift.y() != 0) { + if (shift.x() != 0.0 || shift.y() != 0.0) { QMap<Node*,QPointF> newNodePositions; foreach (QGraphicsItem *gi, selectedItems()) { @@ -557,14 +557,14 @@ void TikzScene::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) break; case ToolPalette::EDGE: // add an edge - if (_edgeStartNodeItem != 0 && _edgeEndNodeItem != 0) { + if (_edgeStartNodeItem != nullptr && _edgeEndNodeItem != nullptr) { Edge *e = new Edge(_edgeStartNodeItem->node(), _edgeEndNodeItem->node(), _tikzDocument); e->setStyleName(_styles->activeEdgeStyleName()); AddEdgeCommand *cmd = new AddEdgeCommand(this, e); _tikzDocument->undoStack()->push(cmd); } - _edgeStartNodeItem = 0; - _edgeEndNodeItem = 0; + _edgeStartNodeItem = nullptr; + _edgeEndNodeItem = nullptr; _drawEdgeItem->setVisible(false); break; case ToolPalette::CROP: @@ -613,19 +613,19 @@ void TikzScene::keyPressEvent(QKeyEvent *event) if (event->modifiers() & Qt::ControlModifier) { QPointF delta(0,0); - float shift = (event->modifiers() & Qt::ShiftModifier) ? 1.0f : 10.0f; + qreal shift = (event->modifiers() & Qt::ShiftModifier) ? 1.0 : 10.0; switch(event->key()) { case Qt::Key_Left: - delta.setX(-0.025f * shift); + delta.setX(-0.025 * shift); break; case Qt::Key_Right: - delta.setX(0.025f * shift); + delta.setX(0.025 * shift); break; case Qt::Key_Up: - delta.setY(0.025f * shift); + delta.setY(0.025 * shift); break; case Qt::Key_Down: - delta.setY(-0.025f * shift); + delta.setY(-0.025 * shift); break; } @@ -665,16 +665,23 @@ void TikzScene::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) } break; } else if (NodeItem *ni = dynamic_cast<NodeItem*>(it)) { - bool ok; - QString newLabel = QInputDialog::getText(views()[0], tr("Node label"), - tr("Label:"), QLineEdit::Normal, - ni->node()->label(), &ok); - if (ok) { + QInputDialog *d = new QInputDialog(views()[0]); + d->setLabelText(tr("Label:")); + d->setTextValue(ni->node()->label()); + d->setWindowTitle(tr("Node label")); + + if (QLineEdit *le = d->findChild<QLineEdit*>()) { + le->setValidator(new DelimitedStringValidator(le)); + } + + if (d->exec()) { QMap<Node*,QString> oldLabels; oldLabels.insert(ni->node(), ni->node()->label()); - ChangeLabelCommand *cmd = new ChangeLabelCommand(this, oldLabels, newLabel); + ChangeLabelCommand *cmd = new ChangeLabelCommand(this, oldLabels, d->textValue()); _tikzDocument->undoStack()->push(cmd); } + + d->deleteLater(); break; } } @@ -767,7 +774,7 @@ void TikzScene::pasteFromClipboard() QRectF srcRect = g->realBbox(); QRectF tgtRect = graph()->realBbox(); - QPointF shift(tgtRect.right() - srcRect.left(), 0.0f); + QPointF shift(tgtRect.right() - srcRect.left(), 0.0); if (shift.x() > 0) { foreach (Node *n, g->nodes()) { @@ -890,7 +897,7 @@ void TikzScene::refreshAdjacentEdges(QList<Node*> nodes) EdgeItem *ei = _edgeItems[e]; // the list "nodes" can be out of date, e.g. if the graph changes while dragging - if (ei != 0) { + if (ei != nullptr) { if (nodes.contains(ei->edge()->source()) || nodes.contains(ei->edge()->target())) { ei->edge()->updateControls(); ei->readPos(); diff --git a/src/gui/tikzscene.h b/src/gui/tikzscene.h index 2a3e988..e8ea2c6 100644 --- a/src/gui/tikzscene.h +++ b/src/gui/tikzscene.h @@ -44,7 +44,7 @@ class TikzScene : public QGraphicsScene Q_OBJECT public: TikzScene(TikzDocument *tikzDocument, ToolPalette *tools, StylePalette *styles, QObject *parent); - ~TikzScene(); + ~TikzScene() override; Graph *graph(); QMap<Node*,NodeItem*> &nodeItems(); QMap<Edge*,EdgeItem*> &edgeItems(); @@ -108,7 +108,7 @@ private: bool _draggingNodes; QMap<Node*,QPointF> _oldNodePositions; - float _oldWeight; + qreal _oldWeight; int _oldBend; int _oldInAngle; int _oldOutAngle; diff --git a/src/gui/tikzview.cpp b/src/gui/tikzview.cpp index 52a32cf..5b0f09c 100644 --- a/src/gui/tikzview.cpp +++ b/src/gui/tikzview.cpp @@ -21,6 +21,7 @@ #include <QDebug> #include <QScrollBar> +#include <QSettings> TikzView::TikzView(QWidget *parent) : QGraphicsView(parent) { @@ -53,6 +54,7 @@ void TikzView::setScene(QGraphicsScene *scene) void TikzView::drawBackground(QPainter *painter, const QRectF &rect) { + QSettings settings("tikzit", "tikzit"); QGraphicsView::drawBackground(painter, rect); // draw a gray background if disabled TikzScene *sc = static_cast<TikzScene*>(scene()); @@ -63,13 +65,13 @@ void TikzView::drawBackground(QPainter *painter, const QRectF &rect) QPen pen1; //pen1.setWidthF(0.5); pen1.setCosmetic(true); - pen1.setColor(QColor(250,250,255)); + pen1.setColor(settings.value("grid-color-minor", QColor(250,250,255)).value<QColor>()); QPen pen2 = pen1; - pen2.setColor(QColor(240,240,250)); + pen2.setColor(settings.value("grid-color-major", QColor(240,240,250)).value<QColor>()); QPen pen3 = pen1; - pen3.setColor(QColor(220,220,240)); + pen3.setColor(settings.value("grid-color-axes", QColor(220,220,240)).value<QColor>()); painter->setPen(pen1); diff --git a/src/gui/undocommands.cpp b/src/gui/undocommands.cpp index f713582..8a00536 100644 --- a/src/gui/undocommands.cpp +++ b/src/gui/undocommands.cpp @@ -80,7 +80,7 @@ void MoveCommand::redo() } EdgeBendCommand::EdgeBendCommand(TikzScene *scene, Edge *edge, - float oldWeight, int oldBend, + qreal oldWeight, int oldBend, int oldInAngle, int oldOutAngle, QUndoCommand *parent) : GraphUpdateCommand(scene, parent), _edge(edge), @@ -405,7 +405,7 @@ void ChangeLabelCommand::undo() foreach (Node *n, _oldLabels.keys()) { n->setLabel(_oldLabels[n]); NodeItem *ni = _scene->nodeItems()[n]; - if (ni != 0) ni->updateBounds(); + if (ni != nullptr) ni->updateBounds(); } GraphUpdateCommand::undo(); @@ -416,7 +416,7 @@ void ChangeLabelCommand::redo() foreach (Node *n, _oldLabels.keys()) { n->setLabel(_newLabel); NodeItem *ni = _scene->nodeItems()[n]; - if (ni != 0) ni->updateBounds(); + if (ni != nullptr) ni->updateBounds(); } GraphUpdateCommand::redo(); diff --git a/src/gui/undocommands.h b/src/gui/undocommands.h index dc60549..ff51c90 100644 --- a/src/gui/undocommands.h +++ b/src/gui/undocommands.h @@ -36,7 +36,7 @@ class GraphUpdateCommand : public QUndoCommand { public: explicit GraphUpdateCommand(TikzScene *scene, - QUndoCommand *parent = 0); + QUndoCommand *parent = nullptr); void undo() override; void redo() override; protected: @@ -49,7 +49,7 @@ public: explicit MoveCommand(TikzScene *scene, QMap<Node*,QPointF> oldNodePositions, QMap<Node*,QPointF> newNodePositions, - QUndoCommand *parent = 0); + QUndoCommand *parent = nullptr); void undo() override; void redo() override; private: @@ -61,18 +61,18 @@ class EdgeBendCommand : public GraphUpdateCommand { public: explicit EdgeBendCommand(TikzScene *scene, Edge *edge, - float oldWeight, int oldBend, + qreal oldWeight, int oldBend, int oldInAngle, int oldOutAngle, - QUndoCommand *parent = 0); + QUndoCommand *parent = nullptr); void undo() override; void redo() override; private: Edge *_edge; - float _oldWeight; + qreal _oldWeight; int _oldBend; int _oldInAngle; int _oldOutAngle; - float _newWeight; + qreal _newWeight; int _newBend; int _newInAngle; int _newOutAngle; @@ -85,7 +85,7 @@ public: QMap<int,Node*> deleteNodes, QMap<int,Edge*> deleteEdges, QSet<Edge*> selEdges, - QUndoCommand *parent = 0); + QUndoCommand *parent = nullptr); void undo() override; void redo() override; private: @@ -98,7 +98,7 @@ class AddNodeCommand : public GraphUpdateCommand { public: explicit AddNodeCommand(TikzScene *scene, Node *node, QRectF newBounds, - QUndoCommand *parent = 0); + QUndoCommand *parent = nullptr); void undo() override; void redo() override; private: @@ -110,7 +110,7 @@ private: class AddEdgeCommand : public GraphUpdateCommand { public: - explicit AddEdgeCommand(TikzScene *scene, Edge *edge, QUndoCommand *parent = 0); + explicit AddEdgeCommand(TikzScene *scene, Edge *edge, QUndoCommand *parent = nullptr); void undo() override; void redo() override; private: @@ -120,7 +120,7 @@ private: class ChangeEdgeModeCommand : public GraphUpdateCommand { public: - explicit ChangeEdgeModeCommand(TikzScene *scene, Edge *edge, QUndoCommand *parent = 0); + explicit ChangeEdgeModeCommand(TikzScene *scene, Edge *edge, QUndoCommand *parent = nullptr); void undo() override; void redo() override; private: @@ -130,7 +130,7 @@ private: class ApplyStyleToNodesCommand : public GraphUpdateCommand { public: - explicit ApplyStyleToNodesCommand(TikzScene *scene, QString style, QUndoCommand *parent = 0); + explicit ApplyStyleToNodesCommand(TikzScene *scene, QString style, QUndoCommand *parent = nullptr); void undo() override; void redo() override; private: @@ -141,7 +141,7 @@ private: class ApplyStyleToEdgesCommand : public GraphUpdateCommand { public: - explicit ApplyStyleToEdgesCommand(TikzScene *scene, QString style, QUndoCommand *parent = 0); + explicit ApplyStyleToEdgesCommand(TikzScene *scene, QString style, QUndoCommand *parent = nullptr); void undo() override; void redo() override; private: @@ -152,7 +152,7 @@ private: class PasteCommand : public GraphUpdateCommand { public: - explicit PasteCommand(TikzScene *scene, Graph *graph, QUndoCommand *parent = 0); + explicit PasteCommand(TikzScene *scene, Graph *graph, QUndoCommand *parent = nullptr); void undo() override; void redo() override; private: @@ -167,7 +167,7 @@ public: explicit ChangeLabelCommand(TikzScene *scene, QMap<Node*,QString> oldLabels, QString newLabel, - QUndoCommand *parent = 0); + QUndoCommand *parent = nullptr); void undo() override; void redo() override; private: @@ -181,7 +181,7 @@ public: explicit ReplaceGraphCommand(TikzScene *scene, Graph *oldGraph, Graph *newGraph, - QUndoCommand *parent = 0); + QUndoCommand *parent = nullptr); void undo() override; void redo() override; private: @@ -195,7 +195,7 @@ public: explicit ReflectNodesCommand(TikzScene *scene, QSet<Node*> nodes, bool horizontal, - QUndoCommand *parent = 0); + QUndoCommand *parent = nullptr); void undo() override; void redo() override; private: @@ -209,7 +209,7 @@ public: explicit RotateNodesCommand(TikzScene *scene, QSet<Node*> nodes, bool clockwise, - QUndoCommand *parent = 0); + QUndoCommand *parent = nullptr); void undo() override; void redo() override; private: @@ -225,7 +225,7 @@ public: const QVector<Node*> &newNodeOrder, const QVector<Edge*> &oldEdgeOrder, const QVector<Edge*> &newEdgeOrder, - QUndoCommand *parent = 0); + QUndoCommand *parent = nullptr); void undo() override; void redo() override; private: diff --git a/src/main.cpp b/src/main.cpp index 99d23e9..7a282e6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -16,7 +16,6 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. */ - /*! * \file main.cpp * @@ -31,21 +30,17 @@ #include <QDebug> #include <QScreen> -// #ifdef Q_OS_WIN -// #include <Windows.h> -// #endif - int main(int argc, char *argv[]) { // #ifdef Q_OS_WIN // SetProcessDPIAware(); // #endif -// QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + // QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); // dummy application for detecting DPI QApplication *a0 = new QApplication(argc, argv); -// qDebug() << "physical DPI" << QApplication::screens()[0]->physicalDotsPerInch(); + // qDebug() << "physical DPI" << QApplication::screens()[0]->physicalDotsPerInch(); if (QApplication::screens()[0]->physicalDotsPerInch() >= 100) { QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); @@ -58,14 +53,11 @@ int main(int argc, char *argv[]) QApplication a(argc, argv); a.setQuitOnLastWindowClosed(false); - - tikzit = new Tikzit(); tikzit->init(); qDebug() << a.arguments().length(); - if (a.arguments().length() > 1) { tikzit->open(a.arguments()[1]); } diff --git a/src/tikzit.cpp b/src/tikzit.cpp index c9286c9..8569817 100644 --- a/src/tikzit.cpp +++ b/src/tikzit.cpp @@ -19,6 +19,8 @@ #include "tikzit.h" #include "tikzassembler.h" #include "tikzstyles.h" +#include "previewwindow.h" +#include "latexprocess.h" #include <QFile> #include <QFileDialog> @@ -28,6 +30,7 @@ #include <QRegularExpression> #include <QVersionNumber> #include <QNetworkAccessManager> +#include <QColorDialog> // application-level instance of Tikzit Tikzit *tikzit; @@ -35,7 +38,7 @@ Tikzit *tikzit; // font to use for node labels QFont Tikzit::LABEL_FONT("Courrier", 9); -Tikzit::Tikzit() : _styleFile("[no styles]"), _activeWindow(0) +Tikzit::Tikzit() : _styleFile("[no styles]"), _activeWindow(nullptr) { } @@ -43,54 +46,7 @@ void Tikzit::init() { QSettings settings("tikzit", "tikzit"); - // 19 standard xcolor colours - _colNames << - "black" << - "darkgray" << - "gray" << - "lightgray" << - "white" << - - "red" << - "orange" << - "yellow" << - "green" << - "blue" << - "purple" << - - "brown" << - "olive" << - "lime" << - "cyan" << - "teal" << - - "magenta" << - "violet" << - "pink"; - - _cols << - QColor::fromRgbF(0,0,0) << - QColor::fromRgbF(0.25,0.25,0.25) << - QColor::fromRgbF(0.5,0.5,0.5) << - QColor::fromRgbF(0.75,0.75,0.75) << - QColor::fromRgbF(1,1,1) << - - QColor::fromRgbF(1,0,0) << - QColor::fromRgbF(1,0.5,0) << - QColor::fromRgbF(1,1,0) << - QColor::fromRgbF(0,1,0) << - QColor::fromRgbF(0,0,1) << - QColor::fromRgbF(0.75,0,0.25) << - - QColor::fromRgbF(0.75,0.5,0.25) << - QColor::fromRgbF(0.5,0.5,0) << - QColor::fromRgbF(0.75,1,0) << - QColor::fromRgbF(0,1,1) << - QColor::fromRgbF(0,0.5,0.5) << - - QColor::fromRgbF(1,0,1) << - QColor::fromRgbF(0.5,0,0.5) << - QColor::fromRgbF(1,0.75,0.75); + initColors(); _mainMenu = new MainMenu(); QMainWindow *dummy = new QMainWindow(); @@ -106,12 +62,14 @@ void Tikzit::init() _windows << new MainWindow(); _windows[0]->show(); + _styleFile = ""; + _styleFilePath = ""; QString styleFile = settings.value("previous-tikzstyles-file").toString(); if (!styleFile.isEmpty()) loadStyles(styleFile); QVariant check = settings.value("check-for-updates"); if (check.isNull()) { - int resp = QMessageBox::question(0, + int resp = QMessageBox::question(nullptr, tr("Check for updates"), tr("Would you like TikZiT to check for updates automatically?" " (You can always change this later in the Help menu.)"), @@ -124,8 +82,11 @@ void Tikzit::init() setCheckForUpdates(check.toBool()); if (check.toBool()) { - checkForUpdates(); + checkForUpdates(false); } + + _preview = new PreviewWindow(); + _latex = nullptr; } //QMenuBar *Tikzit::mainMenu() const @@ -190,7 +151,7 @@ void Tikzit::newTikzStyles() if (dialog.exec() && !dialog.selectedFiles().isEmpty()) { QString fileName = dialog.selectedFiles()[0]; - TikzStyles *st = new TikzStyles; + TikzStyles *st = new TikzStyles(this); if (st->saveStyles(fileName)) { QFileInfo fi(fileName); @@ -198,14 +159,14 @@ void Tikzit::newTikzStyles() _styleFilePath = fi.absoluteFilePath(); settings.setValue("previous-tikzstyles-file", fileName); settings.setValue("previous-tikzstyles-path", fi.absolutePath()); - delete _styles; + _styles->deleteLater(); _styles = st; foreach (MainWindow *w, _windows) { w->tikzScene()->reloadStyles(); } } else { - QMessageBox::warning(0, + QMessageBox::warning(nullptr, "Could not write to style file.", "Could not write to: '" + fileName + "'. Check file permissions or choose a new location."); } @@ -244,7 +205,7 @@ void Tikzit::removeWindow(MainWindow *w) _windows.removeAll(w); if (_activeWindow == w) { if (_windows.isEmpty()) { - _activeWindow = 0; + _activeWindow = nullptr; // TODO: check if we should quit when last window closed quit(); } else _activeWindow = _windows[0]; @@ -254,7 +215,7 @@ void Tikzit::removeWindow(MainWindow *w) void Tikzit::open() { QSettings settings("tikzit", "tikzit"); - QString fileName = QFileDialog::getOpenFileName(0, + QString fileName = QFileDialog::getOpenFileName(nullptr, tr("Open File"), settings.value("previous-file-path").toString(), tr("TiKZ Files (*.tikz)"), @@ -269,23 +230,35 @@ void Tikzit::open(QString fileName) if (!fileName.isEmpty()) { if (_windows.size() == 1 && _windows[0]->tikzDocument()->isClean() && - _windows[0]->tikzDocument()->shortName().isEmpty()) - { + _windows[0]->tikzDocument()->shortName().isEmpty()) + { _windows[0]->open(fileName); _windows[0]->show(); - } - else { - MainWindow *w = new MainWindow(); - w->show(); - w->open(fileName); - _windows << w; + } + else + { + bool found = false; + foreach (MainWindow *w, _windows) { + if (w->tikzDocument()->fileName() == fileName) { + w->raise(); + w->activateWindow(); + found = true; + } + } + + if (!found) { + MainWindow *w = new MainWindow(); + _windows << w; + w->show(); + w->open(fileName); + } } } } void Tikzit::openTikzStyles() { QSettings settings("tikzit", "tikzit"); - QString fileName = QFileDialog::getOpenFileName(0, + QString fileName = QFileDialog::getOpenFileName(nullptr, tr("Open File"), settings.value("previous-tikzstyles-path").toString(), tr("TiKZ Style Files (*.tikzstyles)"), @@ -310,7 +283,7 @@ bool Tikzit::loadStyles(QString fileName) if (st->loadStyles(fileName)) { _styleFile = fi.fileName(); _styleFilePath = fi.absoluteFilePath(); - delete _styles; + _styles->deleteLater(); _styles = st; foreach (MainWindow *w, _windows) { @@ -318,7 +291,7 @@ bool Tikzit::loadStyles(QString fileName) } return true; } else { - QMessageBox::warning(0, + QMessageBox::warning(nullptr, "Bad style file.", "Bad style file: '" + fileName + "'. Check the file is properly formatted and try to load it again."); return false; @@ -326,7 +299,8 @@ bool Tikzit::loadStyles(QString fileName) } else { //settings.setValue("previous-tikzstyles-file", ""); - QMessageBox::warning(0, "Style file not found.", "Could not open style file: '" + fileName + "'."); + QMessageBox::warning(nullptr, + "Style file not found.", "Could not open style file: '" + fileName + "'."); return false; } } @@ -346,6 +320,20 @@ QString Tikzit::styleFilePath() const return _styleFilePath; } +void Tikzit::updateRecentFiles() +{ + foreach (MainWindow *w, _windows) { + w->menu()->updateRecentFiles(); + } +} + +void Tikzit::clearRecentFiles() +{ + QSettings settings("tikzit", "tikzit"); + settings.setValue("recent-files", QStringList()); + updateRecentFiles(); +} + void Tikzit::setCheckForUpdates(bool check) { QSettings settings("tikzit", "tikzit"); @@ -357,16 +345,32 @@ void Tikzit::setCheckForUpdates(bool check) } } -void Tikzit::checkForUpdates() +void Tikzit::checkForUpdates(bool manual) { QNetworkAccessManager *manager = new QNetworkAccessManager(this); - connect(manager, SIGNAL(finished(QNetworkReply*)), - this, SLOT(updateReply(QNetworkReply*))); + + if (manual) { + connect(manager, SIGNAL(finished(QNetworkReply*)), + this, SLOT(updateManual(QNetworkReply*))); + } else { + connect(manager, SIGNAL(finished(QNetworkReply*)), + this, SLOT(updateAuto(QNetworkReply*))); + } manager->get(QNetworkRequest(QUrl("https://tikzit.github.io/latest-version.txt"))); } -void Tikzit::updateReply(QNetworkReply *reply) +void Tikzit::updateAuto(QNetworkReply *reply) +{ + updateReply(reply, false); +} + +void Tikzit::updateManual(QNetworkReply *reply) +{ + updateReply(reply, true); +} + +void Tikzit::updateReply(QNetworkReply *reply, bool manual) { if (!reply->isReadable()) return; @@ -395,7 +399,7 @@ void Tikzit::updateReply(QNetworkReply *reply) QString::number(latest.minorVersion()) + "." + QString::number(latest.microVersion()); if (rcLatest != 1000) strLatest += "-rc" + QString::number(rcLatest); - QMessageBox::information(0, + QMessageBox::information(nullptr, tr("Update available"), "<p><b>A new version of TikZiT is available!</b></p>" "<p><i>current version: " TIKZIT_VERSION "<br />" @@ -404,13 +408,138 @@ void Tikzit::updateReply(QNetworkReply *reply) "<a href=\"https://tikzit.github.io\">tikzit.github.io</a>.</p>"); } } else { - QMessageBox::warning(0, + // don't complain of invalid response for auto update check + if (manual) { + QMessageBox::warning(nullptr, tr("Invalid response"), "<p>Got invalid version response from " "<a href=\"https://tikzit.github.io\">tikzit.github.io</a>.</p>"); + } + } +} + +void Tikzit::makePreview() +{ + if (activeWindow()) { + LatexProcess *oldProc = _latex; + _latex = new LatexProcess(_preview, this); + if (oldProc != nullptr) { + oldProc->kill(); + oldProc->deleteLater(); + } + + connect(_latex, SIGNAL(previewFinished()), this, SLOT(cleanupLatex())); + + if (activeWindow()->tikzDocument()->isEmpty()) { + _latex->makePreview("\\begin{tikzpicture}\n" + " \\node [style=none] (0) at (0,0) {};\n" + "\\end{tikzpicture}\n"); + } else { + _latex->makePreview(activeWindow()->tikzSource()); + } + _preview->show(); + _preview->raise(); } } +void Tikzit::cleanupLatex() +{ + LatexProcess *oldProc = _latex; + _latex = nullptr; + if (oldProc != nullptr) { + oldProc->deleteLater(); + } +} + +void Tikzit::initColors() +{ + // 19 standard xcolor colours + _colNames << + "black" << + "darkgray" << + "gray" << + "lightgray" << + "white" << + + "red" << + "orange" << + "yellow" << + "green" << + "blue" << + "purple" << + + "brown" << + "olive" << + "lime" << + "cyan" << + "teal" << + + "magenta" << + "violet" << + "pink"; + + _cols << + QColor::fromRgbF(0,0,0) << + QColor::fromRgbF(0.25,0.25,0.25) << + QColor::fromRgbF(0.5,0.5,0.5) << + QColor::fromRgbF(0.75,0.75,0.75) << + QColor::fromRgbF(1,1,1) << + + QColor::fromRgbF(1,0,0) << + QColor::fromRgbF(1,0.5,0) << + QColor::fromRgbF(1,1,0) << + QColor::fromRgbF(0,1,0) << + QColor::fromRgbF(0,0,1) << + QColor::fromRgbF(0.75,0,0.25) << + + QColor::fromRgbF(0.75,0.5,0.25) << + QColor::fromRgbF(0.5,0.5,0) << + QColor::fromRgbF(0.75,1,0) << + QColor::fromRgbF(0,1,1) << + QColor::fromRgbF(0,0.5,0.5) << + + QColor::fromRgbF(1,0,1) << + QColor::fromRgbF(0.5,0,0.5) << + QColor::fromRgbF(1,0.75,0.75); + + for (int i = 0; i < 48; ++i) { + QColorDialog::setStandardColor(i, QColor(Qt::white)); + } + + // grayscale in column 1 + int pos = 0; + for (int i=0; i < 5; ++i) { + QColorDialog::setStandardColor(pos, _cols[i]); + pos += 1; + } + + // rainbow in column 2 + pos = 6; + for (int i=5; i < 11; ++i) { + QColorDialog::setStandardColor(pos, _cols[i]); + pos += 1; + } + + // brown/green/teal spectrum in column 3 + pos = 12; + for (int i=11; i < 16; ++i) { + QColorDialog::setStandardColor(pos, _cols[i]); + pos += 1; + } + + // pinks in column 4 + pos = 18; + for (int i=16; i < 19; ++i) { + QColorDialog::setStandardColor(pos, _cols[i]); + pos += 1; + } +} + +PreviewWindow *Tikzit::previewWindow() const +{ + return _preview; +} + //StylePalette *Tikzit::stylePalette() const //{ // return _stylePalette; diff --git a/src/tikzit.h b/src/tikzit.h index d36a940..4797f48 100644 --- a/src/tikzit.h +++ b/src/tikzit.h @@ -60,6 +60,8 @@ #include "propertypalette.h" #include "stylepalette.h" #include "tikzstyles.h" +#include "latexprocess.h" +#include "previewwindow.h" #include <QObject> #include <QVector> @@ -74,12 +76,13 @@ // Number of pixels between (0,0) and (1,0) at 100% zoom level. This should be // divisible by 8 to avoid rounding errors with e.g. grid-snapping. #define GLOBAL_SCALE 40 -#define GLOBAL_SCALEF 40.0f -#define GLOBAL_SCALEF_INV 0.025f +#define GLOBAL_SCALEF 40.0 +#define GLOBAL_SCALEF_INV 0.025 #define GRID_N 4 #define GRID_SEP 10 #define GRID_SEPF 10.0f + inline QPointF toScreen(QPointF src) { src.setY(-src.y()); src *= GLOBAL_SCALEF; return src; } @@ -131,14 +134,26 @@ public: //StylePalette *stylePalette() const; QString styleFilePath() const; + void updateRecentFiles(); + + PreviewWindow *previewWindow() const; public slots: + void clearRecentFiles(); void setCheckForUpdates(bool check); - void checkForUpdates(); - void updateReply(QNetworkReply *reply); + void checkForUpdates(bool manual); + void updateAuto(QNetworkReply *reply); + void updateManual(QNetworkReply *reply); + void updateReply(QNetworkReply *reply, bool manual); + void makePreview(); + void cleanupLatex(); private: - // void createMenu(); + /*! + * \brief initColors initialises a table of xcolor named colors and their associated + * QColor values, and adds them as standard colors to the Qt color dialog. + */ + void initColors(); MainMenu *_mainMenu; ToolPalette *_toolPalette; @@ -152,6 +167,8 @@ private: StyleEditor *_styleEditor; QStringList _colNames; QVector<QColor> _cols; + LatexProcess *_latex; + PreviewWindow *_preview; }; extern Tikzit *tikzit; diff --git a/src/util.cpp b/src/util.cpp index d5e2b96..304f9e7 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -19,31 +19,31 @@ #include "util.h" -float bezierInterpolate(float dist, float c0, float c1, float c2, float c3) { - float distp = 1 - dist; +qreal bezierInterpolate(qreal dist, qreal c0, qreal c1, qreal c2, qreal c3) { + qreal distp = 1 - dist; return (distp*distp*distp) * c0 + 3 * (distp*distp) * dist * c1 + 3 * (dist*dist) * distp * c2 + (dist*dist*dist) * c3; } -QPointF bezierInterpolateFull (float dist, QPointF c0, QPointF c1, QPointF c2, QPointF c3) { +QPointF bezierInterpolateFull (qreal dist, QPointF c0, QPointF c1, QPointF c2, QPointF c3) { return QPointF(bezierInterpolate (dist, c0.x(), c1.x(), c2.x(), c3.x()), bezierInterpolate (dist, c0.y(), c1.y(), c2.y(), c3.y())); } -float roundToNearest(float stepSize, float val) { - if (stepSize==0.0f) return val; +qreal roundToNearest(qreal stepSize, qreal val) { + if (stepSize==0.0) return val; else return round(val/stepSize)*stepSize; } -float radiansToDegrees (float radians) { - return (radians * 180.0f) / M_PI; +qreal radiansToDegrees (qreal radians) { + return (radians * 180.0) / M_PI; } -float degreesToRadians(float degrees) { - return (degrees * M_PI) / 180.0f; +qreal degreesToRadians(qreal degrees) { + return (degrees * M_PI) / 180.0; } int normaliseAngleDeg (int degrees) { @@ -56,7 +56,7 @@ int normaliseAngleDeg (int degrees) { return degrees; } -float normaliseAngleRad (float rads) { +qreal normaliseAngleRad (qreal rads) { while (rads > M_PI) { rads -= 2 * M_PI; } @@ -66,8 +66,17 @@ float normaliseAngleRad (float rads) { return rads; } -// convert float to string, squashing very small floats to zero -QString floatToString(float f) { - if (f >= -0.000001 && f <= 0.000001) return "0"; +bool almostZero(qreal f) { + return (f >= -0.000001 && f <= 0.000001); +} + +bool almostEqual(qreal f1, qreal f2) { + return almostZero(f1 - f2); +} + +// convert qreal to string, squashing very small qreals to zero +QString floatToString(qreal f) { + if (almostZero(f)) return "0"; else return QString::number(f); } + @@ -33,17 +33,19 @@ #endif // interpolate on a cubic bezier curve -float bezierInterpolate(float dist, float c0, float c1, float c2, float c3); -QPointF bezierInterpolateFull (float dist, QPointF c0, QPointF c1, QPointF c2, QPointF c3); +qreal bezierInterpolate(qreal dist, qreal c0, qreal c1, qreal c2, qreal c3); +QPointF bezierInterpolateFull (qreal dist, QPointF c0, QPointF c1, QPointF c2, QPointF c3); // rounding -float roundToNearest(float stepSize, float val); -float radiansToDegrees (float radians); -QString floatToString(float f); +qreal roundToNearest(qreal stepSize, qreal val); +qreal radiansToDegrees (qreal radians); +bool almostZero(qreal f); +bool almostEqual(qreal f1, qreal f2); +QString floatToString(qreal f); // angles -float degreesToRadians(float degrees); +qreal degreesToRadians(qreal degrees); int normaliseAngleDeg (int degrees); -float normaliseAngleRad (float rads); +qreal normaliseAngleRad (qreal rads); #endif // UTIL_H |