diff options
-rw-r--r-- | tikzit/src/data/edge.cpp | 211 | ||||
-rw-r--r-- | tikzit/src/data/edge.h | 35 | ||||
-rw-r--r-- | tikzit/src/data/node.cpp | 25 | ||||
-rw-r--r-- | tikzit/src/data/node.h | 9 | ||||
-rw-r--r-- | tikzit/src/data/nodestyle.cpp | 30 | ||||
-rw-r--r-- | tikzit/src/data/nodestyle.h | 26 | ||||
-rw-r--r-- | tikzit/src/gui/edgeitem.cpp | 89 | ||||
-rw-r--r-- | tikzit/src/gui/edgeitem.h | 28 | ||||
-rw-r--r-- | tikzit/src/gui/mainwindow.cpp | 57 | ||||
-rw-r--r-- | tikzit/src/gui/mainwindow.h | 11 | ||||
-rw-r--r-- | tikzit/src/gui/mainwindow.ui | 55 | ||||
-rw-r--r-- | tikzit/src/gui/nodeitem.cpp | 82 | ||||
-rw-r--r-- | tikzit/src/gui/nodeitem.h | 9 | ||||
-rw-r--r-- | tikzit/src/gui/propertypalette.cpp | 1 | ||||
-rw-r--r-- | tikzit/src/gui/tikzscene.cpp | 64 | ||||
-rw-r--r-- | tikzit/src/gui/tikzscene.h | 10 | ||||
-rw-r--r-- | tikzit/src/gui/tikzview.cpp | 65 | ||||
-rw-r--r-- | tikzit/src/gui/tikzview.h | 5 | ||||
-rw-r--r-- | tikzit/src/gui/toolpalette.cpp | 1 | ||||
-rw-r--r-- | tikzit/src/main.cpp | 19 | ||||
-rw-r--r-- | tikzit/src/test/testparser.cpp | 31 | ||||
-rw-r--r-- | tikzit/src/test/testparser.h | 1 | ||||
-rw-r--r-- | tikzit/src/tikzit.cpp | 132 | ||||
-rw-r--r-- | tikzit/src/tikzit.h | 73 | ||||
-rw-r--r-- | tikzit/tikzit.pro | 16 | ||||
-rw-r--r-- | tikzit/tikzit.pro.user | 2 |
26 files changed, 938 insertions, 149 deletions
diff --git a/tikzit/src/data/edge.cpp b/tikzit/src/data/edge.cpp index 9068a1c..b999f8a 100644 --- a/tikzit/src/data/edge.cpp +++ b/tikzit/src/data/edge.cpp @@ -1,12 +1,22 @@ #include "edge.h" +#include "tikzit.h" #include <QDebug> +#include <QPointF> +#include <cmath> Edge::Edge(Node *s, Node *t, QObject *parent) : QObject(parent), _source(s), _target(t) { _data = new GraphElementData(); _edgeNode = 0; + _dirty = true; + _basicBendMode = true; + _bend = 0; + _inAngle = 0; + _outAngle = 0; + _weight = 0.4f; + updateControls(); } Edge::~Edge() @@ -25,6 +35,16 @@ Node *Edge::target() const return _target; } +bool Edge::isSelfLoop() +{ + return (_source == _target); +} + +bool Edge::isStraight() +{ + return (_basicBendMode && _bend == 0); +} + GraphElementData *Edge::data() const { return _data; @@ -34,6 +54,7 @@ void Edge::setData(GraphElementData *data) { delete _data; _data = data; + setAttributesFromData(); } QString Edge::sourceAnchor() const @@ -72,4 +93,194 @@ bool Edge::hasEdgeNode() return _edgeNode != 0; } +void Edge::updateControls() { + //if (_dirty) { + QPointF src = _source->point(); + QPointF targ = _target->point(); + + float dx = (targ.x() - src.x()); + float dy = (targ.y() - src.y()); + + float outAngleR = 0.0f; + float inAngleR = 0.0f; + + if (_basicBendMode) { + float angle = std::atan2(dy, dx); + float bnd = (float)_bend * (M_PI / 180.0f); + outAngleR = angle - bnd; + inAngleR = M_PI + angle + bnd; + _outAngle = outAngleR * (180.f / M_PI); + _inAngle = inAngleR * (180.f / M_PI); + } else { + outAngleR = (float)_outAngle * (M_PI / 180.0f); + inAngleR = (float)_inAngle * (M_PI / 180.0f); + } + + // TODO: calculate head and tail properly, not just for circles + if (_source->style().isNone()) { + _tail = src; + } else { + _tail = QPointF(src.x() + std::cos(outAngleR) * 0.1, + src.y() + std::sin(outAngleR) * 0.1); + } + + if (_target->style().isNone()) { + _head = targ; + } else { + _head = QPointF(targ.x() + std::cos(inAngleR) * 0.1, + targ.y() + std::sin(inAngleR) * 0.1); + } + + // give a default distance for self-loops + _cpDist = (dx==0.0f && dy==0.0f) ? _weight : std::sqrt(dx*dx + dy*dy) * _weight; + + _cp1 = QPointF(src.x() + (_cpDist * std::cos(outAngleR)), + src.y() + (_cpDist * std::sin(outAngleR))); + + _cp2 = QPointF(targ.x() + (_cpDist * std::cos(inAngleR)), + targ.y() + (_cpDist * std::sin(inAngleR))); + + _mid = bezierInterpolateFull (0.5f, _tail, _cp1, _cp2, _head); +// midTan = [self _findTanFor:mid usingSpanFrom:0.4f to:0.6f]; + +// tailTan = [self _findTanFor:tail usingSpanFrom:0.0f to:0.1f]; +// headTan = [self _findTanFor:head usingSpanFrom:1.0f to:0.9f]; + //_dirty = false; + //} +} + +void Edge::setAttributesFromData() +{ + _basicBendMode = true; + bool ok = true; + + if (_data->atom("bend left")) { + _bend = -30; + } else if (_data->atom("bend right")) { + _bend = 30; + } else if (_data->property("bend left") != 0) { + _bend = -_data->property("bend left").toInt(&ok); + if (!ok) _bend = -30; + } else if (_data->property("bend right") != 0) { + _bend = _data->property("bend right").toInt(&ok); + if (!ok) _bend = 30; + } else { + _bend = 0; + + if (_data->property("in") != 0 && _data->property("out") != 0) { + _basicBendMode = false; + _inAngle = _data->property("in").toInt(&ok); + if (!ok) _inAngle = 0; + _outAngle = _data->property("out").toInt(&ok); + if (!ok) _outAngle = 180; + } + } + + if (_data->property("looseness") != 0) { + _weight = _data->property("looseness").toFloat(&ok) / 2.5f; + if (!ok) _weight = 0.4f; + } else { + _weight = (isSelfLoop()) ? 1.0f : 0.4f; + } + + //qDebug() << "bend: " << _bend << " in: " << _inAngle << " out: " << _outAngle; + _dirty = true; +} + +void Edge::updateData() +{ + _data->unsetAtom("loop"); + _data->unsetProperty("in"); + _data->unsetProperty("out"); + _data->unsetAtom("bend left"); + _data->unsetAtom("bend right"); + _data->unsetProperty("bend left"); + _data->unsetProperty("bend right"); + _data->unsetProperty("looseness"); + + // TODO: style handling? + + if (_basicBendMode && _bend != 0) { + QString bendKey; + int b; + if (_bend < 0) { + bendKey = "bend left"; + b = -_bend; + } else { + bendKey = "bend right"; + b = _bend; + } + + if (b == 30) { + _data->setAtom(bendKey); + } else { + _data->setProperty(bendKey, QString::number(b)); + } + } else { + _data->setProperty("in", QString::number(_inAngle)); + _data->setProperty("out", QString::number(_outAngle)); + } + + if (_source == _target) _data->setAtom("loop"); + if (!isSelfLoop() && !isStraight() && _weight != 0.4f) + _data->setProperty("looseness", QString::number(_weight*2.5f, 'f', 2)); + +} + + +QPointF Edge::head() const +{ + return _head; +} + +QPointF Edge::tail() const +{ + return _tail; +} + +QPointF Edge::cp1() const +{ + return _cp1; +} + +QPointF Edge::cp2() const +{ + return _cp2; +} + +int Edge::bend() const +{ + return _bend; +} + +int Edge::inAngle() const +{ + return _inAngle; +} + +int Edge::outAngle() const +{ + return _outAngle; +} + +float Edge::weight() const +{ + return _weight; +} + +bool Edge::basicBendMode() const +{ + return _basicBendMode; +} + +float Edge::cpDist() const +{ + return _cpDist; +} + +QPointF Edge::mid() const +{ + return _mid; +} + diff --git a/tikzit/src/data/edge.h b/tikzit/src/data/edge.h index 9655e98..e392c56 100644 --- a/tikzit/src/data/edge.h +++ b/tikzit/src/data/edge.h @@ -5,6 +5,7 @@ #include "node.h" #include <QObject> +#include <QPointF> class Edge : public QObject { @@ -16,6 +17,9 @@ public: Node *source() const; Node *target() const; + bool isSelfLoop(); + bool isStraight(); + GraphElementData *data() const; void setData(GraphElementData *data); @@ -29,6 +33,23 @@ public: void setEdgeNode(Node *edgeNode); bool hasEdgeNode(); + void updateControls(); + void setAttributesFromData(); + void updateData(); + + QPointF head() const; + QPointF tail() const; + QPointF cp1() const; + QPointF cp2() const; + QPointF mid() const; + + int bend() const; + int inAngle() const; + int outAngle() const; + float weight() const; + bool basicBendMode() const; + float cpDist() const; + signals: public slots: @@ -44,6 +65,20 @@ private: // referenced Node *_source; Node *_target; + + bool _dirty; + bool _basicBendMode; + int _bend; + int _inAngle; + int _outAngle; + float _weight; + float _cpDist; + + QPointF _head; + QPointF _tail; + QPointF _cp1; + QPointF _cp2; + QPointF _mid; }; #endif // EDGE_H diff --git a/tikzit/src/data/node.cpp b/tikzit/src/data/node.cpp index b3b2155..1b8ccf8 100644 --- a/tikzit/src/data/node.cpp +++ b/tikzit/src/data/node.cpp @@ -1,10 +1,13 @@ #include "node.h" +#include "tikzit.h" #include <QDebug> Node::Node(QObject *parent) : QObject(parent) { _data = new GraphElementData(); + _style = NodeStyle(); + _styleName = "none"; } Node::~Node() @@ -51,5 +54,27 @@ void Node::setData(GraphElementData *data) { delete _data; _data = data; + if (_data->property("style") != 0) _styleName = _data->property("style"); +} + +QString Node::styleName() const +{ + return _styleName; +} + +void Node::setStyleName(const QString &styleName) +{ + _styleName = styleName; +} + +void Node::attachStyle() +{ + if (_styleName == "none") _style = NodeStyle(); + else _style = tikzit->nodeStyle(_styleName); +} + +NodeStyle Node::style() const +{ + return _style; } diff --git a/tikzit/src/data/node.h b/tikzit/src/data/node.h index e72e9a7..91b1725 100644 --- a/tikzit/src/data/node.h +++ b/tikzit/src/data/node.h @@ -2,6 +2,7 @@ #define NODE_H #include "graphelementdata.h" +#include "nodestyle.h" #include <QObject> #include <QPointF> @@ -26,6 +27,12 @@ public: GraphElementData *data() const; void setData(GraphElementData *data); + QString styleName() const; + void setStyleName(const QString &styleName); + + void attachStyle(); + NodeStyle style() const; + signals: public slots: @@ -34,6 +41,8 @@ private: QPointF _point; QString _name; QString _label; + QString _styleName; + NodeStyle _style; GraphElementData *_data; }; diff --git a/tikzit/src/data/nodestyle.cpp b/tikzit/src/data/nodestyle.cpp new file mode 100644 index 0000000..109e2af --- /dev/null +++ b/tikzit/src/data/nodestyle.cpp @@ -0,0 +1,30 @@ +#include "nodestyle.h" + +NodeStyle::NodeStyle() +{ + name = "none"; + shape = NodeShape::Circle; + fillColor = Qt::white; + strokeColor = Qt::black; + strokeThickness = 1; +} + +NodeStyle::NodeStyle(QString nm, NodeShape sh, QColor fillCol) +{ + name = nm; + shape = sh; + fillColor = fillCol; + strokeColor = Qt::black; + strokeThickness = 1; +} + +NodeStyle::NodeStyle(QString nm, NodeShape sh, QColor fillCol, QColor strokeCol, int strokeThick) +{ + name = nm; + shape = sh; + fillColor = fillCol; + strokeColor = strokeCol; + strokeThickness = strokeThick; +} + +bool NodeStyle::isNone() { return name == "none"; } diff --git a/tikzit/src/data/nodestyle.h b/tikzit/src/data/nodestyle.h new file mode 100644 index 0000000..baf967c --- /dev/null +++ b/tikzit/src/data/nodestyle.h @@ -0,0 +1,26 @@ +#ifndef NODESTYLE_H +#define NODESTYLE_H + +#include <QColor> + +enum NodeShape { + Square, UpTriangle, DownTriangle, Circle +}; + +class NodeStyle +{ +public: + NodeStyle(); + NodeStyle(QString nm, NodeShape sh, QColor fillCol); + NodeStyle(QString nm, NodeShape sh, QColor fillCol, QColor strokeCol, int strokeThick); + bool isNone(); + QString name; + NodeShape shape; + QColor fillColor; + QColor strokeColor; + int strokeThickness; +}; + +extern NodeStyle noneStyle; + +#endif // NODESTYLE_H diff --git a/tikzit/src/gui/edgeitem.cpp b/tikzit/src/gui/edgeitem.cpp new file mode 100644 index 0000000..f45493d --- /dev/null +++ b/tikzit/src/gui/edgeitem.cpp @@ -0,0 +1,89 @@ +#include "tikzit.h" +#include "edgeitem.h" + +#include <QPainterPath> +#include <QPen> + +EdgeItem::EdgeItem(Edge *edge) +{ + _edge = edge; + setFlag(QGraphicsItem::ItemIsSelectable); + + QPen pen(Qt::black); + pen.setWidth(2); + setPen(pen); + _cp1Item = new QGraphicsEllipseItem(this); + _cp1Item->setParentItem(this); + _cp2Item = new QGraphicsEllipseItem(this); + _cp2Item->setParentItem(this); + syncPos(); +} + +void EdgeItem::syncPos() +{ + _edge->setAttributesFromData(); + _edge->updateControls(); + QPainterPath path; + + path.moveTo (toScreen(_edge->tail())); + path.cubicTo(toScreen(_edge->cp1()), + toScreen(_edge->cp2()), + toScreen(_edge->head())); + setPath(path); + + float r = GLOBAL_SCALEF * 0.05; + //painter->drawEllipse(toScreen(_edge->cp1()), r, r); + //painter->drawEllipse(toScreen(_edge->cp2()), r, r); +} + +void EdgeItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) +{ + //QGraphicsPathItem::paint(painter, option, widget); + painter->setPen(pen()); + painter->setBrush(Qt::NoBrush); + painter->drawPath(path()); + + + + if (isSelected()) { + QColor draw; + QColor draw1; + QColor fill; + + if (_edge->basicBendMode()) { + draw = Qt::blue; + draw1 = QColor(100,100,255,100); + fill = QColor(200,200,255,50); + } else { + draw = Qt::darkGreen; + draw1 = QColor(0, 150, 0, 50); + fill = QColor(200,255,200,150); + } + + painter->setPen(QPen(draw1)); + + float r = GLOBAL_SCALEF * _edge->cpDist(); + painter->drawEllipse(toScreen(_edge->source()->point()), r, r); + painter->drawEllipse(toScreen(_edge->target()->point()), r, r); + + painter->setPen(QPen(draw)); + painter->setBrush(QBrush(fill)); + + painter->drawLine(toScreen(_edge->tail()), toScreen(_edge->cp1())); + painter->drawLine(toScreen(_edge->head()), toScreen(_edge->cp2())); + + r = GLOBAL_SCALEF * 0.05; + painter->drawEllipse(toScreen(_edge->cp1()), r, r); + painter->drawEllipse(toScreen(_edge->cp2()), r, r); + + painter->setPen(QPen(Qt::black)); + painter->setBrush(QBrush(QColor(255,255,255,200))); + painter->drawEllipse(toScreen(_edge->mid()), r, r); + } +} + +QRectF EdgeItem::boundingRect() const +{ + float r = GLOBAL_SCALEF * (_edge->cpDist() + 0.2); + return QGraphicsPathItem::boundingRect().adjusted(-r,-r,r,r); +} diff --git a/tikzit/src/gui/edgeitem.h b/tikzit/src/gui/edgeitem.h new file mode 100644 index 0000000..935138b --- /dev/null +++ b/tikzit/src/gui/edgeitem.h @@ -0,0 +1,28 @@ +#ifndef EDGEITEM_H +#define EDGEITEM_H + +#include "edge.h" + +#include <QObject> +#include <QGraphicsPathItem> +#include <QPainter> +#include <QStyleOptionGraphicsItem> +#include <QWidget> +#include <QGraphicsEllipseItem> + +class EdgeItem : public QGraphicsPathItem +{ +public: + EdgeItem(Edge *edge); + void syncPos(); + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, + QWidget *widget); + QRectF boundingRect() const; + +private: + Edge *_edge; + QGraphicsEllipseItem *_cp1Item; + QGraphicsEllipseItem *_cp2Item; +}; + +#endif // EDGEITEM_H diff --git a/tikzit/src/gui/mainwindow.cpp b/tikzit/src/gui/mainwindow.cpp index f7357ec..7d7ab04 100644 --- a/tikzit/src/gui/mainwindow.cpp +++ b/tikzit/src/gui/mainwindow.cpp @@ -1,6 +1,8 @@ #include "mainwindow.h" #include "ui_mainwindow.h" #include "tikzgraphassembler.h" +#include "toolpalette.h" +#include "tikzit.h" #include <QDebug> #include <QFile> @@ -15,12 +17,13 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { + _windowId = _numWindows; _numWindows++; ui->setupUi(this); - setAttribute(Qt::WA_DeleteOnClose); + setAttribute(Qt::WA_DeleteOnClose, true); _graph = new Graph(this); - tikzScene = new TikzScene(_graph, this); - ui->tikzView->setScene(tikzScene); + _tikzScene = new TikzScene(_graph, this); + ui->tikzView->setScene(_tikzScene); _fileName = ""; _pristine = true; @@ -33,7 +36,7 @@ MainWindow::MainWindow(QWidget *parent) : MainWindow::~MainWindow() { - //qDebug() << "~MainWindow"; + tikzit->removeWindow(this); } void MainWindow::open(QString fileName) @@ -45,6 +48,8 @@ void MainWindow::open(QString fileName) QSettings settings("tikzit", "tikzit"); settings.setValue("previous-file-path", fi.absolutePath()); + setWindowTitle("TiKZiT - " + fi.fileName()); + if (!file.open(QIODevice::ReadOnly)) { QMessageBox::critical(this, tr("Error"), tr("Could not open file")); @@ -61,9 +66,11 @@ void MainWindow::open(QString fileName) TikzGraphAssembler ass(newGraph); if (ass.parse(tikz)) { statusBar()->showMessage("TiKZ parsed successfully", 2000); - tikzScene->setGraph(newGraph); delete _graph; _graph = newGraph; + foreach (Node *n, _graph->nodes()) n->attachStyle(); + foreach (Edge *e, _graph->edges()) e->updateControls(); + _tikzScene->setGraph(_graph); } else { statusBar()->showMessage("Cannot read TiKZ source"); delete newGraph; @@ -77,24 +84,32 @@ void MainWindow::closeEvent(QCloseEvent *event) QMainWindow::closeEvent(event); } -void MainWindow::on_actionOpen_triggered() +void MainWindow::changeEvent(QEvent *event) { - QSettings settings("tikzit", "tikzit"); - QString fileName = QFileDialog::getOpenFileName( - this, - tr("Open File"), - settings.value("previous-file-path").toString(), - tr("TiKZ Files (*.tikz)")); - - if (!fileName.isEmpty()) { - if (_pristine) { - open(fileName); - } else { - MainWindow *w = new MainWindow(); - w->show(); - w->open(fileName); - } + if (event->type() == QEvent::ActivationChange && isActiveWindow()) { + tikzit->setActiveWindow(this); } + QMainWindow::changeEvent(event); +} + +TikzScene *MainWindow::tikzScene() const +{ + return _tikzScene; +} + +int MainWindow::windowId() const +{ + return _windowId; +} + +TikzView *MainWindow::tikzView() const +{ + return ui->tikzView; +} + +bool MainWindow::pristine() const +{ + return _pristine; } diff --git a/tikzit/src/gui/mainwindow.h b/tikzit/src/gui/mainwindow.h index f48fd64..2e52cd5 100644 --- a/tikzit/src/gui/mainwindow.h +++ b/tikzit/src/gui/mainwindow.h @@ -2,6 +2,7 @@ #define MAINWINDOW_H #include "tikzscene.h" +#include "tikzview.h" #include "graph.h" #include <QMainWindow> @@ -20,18 +21,22 @@ public: ~MainWindow(); void open(QString fileName); + bool pristine() const; + int windowId() const; + TikzView *tikzView() const; + TikzScene *tikzScene() const; protected: void closeEvent(QCloseEvent *event); + void changeEvent(QEvent *event); private: - TikzScene *tikzScene; + TikzScene *_tikzScene; Ui::MainWindow *ui; Graph *_graph; QString _fileName; bool _pristine; + int _windowId; static int _numWindows; -public slots: - void on_actionOpen_triggered(); }; #endif // MAINWINDOW_H diff --git a/tikzit/src/gui/mainwindow.ui b/tikzit/src/gui/mainwindow.ui index a2655e6..8c7e8ae 100644 --- a/tikzit/src/gui/mainwindow.ui +++ b/tikzit/src/gui/mainwindow.ui @@ -11,7 +11,7 @@ </rect> </property> <property name="windowTitle"> - <string>TikZiT</string> + <string>TikZiT - untitled</string> </property> <widget class="QWidget" name="centralWidget"> <layout class="QVBoxLayout" name="verticalLayout"> @@ -58,59 +58,6 @@ p, li { white-space: pre-wrap; } </item> </layout> </widget> - <widget class="QMenuBar" name="menuBar"> - <property name="geometry"> - <rect> - <x>0</x> - <y>0</y> - <width>476</width> - <height>22</height> - </rect> - </property> - <widget class="QMenu" name="menuFile"> - <property name="title"> - <string>File</string> - </property> - <addaction name="actionNew"/> - <addaction name="actionOpen"/> - <addaction name="separator"/> - <addaction name="actionClose"/> - <addaction name="actionSave"/> - <addaction name="actionSave_As"/> - </widget> - <widget class="QMenu" name="menuEdit"> - <property name="title"> - <string>Edit</string> - </property> - <addaction name="actionUndo"/> - <addaction name="actionRedo"/> - <addaction name="separator"/> - <addaction name="actionCut"/> - <addaction name="actionCopy"/> - <addaction name="actionPase"/> - <addaction name="actionDelete"/> - <addaction name="separator"/> - <addaction name="actionSelect_All"/> - <addaction name="actionDeselect_All"/> - </widget> - <widget class="QMenu" name="menuTikz"> - <property name="title"> - <string>Tikz</string> - </property> - <addaction name="actionParse"/> - </widget> - <widget class="QMenu" name="menuView"> - <property name="title"> - <string>View</string> - </property> - <addaction name="actionZoom_In"/> - <addaction name="actionZoom_Out"/> - </widget> - <addaction name="menuFile"/> - <addaction name="menuEdit"/> - <addaction name="menuView"/> - <addaction name="menuTikz"/> - </widget> <widget class="QStatusBar" name="statusBar"/> <action name="actionNew"> <property name="text"> diff --git a/tikzit/src/gui/nodeitem.cpp b/tikzit/src/gui/nodeitem.cpp index e817d98..d91bfd6 100644 --- a/tikzit/src/gui/nodeitem.cpp +++ b/tikzit/src/gui/nodeitem.cpp @@ -1,17 +1,93 @@ +#include "tikzit.h" #include "nodeitem.h" #include <QPen> #include <QBrush> +#include <QDebug> +#include <QFont> +#include <QFontMetrics> +#include <QPainterPathStroker> NodeItem::NodeItem(Node *node) { _node = node; - setPen(QPen(Qt::black)); - setBrush(QBrush(Qt::white)); + setFlag(QGraphicsItem::ItemIsSelectable); + setFlag(QGraphicsItem::ItemIsMovable); syncPos(); } void NodeItem::syncPos() { - setRect(80*_node->point().x() - 8, -80*_node->point().y() - 8, 16, 16); + setPos(toScreen(_node->point())); +} + + +void NodeItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) +{ + if (_node->style().isNone()) { + QColor c(180,180,200); + painter->setPen(QPen(c)); + painter->setBrush(QBrush(c)); + painter->drawEllipse(QPointF(0,0), 1,1); + + QPen pen(QColor(180,180,220)); + QVector<qreal> p; + p << 2.0 << 2.0; + pen.setDashPattern(p); + painter->setPen(pen); + painter->setBrush(Qt::NoBrush); + painter->drawPath(shape()); + } else { + QPen pen(_node->style().strokeColor); + pen.setWidth(_node->style().strokeThickness); + painter->setPen(pen); + painter->setBrush(QBrush(_node->style().fillColor)); + painter->drawPath(shape()); + } + + if (_node->label() != "") { + QString label = _node->label(); + QFont f("Monaco", 9); + QFontMetrics fm(f); + int w = fm.width(label) + 4; + int h = fm.height() + 2; + + QRectF rect = fm.boundingRect(label); + rect.adjust(-2,-2,2,2); + rect.moveCenter(QPointF(0,0)); + QPen pen(QColor(200,0,0,120)); + QVector<qreal> d; + d << 2.0 << 2.0; + pen.setDashPattern(d); + painter->setPen(pen); + painter->setBrush(QBrush(QColor(255,255,100,120))); + painter->drawRect(rect); + + painter->setPen(QPen(Qt::black)); + painter->setFont(f); + painter->drawText(rect, Qt::AlignCenter, _node->label()); + } + + if (isSelected()) { + QPainterPath sh = shape(); + QPainterPathStroker stroker; + stroker.setWidth(4); + QPainterPath outline = (stroker.createStroke(sh) + sh).simplified(); + painter->setPen(Qt::NoPen); + painter->setBrush(QBrush(QColor(150,200,255,100))); + painter->drawPath(outline); + } + +} + +QPainterPath NodeItem::shape() const +{ + QPainterPath path; + path.addEllipse(QPointF(0,0), GLOBAL_SCALEF * 0.1, GLOBAL_SCALEF * 0.1); + return path; +} + +QRectF NodeItem::boundingRect() const +{ + return shape().boundingRect().adjusted(-4,-4,4,4); } diff --git a/tikzit/src/gui/nodeitem.h b/tikzit/src/gui/nodeitem.h index 60b2c05..867d8a3 100644 --- a/tikzit/src/gui/nodeitem.h +++ b/tikzit/src/gui/nodeitem.h @@ -4,13 +4,18 @@ #include "node.h" #include <QObject> -#include <QGraphicsEllipseItem> +#include <QGraphicsItem> +#include <QPainterPath> +#include <QRectF> -class NodeItem : public QGraphicsEllipseItem +class NodeItem : public QGraphicsItem { public: NodeItem(Node *node); void syncPos(); + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); + QPainterPath shape() const; + QRectF boundingRect() const; private: Node *_node; }; diff --git a/tikzit/src/gui/propertypalette.cpp b/tikzit/src/gui/propertypalette.cpp index ea0e90e..ea906a7 100644 --- a/tikzit/src/gui/propertypalette.cpp +++ b/tikzit/src/gui/propertypalette.cpp @@ -23,7 +23,6 @@ PropertyPalette::PropertyPalette(QWidget *parent) : d->setProperty("key 2", "value 2"); QModelIndex i = d->index(0,0); - qDebug() << "data: " << i.data(); ui->treeView->setModel(d); QSettings settings("tikzit", "tikzit"); diff --git a/tikzit/src/gui/tikzscene.cpp b/tikzit/src/gui/tikzscene.cpp index 11d5a72..cf7cde5 100644 --- a/tikzit/src/gui/tikzscene.cpp +++ b/tikzit/src/gui/tikzscene.cpp @@ -1,7 +1,10 @@ +#include "tikzit.h" #include "tikzscene.h" #include <QPen> #include <QBrush> +#include <QDebug> + TikzScene::TikzScene(Graph *graph, QObject *parent) : QGraphicsScene(parent), _graph(graph) @@ -28,8 +31,17 @@ void TikzScene::graphReplaced() } nodeItems.clear(); - QPen blackPen(Qt::black); - QBrush redBrush(Qt::red); + foreach (EdgeItem *ei, edgeItems) { + removeItem(ei); + delete ei; + } + edgeItems.clear(); + + foreach (Edge *e, _graph->edges()) { + EdgeItem *ei = new EdgeItem(e); + edgeItems << ei; + addItem(ei); + } foreach (Node *n, _graph->nodes()) { NodeItem *ni = new NodeItem(n); @@ -38,45 +50,19 @@ void TikzScene::graphReplaced() } } -void TikzScene::drawBackground(QPainter *painter, const QRectF &rect) +void TikzScene::mousePressEvent(QGraphicsSceneMouseEvent *event) { - // draw the grid - int step = 10; + // TODO: check if we grabbed a control point - QPen pen; - pen.setWidth(2); - pen.setCosmetic(true); - pen.setColor(QColor(245,245,255)); - - painter->setPen(pen); - for (int x = step; x < rect.right(); x += step) { - if (x % (step * 8) != 0) { - painter->drawLine(x, rect.top(), x, rect.bottom()); - painter->drawLine(-x, rect.top(), -x, rect.bottom()); - } - } - - for (int y = step; y < rect.bottom(); y += step) { - if (y % (step * 8) != 0) { - painter->drawLine(rect.left(), y, rect.right(), y); - painter->drawLine(rect.left(), -y, rect.right(), -y); - } - } - - pen.setColor(QColor(240,240,245)); - painter->setPen(pen); - for (int x = step*8; x < rect.right(); x += step*8) { - painter->drawLine(x, rect.top(), x, rect.bottom()); - painter->drawLine(-x, rect.top(), -x, rect.bottom()); - } + QGraphicsScene::mousePressEvent(event); +} - for (int y = step*8; y < rect.bottom(); y += step*8) { - painter->drawLine(rect.left(), y, rect.right(), y); - painter->drawLine(rect.left(), -y, rect.right(), -y); - } +void TikzScene::mouseMoveEvent(QGraphicsSceneMouseEvent *event) +{ + QGraphicsScene::mouseMoveEvent(event); +} - pen.setColor(QColor(230,230,240)); - painter->setPen(pen); - painter->drawLine(rect.left(), 0, rect.right(), 0); - painter->drawLine(0, rect.top(), 0, rect.bottom()); +void TikzScene::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) +{ + QGraphicsScene::mouseReleaseEvent(event); } diff --git a/tikzit/src/gui/tikzscene.h b/tikzit/src/gui/tikzscene.h index c7039e6..2c77389 100644 --- a/tikzit/src/gui/tikzscene.h +++ b/tikzit/src/gui/tikzscene.h @@ -3,6 +3,7 @@ #include "graph.h" #include "nodeitem.h" +#include "edgeitem.h" #include <QWidget> #include <QGraphicsScene> @@ -10,6 +11,7 @@ #include <QRectF> #include <QVector> #include <QGraphicsEllipseItem> +#include <QGraphicsSceneMouseEvent> class TikzScene : public QGraphicsScene { @@ -20,13 +22,15 @@ public: void setGraph(Graph *graph); public slots: void graphReplaced(); - +protected: + void mousePressEvent(QGraphicsSceneMouseEvent *event); + void mouseMoveEvent(QGraphicsSceneMouseEvent *event); + void mouseReleaseEvent(QGraphicsSceneMouseEvent *event); private: Graph *_graph; QVector<NodeItem*> nodeItems; + QVector<EdgeItem*> edgeItems; -protected: - void drawBackground(QPainter *painter, const QRectF &rect); }; #endif // TIKZSCENE_H diff --git a/tikzit/src/gui/tikzview.cpp b/tikzit/src/gui/tikzview.cpp index d2a769c..a83c9ec 100644 --- a/tikzit/src/gui/tikzview.cpp +++ b/tikzit/src/gui/tikzview.cpp @@ -1,20 +1,83 @@ #include "tikzview.h" +#include "tikzit.h" #include <QDebug> TikzView::TikzView(QWidget *parent) : QGraphicsView(parent) { setRenderHint(QPainter::Antialiasing); - qDebug() << "TikzView()"; + setDragMode(QGraphicsView::RubberBandDrag); + _scale = 1.0f; } void TikzView::zoomIn() { + _scale *= 1.6f; scale(1.6,1.6); } void TikzView::zoomOut() { + _scale *= 0.625f; scale(0.625,0.625); } +void TikzView::drawBackground(QPainter *painter, const QRectF &rect) +{ + // draw the grid + int step = GLOBAL_SCALE / 8; + + QPen pen1; + pen1.setWidth(1); + pen1.setCosmetic(true); + pen1.setColor(QColor(230,230,230)); + + QPen pen2 = pen1; + pen2.setColor(QColor(200,200,200)); + + QPen pen3 = pen1; + pen3.setColor(QColor(160,160,160)); + + painter->setPen(pen1); + + if (_scale > 0.2f) { + for (int x = -step; x > rect.left(); x -= step) { + if (x % (step * 8) != 0) painter->drawLine(x, rect.top(), x, rect.bottom()); + } + + for (int x = step; x < rect.right(); x += step) { + if (x % (step * 8) != 0) painter->drawLine(x, rect.top(), x, rect.bottom()); + } + + for (int y = -step; y > rect.top(); y -= step) { + if (y % (step * 8) != 0) painter->drawLine(rect.left(), y, rect.right(), y); + } + + for (int y = step; y < rect.bottom(); y += step) { + if (y % (step * 8) != 0) painter->drawLine(rect.left(), y, rect.right(), y); + } + } + + painter->setPen(pen2); + + for (int x = -step*8; x > rect.left(); x -= step*8) { + painter->drawLine(x, rect.top(), x, rect.bottom()); + } + + for (int x = step*8; x < rect.right(); x += step*8) { + painter->drawLine(x, rect.top(), x, rect.bottom()); + } + + for (int y = -step*8; y > rect.top(); y -= step*8) { + painter->drawLine(rect.left(), y, rect.right(), y); + } + + for (int y = step*8; y < rect.bottom(); y += step*8) { + painter->drawLine(rect.left(), y, rect.right(), y); + } + + painter->setPen(pen3); + painter->drawLine(rect.left(), 0, rect.right(), 0); + painter->drawLine(0, rect.top(), 0, rect.bottom()); +} + diff --git a/tikzit/src/gui/tikzview.h b/tikzit/src/gui/tikzview.h index 032b9c4..b16e0df 100644 --- a/tikzit/src/gui/tikzview.h +++ b/tikzit/src/gui/tikzview.h @@ -8,6 +8,7 @@ #include <QGraphicsItem> #include <QStyleOptionGraphicsItem> #include <QRectF> +#include <QMouseEvent> class TikzView : public QGraphicsView { @@ -17,6 +18,10 @@ public: public slots: void zoomIn(); void zoomOut(); +protected: + void drawBackground(QPainter *painter, const QRectF &rect); +private: + float _scale; }; #endif // TIKZVIEW_H diff --git a/tikzit/src/gui/toolpalette.cpp b/tikzit/src/gui/toolpalette.cpp index 61b6cbf..3ee2106 100644 --- a/tikzit/src/gui/toolpalette.cpp +++ b/tikzit/src/gui/toolpalette.cpp @@ -9,7 +9,6 @@ ToolPalette::ToolPalette(QWidget *parent) : QToolBar(parent) { setWindowFlags(Qt::Window - | Qt::WindowStaysOnTopHint | Qt::CustomizeWindowHint | Qt::WindowDoesNotAcceptFocus); setOrientation(Qt::Vertical); diff --git a/tikzit/src/main.cpp b/tikzit/src/main.cpp index e004529..b676211 100644 --- a/tikzit/src/main.cpp +++ b/tikzit/src/main.cpp @@ -1,25 +1,14 @@ - -#include "mainwindow.h" -#include "toolpalette.h" -#include "propertypalette.h" -#include "graph.h" +#include "tikzit.h" #include <QApplication> +#include <QMenuBar> int main(int argc, char *argv[]) { QApplication a(argc, argv); - - ToolPalette *tp = new ToolPalette(new QMainWindow()); - tp->show(); - //w->addToolBar(Qt::LeftToolBarArea, tp); - - PropertyPalette *pp = new PropertyPalette; - pp->show(); - - MainWindow *w = new MainWindow(); - w->show(); + a.setQuitOnLastWindowClosed(false); + tikzit = new Tikzit(); return a.exec(); } diff --git a/tikzit/src/test/testparser.cpp b/tikzit/src/test/testparser.cpp index bbc90cf..e220e2e 100644 --- a/tikzit/src/test/testparser.cpp +++ b/tikzit/src/test/testparser.cpp @@ -103,6 +103,36 @@ void TestParser::parseEdgeNode() delete g; } +void TestParser::parseEdgeBends() +{ + Graph *g = new Graph(); + TikzGraphAssembler ga(g); + bool res = ga.parse( + "\\begin{tikzpicture}\n" + " \\begin{pgfonlayer}{nodelayer}\n" + " \\node [style=white] (0) at (-1, 0) {};\n" + " \\node [style=black] (1) at (1, 0) {};\n" + " \\end{pgfonlayer}\n" + " \\begin{pgfonlayer}{edgelayer}\n" + " \\draw [style=diredge,bend left] (0) to (1);\n" + " \\draw [style=diredge,bend right] (0) to (1);\n" + " \\draw [style=diredge,bend left=20] (0) to (1);\n" + " \\draw [style=diredge,bend right=80] (0) to (1);\n" + " \\draw [style=diredge,in=10,out=150,looseness=2] (0) to (1);\n" + " \\end{pgfonlayer}\n" + "\\end{tikzpicture}\n"); + QVERIFY(res); + QVERIFY(g->nodes().size() == 2); + QVERIFY(g->edges().size() == 5); + QVERIFY(g->edges()[0]->bend() == -30); + QVERIFY(g->edges()[1]->bend() == 30); + QVERIFY(g->edges()[2]->bend() == -20); + QVERIFY(g->edges()[3]->bend() == 80); + QVERIFY(g->edges()[4]->inAngle() == 10); + QVERIFY(g->edges()[4]->outAngle() == 150); + QVERIFY(g->edges()[4]->weight() == 2.0f/2.5f); +} + void TestParser::parseBbox() { Graph *g = new Graph(); @@ -121,6 +151,7 @@ void TestParser::parseBbox() " \\draw [style=diredge] (0) to (1);\n" " \\end{pgfonlayer}\n" "\\end{tikzpicture}\n"); + QVERIFY(res); QVERIFY(g->nodes().size() == 3); QVERIFY(g->edges().size() == 3); QVERIFY(g->hasBbox()); diff --git a/tikzit/src/test/testparser.h b/tikzit/src/test/testparser.h index 69dc965..a40a58f 100644 --- a/tikzit/src/test/testparser.h +++ b/tikzit/src/test/testparser.h @@ -11,6 +11,7 @@ private slots: void parseNodeGraph(); void parseEdgeGraph(); void parseEdgeNode(); + void parseEdgeBends(); void parseBbox(); }; diff --git a/tikzit/src/tikzit.cpp b/tikzit/src/tikzit.cpp new file mode 100644 index 0000000..9abf33e --- /dev/null +++ b/tikzit/src/tikzit.cpp @@ -0,0 +1,132 @@ +#include "tikzit.h" + +#include <QFileDialog> +#include <QSettings> + +// application-level instance of Tikzit +Tikzit *tikzit; + +Tikzit::Tikzit() +{ + _activeWindow = 0; + QMainWindow *dummy = new QMainWindow(); + + _toolPalette = new ToolPalette(dummy); + _propertyPalette = new PropertyPalette(dummy); + + createMenu(); + loadStyles(); + + _toolPalette->show(); + _propertyPalette->show(); + + _windows << new MainWindow(); + _windows[0]->show(); +} + +QMenuBar *Tikzit::mainMenu() const +{ + return _mainMenu; +} + +ToolPalette *Tikzit::toolPalette() const +{ + return _toolPalette; +} + +PropertyPalette *Tikzit::propertyPalette() const +{ + return _propertyPalette; +} + +void Tikzit::createMenu() +{ + _mainMenu = new QMenuBar(0); + QMenu *file = _mainMenu->addMenu(tr("&File")); + QAction *aNew = file->addAction(tr("&New")); + aNew->setShortcut(QKeySequence::New); + QAction *aOpen = file->addAction(tr("&Open")); + aOpen->setShortcut(QKeySequence::Open); + + QMenu *view = _mainMenu->addMenu(tr("&View")); + QAction *aZoomIn = view->addAction(tr("Zoom &In")); + aZoomIn->setShortcut(QKeySequence::ZoomIn); + QAction *aZoomOut = view->addAction(tr("Zoom &Out")); + aZoomOut->setShortcut(QKeySequence::ZoomOut); + + connect(aNew, SIGNAL(triggered()), this, SLOT(newDoc())); + connect(aOpen, SIGNAL(triggered()), this, SLOT(open())); + connect(aZoomIn, SIGNAL(triggered()), this, SLOT(zoomIn())); + connect(aZoomOut, SIGNAL(triggered()), this, SLOT(zoomOut())); +} + +void Tikzit::loadStyles() +{ + _nodeStyles << NodeStyle("black dot", NodeShape::Circle, Qt::black, Qt::black, 1); + _nodeStyles << NodeStyle("white dot", NodeShape::Circle, Qt::white, Qt::black, 1); + _nodeStyles << NodeStyle("gray dot", NodeShape::Circle, Qt::gray, Qt::black, 1); +} + +void Tikzit::newDoc() +{ + MainWindow *w = new MainWindow(); + w->show(); + _windows << w; +} + +MainWindow *Tikzit::activeWindow() const +{ + return _activeWindow; +} + +void Tikzit::setActiveWindow(MainWindow *activeWindow) +{ + _activeWindow = activeWindow; +} + +void Tikzit::removeWindow(MainWindow *w) +{ + _windows.removeAll(w); + if (_activeWindow == w) { + if (_windows.isEmpty()) _activeWindow = 0; + else _activeWindow = _windows[0]; + } +} + +NodeStyle Tikzit::nodeStyle(QString name) +{ + foreach (NodeStyle s , _nodeStyles) + if (s.name == name) return s; + return NodeStyle(name, NodeShape::Circle, Qt::white); +} + +void Tikzit::open() +{ + QSettings settings("tikzit", "tikzit"); + QString fileName = QFileDialog::getOpenFileName(0, + tr("Open File"), + settings.value("previous-file-path").toString(), + tr("TiKZ Files (*.tikz)")); + + if (!fileName.isEmpty()) { + if (_windows.size() == 1 && _windows[0]->pristine()) { + _windows[0]->open(fileName); + _windows[0]->show(); + } else { + MainWindow *w = new MainWindow(); + w->show(); + w->open(fileName); + _windows << w; + } + } +} + +void Tikzit::zoomIn() +{ + if (_activeWindow != 0) _activeWindow->tikzView()->zoomIn(); +} + +void Tikzit::zoomOut() +{ + if (_activeWindow != 0) _activeWindow->tikzView()->zoomOut(); +} diff --git a/tikzit/src/tikzit.h b/tikzit/src/tikzit.h new file mode 100644 index 0000000..74a7ea6 --- /dev/null +++ b/tikzit/src/tikzit.h @@ -0,0 +1,73 @@ +#ifndef TIKZIT_H +#define TIKZIT_H + +#include "mainwindow.h" +#include "toolpalette.h" +#include "propertypalette.h" +#include "nodestyle.h" + +#include <QObject> +#include <QVector> +#include <QPointF> +#include <QMenuBar> +#include <QMainWindow> + +// 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 80 +#define GLOBAL_SCALEF 80.0f + +inline QPointF toScreen(QPointF src) +{ src.setY(-src.y()); src *= GLOBAL_SCALEF; return src; } + +inline QPointF fromScreen(QPointF src) +{ src.setY(-src.y()); src /= GLOBAL_SCALEF; return src; } + +// interpolate on a cubic bezier curve +inline float bezierInterpolate(float dist, float c0, float c1, float c2, float c3) { + float distp = 1 - dist; + return (distp*distp*distp) * c0 + + 3 * (distp*distp) * dist * c1 + + 3 * (dist*dist) * distp * c2 + + (dist*dist*dist) * c3; +} + +inline QPointF bezierInterpolateFull (float 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())); +} + +class Tikzit : public QObject { + Q_OBJECT +public: + Tikzit(); + QMenuBar *mainMenu() const; + ToolPalette *toolPalette() const; + PropertyPalette *propertyPalette() const; + + MainWindow *activeWindow() const; + void setActiveWindow(MainWindow *activeWindow); + void removeWindow(MainWindow *w); + NodeStyle nodeStyle(QString name); + +private: + void createMenu(); + void loadStyles(); + + QMenuBar *_mainMenu; + ToolPalette *_toolPalette; + PropertyPalette *_propertyPalette; + QVector<MainWindow*> _windows; + MainWindow *_activeWindow; + QVector<NodeStyle> _nodeStyles; + +public slots: + void newDoc(); + void open(); + void zoomIn(); + void zoomOut(); +}; + +extern Tikzit *tikzit; + +#endif // TIKZIT_H diff --git a/tikzit/tikzit.pro b/tikzit/tikzit.pro index 41bc6a3..cf0756f 100644 --- a/tikzit/tikzit.pro +++ b/tikzit/tikzit.pro @@ -40,7 +40,10 @@ SOURCES += src/gui/mainwindow.cpp \ src/data/graphelementproperty.cpp \ src/gui/propertypalette.cpp \ src/gui/tikzview.cpp \ - src/gui/nodeitem.cpp + src/gui/nodeitem.cpp \ + src/gui/edgeitem.cpp \ + src/tikzit.cpp \ + src/data/nodestyle.cpp HEADERS += src/gui/mainwindow.h \ src/gui/toolpalette.h \ @@ -54,17 +57,20 @@ HEADERS += src/gui/mainwindow.h \ src/gui/propertypalette.h \ src/data/tikzparserdefs.h \ src/gui/tikzview.h \ - src/gui/nodeitem.h + src/gui/nodeitem.h \ + src/tikzit.h \ + src/gui/edgeitem.h \ + src/data/nodestyle.h FORMS += src/gui/mainwindow.ui \ - src/gui/propertypalette.ui + src/gui/propertypalette.ui \ + src/gui/mainmenu.ui INCLUDEPATH += src src/gui src/data DISTFILES += -RESOURCES += \ - tikzit.qrc +RESOURCES += tikzit.qrc test { QT += testlib diff --git a/tikzit/tikzit.pro.user b/tikzit/tikzit.pro.user index 6d72c88..aca5fe8 100644 --- a/tikzit/tikzit.pro.user +++ b/tikzit/tikzit.pro.user @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE QtCreatorProject> -<!-- Written by QtCreator 4.2.0, 2017-02-02T17:06:07. --> +<!-- Written by QtCreator 4.2.0, 2017-02-27T21:48:49. --> <qtcreator> <data> <variable>EnvironmentId</variable> |