From 3cea1514203a451c0a8806d276807863b463a78f Mon Sep 17 00:00:00 2001 From: Aleks Kissinger Date: Sat, 17 Mar 2018 19:01:02 -0400 Subject: added saving, style application, and copy and paste --- src/data/edge.cpp | 22 ++++++ src/data/edge.h | 1 + src/data/graph.cpp | 82 +++++++++++++++++++- src/data/graph.h | 25 ++++++ src/data/graphelementdata.cpp | 17 +++- src/data/graphelementdata.h | 6 +- src/data/node.cpp | 35 +++++++-- src/data/node.h | 7 +- src/data/nodestyle.cpp | 10 +-- src/data/nodestyle.h | 10 +-- src/data/tikzdocument.cpp | 45 +++++++++++ src/data/tikzdocument.h | 3 + src/gui/mainmenu.cpp | 21 +++-- src/gui/mainwindow.cpp | 23 +++++- src/gui/mainwindow.h | 8 +- src/gui/nodeitem.cpp | 4 +- src/gui/tikzscene.cpp | 176 ++++++++++++++++++++++++++++++++++-------- src/gui/tikzscene.h | 10 ++- src/gui/toolpalette.cpp | 18 +++++ src/gui/toolpalette.h | 1 + src/gui/undocommands.cpp | 147 +++++++++++++++++++++++++++++++---- src/gui/undocommands.h | 63 +++++++++++---- src/main.cpp | 3 +- src/tikzit.cpp | 25 +++++- src/tikzit.h | 7 +- 25 files changed, 659 insertions(+), 110 deletions(-) (limited to 'src') diff --git a/src/data/edge.cpp b/src/data/edge.cpp index e4d5d69..d3396e8 100644 --- a/src/data/edge.cpp +++ b/src/data/edge.cpp @@ -25,6 +25,28 @@ Edge::~Edge() delete _edgeNode; } +/*! + * @brief Edge::copy makes a deep copy of an edge. + * @param nodeTable is an optional pointer to a table mapping the old source/target + * node pointers to their new, copied versions. This is used when making a copy of + * an entire (sub)graph. + * @return a copy of the edge + */ +Edge *Edge::copy(QMap *nodeTable) +{ + Edge *e; + if (nodeTable == 0) e = new Edge(_source, _target); + else e = new Edge(nodeTable->value(_source), nodeTable->value(_target)); + e->setData(_data->copy()); + e->setBasicBendMode(_basicBendMode); + e->setBend(_bend); + e->setInAngle(_inAngle); + e->setOutAngle(_outAngle); + e->setWeight(_weight); + e->updateControls(); + return e; +} + Node *Edge::source() const { return _source; diff --git a/src/data/edge.h b/src/data/edge.h index 595b094..f010acd 100644 --- a/src/data/edge.h +++ b/src/data/edge.h @@ -13,6 +13,7 @@ class Edge : public QObject public: explicit Edge(Node *s, Node *t, QObject *parent = 0); ~Edge(); + Edge *copy(QMap *nodeTable = 0); Node *source() const; Node *target() const; diff --git a/src/data/graph.cpp b/src/data/graph.cpp index ba9a4c6..7a5fedc 100644 --- a/src/data/graph.cpp +++ b/src/data/graph.cpp @@ -54,6 +54,32 @@ void Graph::removeEdge(Edge *e) _edges.removeOne(e); } +int Graph::maxIntName() +{ + int max = -1; + int i; + bool ok; + foreach (Node *n, _nodes) { + i = n->name().toInt(&ok); + if (ok && i > max) max = i; + } + return max; +} + +QString Graph::freshNodeName() +{ + return QString::number(maxIntName() + 1); +} + +void Graph::renameApart(Graph *graph) +{ + int i = graph->maxIntName() + 1; + foreach (Node *n, _nodes) { + n->setName(QString::number(i)); + i++; + } +} + GraphElementData *Graph::data() const { return _data; @@ -92,21 +118,27 @@ QString Graph::tikz() { QString str; QTextStream code(&str); + int line = 0; code << "\\begin{tikzpicture}" << _data->tikz() << "\n"; + line++; if (hasBbox()) { code << "\t\\path [use as bounding box] (" << _bbox.topLeft().x() << "," << _bbox.topLeft().y() << ") rectangle (" << _bbox.bottomRight().x() << "," << _bbox.bottomRight().y() << ");\n"; + line++; } - if (!_nodes.isEmpty()) + if (!_nodes.isEmpty()) { code << "\t\\begin{pgfonlayer}{nodelayer}\n"; + line++; + } Node *n; foreach (n, _nodes) { + n->setTikzLine(line); code << "\t\t\\node "; if (!n->data()->isEmpty()) @@ -115,17 +147,23 @@ QString Graph::tikz() code << "(" << n->name() << ") at (" << n->point().x() << ", " << n->point().y() << ") {" << n->label() << "};\n"; + line++; } - if (!_nodes.isEmpty()) + if (!_nodes.isEmpty()) { code << "\t\\end{pgfonlayer}\n"; + line++; + } - if (!_edges.isEmpty()) + if (!_edges.isEmpty()) { code << "\t\\begin{pgfonlayer}{edgelayer}\n"; + line++; + } Edge *e; foreach (e, _edges) { + e->updateData(); code << "\t\t\\draw "; if (!e->data()->isEmpty()) @@ -153,17 +191,53 @@ QString Graph::tikz() } code << ";\n"; + line++; } - if (!_edges.isEmpty()) + if (!_edges.isEmpty()) { code << "\t\\end{pgfonlayer}\n"; + line++; + } code << "\\end{tikzpicture}\n"; + line++; code.flush(); return str; } +Graph *Graph::copyOfSubgraphWithNodes(QSet nds) +{ + Graph *g = new Graph(); + g->setData(_data->copy()); + QMap nodeTable; + foreach (Node *n, nds) { + Node *n1 = n->copy(); + nodeTable.insert(n, n1); + g->addNode(n1); + } + foreach (Edge *e, edges()) { + if (nds.contains(e->source()) || nds.contains(e->target())) { + g->addEdge(e->copy(&nodeTable)); + } + } + + return g; +} + +void Graph::insertGraph(Graph *graph) +{ + QMap nodeTable; + foreach (Node *n, graph->nodes()) { + Node *n1 = n->copy(); + nodeTable.insert(n, n1); + addNode(n1); + } + foreach (Edge *e, graph->edges()) { + addEdge(e->copy(&nodeTable)); + } +} + void Graph::setBbox(const QRectF &bbox) { _bbox = bbox; diff --git a/src/data/graph.h b/src/data/graph.h index c25d51b..4d575e4 100644 --- a/src/data/graph.h +++ b/src/data/graph.h @@ -27,6 +27,15 @@ public: void addEdge(Edge *e); void addEdge(Edge *e, int index); void removeEdge(Edge *e); + int maxIntName(); + QString freshNodeName(); + + /*! + * \brief renameApart assigns fresh names to all of the nodes in "this", + * with respect to the given graph + * \param graph + */ + void renameApart(Graph *graph); GraphElementData *data() const; void setData(GraphElementData *data); @@ -40,6 +49,22 @@ public: void clearBbox(); QString tikz(); + + /*! + * \brief copyOfSubgraphWithNodes produces a copy of the full subgraph + * with the given nodes. Used for cutting and copying to clipboard. + * \param nds + * \return + */ + Graph *copyOfSubgraphWithNodes(QSet nds); + + /*! + * \brief insertGraph inserts a copy of the given graph. Prior to calling this + * method, the node names in the given graph should be made fresh via + * "renameApart". + * \param graph + */ + void insertGraph(Graph *graph); signals: public slots: diff --git a/src/data/graphelementdata.cpp b/src/data/graphelementdata.cpp index 43f7516..63c8cea 100644 --- a/src/data/graphelementdata.cpp +++ b/src/data/graphelementdata.cpp @@ -3,9 +3,14 @@ #include #include -GraphElementData::GraphElementData(QObject *parent) : QAbstractItemModel(parent) +GraphElementData::GraphElementData(QVector init, QObject *parent) : QAbstractItemModel(parent) { root = new GraphElementProperty(); + _properties = init; +} + +GraphElementData::GraphElementData(QObject *parent) : QAbstractItemModel(parent) { + root = new GraphElementProperty(); } GraphElementData::~GraphElementData() @@ -13,6 +18,11 @@ GraphElementData::~GraphElementData() delete root; } +GraphElementData *GraphElementData::copy() +{ + return new GraphElementData(_properties); +} + void GraphElementData::setProperty(QString key, QString value) { GraphElementProperty m(key, true); @@ -170,3 +180,8 @@ bool GraphElementData::isEmpty() { return _properties.isEmpty(); } + +QVector GraphElementData::properties() const +{ + return _properties; +} diff --git a/src/data/graphelementdata.h b/src/data/graphelementdata.h index 0d43bb8..319edf7 100644 --- a/src/data/graphelementdata.h +++ b/src/data/graphelementdata.h @@ -13,8 +13,11 @@ class GraphElementData : public QAbstractItemModel { Q_OBJECT public: + explicit GraphElementData(QVector init, + QObject *parent = 0); explicit GraphElementData(QObject *parent = 0); ~GraphElementData(); + GraphElementData *copy(); void setProperty(QString key, QString value); void unsetProperty(QString key); void setAtom(QString atom); @@ -22,7 +25,6 @@ public: QString property(QString key); bool atom(QString atom); - QVariant data(const QModelIndex &index, int role) const Q_DECL_OVERRIDE; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE; @@ -55,6 +57,8 @@ public: QString tikz(); bool isEmpty(); + QVector properties() const; + signals: public slots: diff --git a/src/data/node.cpp b/src/data/node.cpp index c78f49c..085bdf5 100644 --- a/src/data/node.cpp +++ b/src/data/node.cpp @@ -3,11 +3,11 @@ #include -Node::Node(QObject *parent) : QObject(parent) +Node::Node(QObject *parent) : QObject(parent), _tikzLine(-1) { _data = new GraphElementData(); _style = noneStyle; - _styleName = "none"; + _data->setProperty("style", "none"); } Node::~Node() @@ -15,6 +15,17 @@ Node::~Node() delete _data; } +Node *Node::copy() { + Node *n1 = new Node(); + n1->setName(name()); + n1->setData(data()->copy()); + n1->setPoint(point()); + n1->setLabel(label()); + n1->attachStyle(); + n1->setTikzLine(tikzLine()); + return n1; +} + QPointF Node::point() const { return _point; @@ -54,26 +65,36 @@ void Node::setData(GraphElementData *data) { delete _data; _data = data; - if (_data->property("style") != 0) _styleName = _data->property("style"); } QString Node::styleName() const { - return _styleName; + return _data->property("style"); } void Node::setStyleName(const QString &styleName) { - _styleName = styleName; + _data->setProperty("style", styleName); } void Node::attachStyle() { - if (_styleName == "none") _style = noneStyle; - else _style = tikzit->styles()->nodeStyle(_styleName); + QString nm = styleName(); + if (nm == "none") _style = noneStyle; + else _style = tikzit->styles()->nodeStyle(nm); } NodeStyle *Node::style() const { return _style; } + +int Node::tikzLine() const +{ + return _tikzLine; +} + +void Node::setTikzLine(int tikzLine) +{ + _tikzLine = tikzLine; +} diff --git a/src/data/node.h b/src/data/node.h index ee70835..241d1ca 100644 --- a/src/data/node.h +++ b/src/data/node.h @@ -15,6 +15,8 @@ public: explicit Node(QObject *parent = 0); ~Node(); + Node *copy(); + QPointF point() const; void setPoint(const QPointF &point); @@ -33,6 +35,9 @@ public: void attachStyle(); NodeStyle *style() const; + int tikzLine() const; + void setTikzLine(int tikzLine); + signals: public slots: @@ -41,9 +46,9 @@ private: QPointF _point; QString _name; QString _label; - QString _styleName; NodeStyle *_style; GraphElementData *_data; + int _tikzLine; }; #endif // NODE_H diff --git a/src/data/nodestyle.cpp b/src/data/nodestyle.cpp index e38d3a3..302ab84 100644 --- a/src/data/nodestyle.cpp +++ b/src/data/nodestyle.cpp @@ -24,13 +24,13 @@ QString NodeStyle::name() const return _name; } -NodeShape NodeStyle::shape() const +NodeStyle::Shape NodeStyle::shape() const { QString sh = _data->property("shape"); - if (sh.isNull()) return NodeShape::Circle; - else if (sh == "circle") return NodeShape::Circle; - else if (sh == "rectangle") return NodeShape::Rectangle; - else return NodeShape::Circle; + if (sh.isNull()) return NodeStyle::Circle; + else if (sh == "circle") return NodeStyle::Circle; + else if (sh == "rectangle") return NodeStyle::Rectangle; + else return NodeStyle::Circle; } QColor NodeStyle::fillColor() const diff --git a/src/data/nodestyle.h b/src/data/nodestyle.h index 58c0c12..0b9f282 100644 --- a/src/data/nodestyle.h +++ b/src/data/nodestyle.h @@ -9,20 +9,20 @@ #include #include -enum NodeShape { - Rectangle, UpTriangle, DownTriangle, Circle -}; - class NodeStyle { public: + enum Shape { + Rectangle, UpTriangle, DownTriangle, Circle + }; + NodeStyle(); NodeStyle(QString name, GraphElementData *data); bool isNone(); GraphElementData *data() const; QString name() const; - NodeShape shape() const; + Shape shape() const; QColor fillColor() const; QColor strokeColor() const; int strokeThickness() const; diff --git a/src/data/tikzdocument.cpp b/src/data/tikzdocument.cpp index a3fa961..eeb4e14 100644 --- a/src/data/tikzdocument.cpp +++ b/src/data/tikzdocument.cpp @@ -3,9 +3,12 @@ #include #include #include +#include +#include "tikzit.h" #include "tikzdocument.h" #include "tikzassembler.h" +#include "mainwindow.h" TikzDocument::TikzDocument(QObject *parent) : QObject(parent) { @@ -71,6 +74,41 @@ void TikzDocument::open(QString fileName) } } +void TikzDocument::save() { + if (_fileName == "") { + saveAs(); + } else { + refreshTikz(); + QFile file(_fileName); + QFileInfo fi(file); + _shortName = fi.fileName(); + QSettings settings("tikzit", "tikzit"); + settings.setValue("previous-file-path", fi.absolutePath()); + + if (file.open(QIODevice::ReadWrite)) { + QTextStream stream(&file); + stream << _tikz; + file.close(); + tikzit->activeWindow()->updateFileName(); + } else { + QMessageBox::warning(0, "Save Failed", "Could not open file: '" + _fileName + "' for writing."); + } + } +} + +void TikzDocument::saveAs() { + QSettings settings("tikzit", "tikzit"); + QString fileName = QFileDialog::getSaveFileName(tikzit->activeWindow(), + tr("Save File As"), + settings.value("previous-file-path").toString(), + tr("TiKZ Files (*.tikz)")); + + if (!fileName.isEmpty()) { + _fileName = fileName; + save(); + } +} + QString TikzDocument::shortName() const { return _shortName; @@ -80,3 +118,10 @@ bool TikzDocument::parseSuccess() const { return _parseSuccess; } + +void TikzDocument::refreshTikz() +{ + _tikz = _graph->tikz(); + if (MainWindow *w = dynamic_cast(parent())) + w->refreshTikz(); +} diff --git a/src/data/tikzdocument.h b/src/data/tikzdocument.h index d3a18b1..edb1beb 100644 --- a/src/data/tikzdocument.h +++ b/src/data/tikzdocument.h @@ -22,11 +22,14 @@ public: QString tikz() const; QUndoStack *undoStack() const; bool parseSuccess() const; + void refreshTikz(); void open(QString fileName); QString shortName() const; + void saveAs(); + void save(); private: Graph *_graph; QString _tikz; diff --git a/src/gui/mainmenu.cpp b/src/gui/mainmenu.cpp index 714ed34..dfb447f 100644 --- a/src/gui/mainmenu.cpp +++ b/src/gui/mainmenu.cpp @@ -19,17 +19,20 @@ void MainMenu::on_actionOpen_triggered() void MainMenu::on_actionClose_triggered() { - // TODO + if (tikzit->activeWindow() != 0) + tikzit->activeWindow()->close(); } void MainMenu::on_actionSave_triggered() { - // TODO + if (tikzit->activeWindow() != 0) + tikzit->activeWindow()->tikzDocument()->save(); } void MainMenu::on_actionSave_As_triggered() { - // TODO + if (tikzit->activeWindow() != 0) + tikzit->activeWindow()->tikzDocument()->saveAs(); } void MainMenu::on_actionExit_triggered() @@ -53,22 +56,26 @@ void MainMenu::on_actionRedo_triggered() void MainMenu::on_actionCut_triggered() { - // TODO + if (tikzit->activeWindow() != 0) + tikzit->activeWindow()->tikzScene()->cutToClipboard(); } void MainMenu::on_actionCopy_triggered() { - // TODO + if (tikzit->activeWindow() != 0) + tikzit->activeWindow()->tikzScene()->copyToClipboard(); } void MainMenu::on_actionPaste_triggered() { - // TODO + if (tikzit->activeWindow() != 0) + tikzit->activeWindow()->tikzScene()->pasteFromClipboard(); } void MainMenu::on_actionDelete_triggered() { - // TODO + if (tikzit->activeWindow() != 0) + tikzit->activeWindow()->tikzScene()->deleteSelectedItems(); } void MainMenu::on_actionSelect_All_triggered() diff --git a/src/gui/mainwindow.cpp b/src/gui/mainwindow.cpp index eac7c44..26e19b6 100644 --- a/src/gui/mainwindow.cpp +++ b/src/gui/mainwindow.cpp @@ -25,12 +25,11 @@ MainWindow::MainWindow(QWidget *parent) : setAttribute(Qt::WA_DeleteOnClose, true); _tikzDocument = new TikzDocument(this); - _tools = new ToolPalette(this); - addToolBar(_tools); + _toolPalette = new ToolPalette(this); + addToolBar(_toolPalette); - _tikzScene = new TikzScene(_tikzDocument, _tools, this); + _tikzScene = new TikzScene(_tikzDocument, _toolPalette, this); ui->tikzView->setScene(_tikzScene); - _fileName = ""; _pristine = true; @@ -80,10 +79,26 @@ void MainWindow::changeEvent(QEvent *event) { if (event->type() == QEvent::ActivationChange && isActiveWindow()) { tikzit->setActiveWindow(this); + tikzit->stylePalette()->raise(); } QMainWindow::changeEvent(event); } +void MainWindow::updateFileName() +{ + setWindowTitle("TiKZiT - " + _tikzDocument->shortName()); +} + +void MainWindow::refreshTikz() +{ + ui->tikzSource->setText(_tikzDocument->tikz()); +} + +ToolPalette *MainWindow::toolPalette() const +{ + return _toolPalette; +} + TikzDocument *MainWindow::tikzDocument() const { return _tikzDocument; diff --git a/src/gui/mainwindow.h b/src/gui/mainwindow.h index ba680b0..613bfcb 100644 --- a/src/gui/mainwindow.h +++ b/src/gui/mainwindow.h @@ -33,17 +33,19 @@ public: TikzView *tikzView() const; TikzScene *tikzScene() const; TikzDocument *tikzDocument() const; - + ToolPalette *toolPalette() const; + void updateFileName(); + void refreshTikz(); protected: void closeEvent(QCloseEvent *event); void changeEvent(QEvent *event); + private: TikzScene *_tikzScene; TikzDocument *_tikzDocument; MainMenu *_menu; - ToolPalette *_tools; + ToolPalette *_toolPalette; Ui::MainWindow *ui; - QString _fileName; bool _pristine; int _windowId; static int _numWindows; diff --git a/src/gui/nodeitem.cpp b/src/gui/nodeitem.cpp index 21cdf79..36d488c 100644 --- a/src/gui/nodeitem.cpp +++ b/src/gui/nodeitem.cpp @@ -15,8 +15,8 @@ NodeItem::NodeItem(Node *node) { _node = node; setFlag(QGraphicsItem::ItemIsSelectable); - setFlag(QGraphicsItem::ItemIsMovable); - setFlag(QGraphicsItem::ItemSendsGeometryChanges); + //setFlag(QGraphicsItem::ItemIsMovable); + //setFlag(QGraphicsItem::ItemSendsGeometryChanges); readPos(); } diff --git a/src/gui/tikzscene.cpp b/src/gui/tikzscene.cpp index a3dd8ce..2ee3c50 100644 --- a/src/gui/tikzscene.cpp +++ b/src/gui/tikzscene.cpp @@ -2,10 +2,12 @@ #include "util.h" #include "tikzscene.h" #include "undocommands.h" +#include "tikzassembler.h" #include #include #include +#include TikzScene::TikzScene(TikzDocument *tikzDocument, ToolPalette *tools, QObject *parent) : @@ -63,7 +65,9 @@ void TikzScene::graphReplaced() void TikzScene::mousePressEvent(QGraphicsSceneMouseEvent *event) { // current mouse position, in scene coordinates - QPointF mousePos = event->scenePos(); + _mouseDownPos = event->scenePos(); + + _draggingNodes = false; // disable rubber band drag, which will clear the selection. Only re-enable it // for the SELECT tool, and when no control point has been clicked. @@ -80,8 +84,8 @@ void TikzScene::mousePressEvent(QGraphicsSceneMouseEvent *event) if (EdgeItem *ei = dynamic_cast(gi)) { qreal dx, dy; - dx = ei->cp1Item()->pos().x() - mousePos.x(); - dy = ei->cp1Item()->pos().y() - mousePos.y(); + dx = ei->cp1Item()->pos().x() - _mouseDownPos.x(); + dy = ei->cp1Item()->pos().y() - _mouseDownPos.y(); if (dx*dx + dy*dy <= cpR2) { _modifyEdgeItem = ei; @@ -89,8 +93,8 @@ void TikzScene::mousePressEvent(QGraphicsSceneMouseEvent *event) break; } - dx = ei->cp2Item()->pos().x() - mousePos.x(); - dy = ei->cp2Item()->pos().y() - mousePos.y(); + dx = ei->cp2Item()->pos().x() - _mouseDownPos.x(); + dy = ei->cp2Item()->pos().y() - _mouseDownPos.y(); if (dx*dx + dy*dy <= cpR2) { _modifyEdgeItem = ei; @@ -119,17 +123,21 @@ void TikzScene::mousePressEvent(QGraphicsSceneMouseEvent *event) _oldNodePositions.insert(ni->node(), ni->node()->point()); } } + + auto its = items(_mouseDownPos); + if (!its.isEmpty() && dynamic_cast(its[0])) + _draggingNodes = true; } break; case ToolPalette::VERTEX: break; case ToolPalette::EDGE: - foreach (QGraphicsItem *gi, items(mousePos)) { + foreach (QGraphicsItem *gi, items(_mouseDownPos)) { if (NodeItem *ni = dynamic_cast(gi)){ _edgeStartNodeItem = ni; _edgeEndNodeItem = ni; - QLineF line(toScreen(ni->node()->point()), mousePos); + QLineF line(toScreen(ni->node()->point()), _mouseDownPos); _drawEdgeItem->setLine(line); _drawEdgeItem->setVisible(true); break; @@ -145,8 +153,9 @@ void TikzScene::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { // current mouse position, in scene coordinates QPointF mousePos = event->scenePos(); - QRectF rb = views()[0]->rubberBandRect(); - invalidate(-800,-800,1600,1600); + //QRectF rb = views()[0]->rubberBandRect(); + //invalidate(-800,-800,1600,1600); + invalidate(QRectF(), QGraphicsScene::BackgroundLayer); switch (_tools->currentTool()) { case ToolPalette::SELECT: @@ -229,10 +238,25 @@ void TikzScene::mouseMoveEvent(QGraphicsSceneMouseEvent *event) _modifyEdgeItem->readPos(); + } else if (_draggingNodes) { // nodes being dragged + QGraphicsScene::mouseMoveEvent(event); + + // apply the same offset to all nodes, otherwise we get odd rounding behaviour with + // multiple selection. + QPointF shift = mousePos - _mouseDownPos; + int gridSize = GLOBAL_SCALE / 8; + shift = QPointF(round(shift.x()/gridSize)*gridSize, round(shift.y()/gridSize)*gridSize); + + foreach (Node *n, _oldNodePositions.keys()) { + NodeItem *ni = _nodeItems[n]; + ni->setPos(toScreen(_oldNodePositions[n]) + shift); + ni->writePos(); + } + + refreshAdjacentEdges(_oldNodePositions.keys()); } else { // otherwise, process mouse move normally QGraphicsScene::mouseMoveEvent(event); - refreshAdjacentEdges(_oldNodePositions.keys()); } break; @@ -308,7 +332,9 @@ void TikzScene::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) int gridSize = GLOBAL_SCALE / 8; QPointF gridPos(round(mousePos.x()/gridSize)*gridSize, round(mousePos.y()/gridSize)*gridSize); Node *n = new Node(_tikzDocument); + n->setName(graph()->freshNodeName()); n->setPoint(fromScreen(gridPos)); + n->setStyleName(tikzit->stylePalette()->activeNodeStyleName()); QRectF grow(gridPos.x() - GLOBAL_SCALEF, gridPos.y() - GLOBAL_SCALEF, 2 * GLOBAL_SCALEF, 2 * GLOBAL_SCALEF); QRectF newBounds = sceneRect().united(grow); @@ -332,34 +358,31 @@ void TikzScene::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) case ToolPalette::CROP: break; } + + // clear artefacts from rubber band selection + invalidate(QRect(), QGraphicsScene::BackgroundLayer); } void TikzScene::keyReleaseEvent(QKeyEvent *event) { if (event->key() == Qt::Key_Backspace || event->key() == Qt::Key_Delete) { - QSet selNodes; - QSet selEdges; - getSelection(selNodes, selEdges); - - QMap deleteNodes; - QMap deleteEdges; - - for (int i = 0; i < _tikzDocument->graph()->nodes().length(); ++i) { - Node *n = _tikzDocument->graph()->nodes()[i]; - if (selNodes.contains(n)) deleteNodes.insert(i, n); - } - - for (int i = 0; i < _tikzDocument->graph()->edges().length(); ++i) { - Edge *e = _tikzDocument->graph()->edges()[i]; - if (selEdges.contains(e) || - selNodes.contains(e->source()) || - selNodes.contains(e->target())) deleteEdges.insert(i, e); + deleteSelectedItems(); + } else if (event->modifiers() == Qt::NoModifier) { + switch(event->key()) { + case Qt::Key_S: + tikzit->activeWindow()->toolPalette()->setCurrentTool(ToolPalette::SELECT); + break; + case Qt::Key_V: + case Qt::Key_N: + tikzit->activeWindow()->toolPalette()->setCurrentTool(ToolPalette::VERTEX); + break; + case Qt::Key_E: + tikzit->activeWindow()->toolPalette()->setCurrentTool(ToolPalette::EDGE); + break; + case Qt::Key_B: + tikzit->activeWindow()->toolPalette()->setCurrentTool(ToolPalette::CROP); + break; } - - //qDebug() << "nodes:" << deleteNodes; - //qDebug() << "edges:" << deleteEdges; - DeleteCommand *cmd = new DeleteCommand(this, deleteNodes, deleteEdges, selEdges); - _tikzDocument->undoStack()->push(cmd); } } @@ -375,6 +398,78 @@ void TikzScene::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) } } +void TikzScene::applyActiveStyleToNodes() { + ApplyStyleToNodesCommand *cmd = new ApplyStyleToNodesCommand(this, tikzit->stylePalette()->activeNodeStyleName()); + _tikzDocument->undoStack()->push(cmd); +} + +void TikzScene::deleteSelectedItems() +{ + QSet selNodes; + QSet selEdges; + getSelection(selNodes, selEdges); + + QMap deleteNodes; + QMap deleteEdges; + + for (int i = 0; i < _tikzDocument->graph()->nodes().length(); ++i) { + Node *n = _tikzDocument->graph()->nodes()[i]; + if (selNodes.contains(n)) deleteNodes.insert(i, n); + } + + for (int i = 0; i < _tikzDocument->graph()->edges().length(); ++i) { + Edge *e = _tikzDocument->graph()->edges()[i]; + if (selEdges.contains(e) || + selNodes.contains(e->source()) || + selNodes.contains(e->target())) deleteEdges.insert(i, e); + } + + //qDebug() << "nodes:" << deleteNodes; + //qDebug() << "edges:" << deleteEdges; + DeleteCommand *cmd = new DeleteCommand(this, deleteNodes, deleteEdges, selEdges); + _tikzDocument->undoStack()->push(cmd); +} + +void TikzScene::copyToClipboard() +{ + Graph *g = graph()->copyOfSubgraphWithNodes(getSelectedNodes()); + QGuiApplication::clipboard()->setText(g->tikz()); + delete g; +} + +void TikzScene::cutToClipboard() +{ + copyToClipboard(); + deleteSelectedItems(); +} + + +void TikzScene::pasteFromClipboard() +{ + QString tikz = QGuiApplication::clipboard()->text(); + Graph *g = new Graph(); + TikzAssembler ass(g); + + // attempt to parse whatever's on the clipboard, if we get a + // non-empty tikz graph, insert it. + if (ass.parse(tikz) && !g->nodes().isEmpty()) { + // make sure names in the new subgraph are fresh + g->renameApart(graph()); + + // shift g to the right until there is some free space + QPointF p0 = toScreen(g->nodes()[0]->point()); + QPointF p = p0; + while (!items(p).isEmpty()) p.setX(p.x()+GLOBAL_SCALEF); + QPointF shift(roundf((p.x() - p0.x())/GLOBAL_SCALEF), 0.0f); + foreach (Node *n, g->nodes()) { + n->setPoint(n->point() + shift); + } + + PasteCommand *cmd = new PasteCommand(this, g); + _tikzDocument->undoStack()->push(cmd); + } +} + void TikzScene::getSelection(QSet &selNodes, QSet &selEdges) { foreach (QGraphicsItem *gi, selectedItems()) { @@ -383,6 +478,15 @@ void TikzScene::getSelection(QSet &selNodes, QSet &selEdges) } } +QSet TikzScene::getSelectedNodes() +{ + QSet selNodes; + foreach (QGraphicsItem *gi, selectedItems()) { + if (NodeItem *ni = dynamic_cast(gi)) selNodes << ni->node(); + } + return selNodes; +} + TikzDocument *TikzScene::tikzDocument() const { @@ -395,6 +499,14 @@ void TikzScene::setTikzDocument(TikzDocument *tikzDocument) graphReplaced(); } +void TikzScene::reloadStyles() +{ + foreach (NodeItem *ni, _nodeItems) { + ni->node()->attachStyle(); + ni->readPos(); // trigger a repaint + } +} + void TikzScene::refreshAdjacentEdges(QList nodes) { if (nodes.empty()) return; diff --git a/src/gui/tikzscene.h b/src/gui/tikzscene.h index cb684b2..5d3eec2 100644 --- a/src/gui/tikzscene.h +++ b/src/gui/tikzscene.h @@ -34,7 +34,12 @@ public: TikzDocument *tikzDocument() const; void setTikzDocument(TikzDocument *tikzDocument); - + void reloadStyles(); + void applyActiveStyleToNodes(); + void deleteSelectedItems(); + void copyToClipboard(); + void cutToClipboard(); + void pasteFromClipboard(); public slots: void graphReplaced(); @@ -54,6 +59,8 @@ private: NodeItem *_edgeStartNodeItem; NodeItem *_edgeEndNodeItem; bool _firstControlPoint; + QPointF _mouseDownPos; + bool _draggingNodes; QMap _oldNodePositions; float _oldWeight; @@ -62,6 +69,7 @@ private: int _oldOutAngle; void getSelection(QSet &selNodes, QSet &selEdges); + QSet getSelectedNodes(); }; #endif // TIKZSCENE_H diff --git a/src/gui/toolpalette.cpp b/src/gui/toolpalette.cpp index 0a832a6..0d0bd30 100644 --- a/src/gui/toolpalette.cpp +++ b/src/gui/toolpalette.cpp @@ -48,3 +48,21 @@ ToolPalette::Tool ToolPalette::currentTool() const else return SELECT; } +void ToolPalette::setCurrentTool(ToolPalette::Tool tool) +{ + switch(tool) { + case SELECT: + select->setChecked(true); + break; + case VERTEX: + vertex->setChecked(true); + break; + case EDGE: + edge->setChecked(true); + break; + case CROP: + crop->setChecked(true); + break; + } +} + diff --git a/src/gui/toolpalette.h b/src/gui/toolpalette.h index c28b5a1..1876043 100644 --- a/src/gui/toolpalette.h +++ b/src/gui/toolpalette.h @@ -23,6 +23,7 @@ public: }; Tool currentTool() const; + void setCurrentTool(Tool tool); private: QActionGroup *tools; QAction *select; diff --git a/src/gui/undocommands.cpp b/src/gui/undocommands.cpp index 9c6a9c3..0ebfd21 100644 --- a/src/gui/undocommands.cpp +++ b/src/gui/undocommands.cpp @@ -4,12 +4,28 @@ #include +GraphUpdateCommand::GraphUpdateCommand(TikzScene *scene, QUndoCommand *parent) : QUndoCommand(parent), _scene(scene) +{ +} + +void GraphUpdateCommand::undo() +{ + _scene->tikzDocument()->refreshTikz(); + _scene->invalidate(); +} + +void GraphUpdateCommand::redo() +{ + _scene->tikzDocument()->refreshTikz(); + _scene->invalidate(); +} + + MoveCommand::MoveCommand(TikzScene *scene, QMap oldNodePositions, QMap newNodePositions, QUndoCommand *parent) : - QUndoCommand(parent), - _scene(scene), + GraphUpdateCommand(scene, parent), _oldNodePositions(oldNodePositions), _newNodePositions(newNodePositions) {} @@ -25,6 +41,7 @@ void MoveCommand::undo() } _scene->refreshAdjacentEdges(_oldNodePositions.keys()); + GraphUpdateCommand::undo(); } void MoveCommand::redo() @@ -37,12 +54,14 @@ void MoveCommand::redo() } _scene->refreshAdjacentEdges(_newNodePositions.keys()); + GraphUpdateCommand::redo(); } EdgeBendCommand::EdgeBendCommand(TikzScene *scene, Edge *edge, float oldWeight, int oldBend, - int oldInAngle, int oldOutAngle) : - _scene(scene), _edge(edge), + int oldInAngle, int oldOutAngle, QUndoCommand *parent) : + GraphUpdateCommand(scene, parent), + _edge(edge), _oldWeight(oldWeight), _oldBend(oldBend), _oldInAngle(oldInAngle), _oldOutAngle(oldOutAngle) { @@ -65,6 +84,7 @@ void EdgeBendCommand::undo() break; } } + GraphUpdateCommand::undo(); } void EdgeBendCommand::redo() @@ -80,20 +100,23 @@ void EdgeBendCommand::redo() break; } } + + GraphUpdateCommand::redo(); } DeleteCommand::DeleteCommand(TikzScene *scene, QMap deleteNodes, QMap deleteEdges, - QSet selEdges) : - _scene(scene), _deleteNodes(deleteNodes), - _deleteEdges(deleteEdges), _selEdges(selEdges) + QSet selEdges, QUndoCommand *parent) : + GraphUpdateCommand(scene, parent), + _deleteNodes(deleteNodes), _deleteEdges(deleteEdges), _selEdges(selEdges) {} void DeleteCommand::undo() { for (auto it = _deleteNodes.begin(); it != _deleteNodes.end(); ++it) { Node *n = it.value(); + n->attachStyle(); // in case styles have changed _scene->graph()->addNode(n, it.key()); NodeItem *ni = new NodeItem(n); _scene->nodeItems().insert(n, ni); @@ -110,6 +133,8 @@ void DeleteCommand::undo() if (_selEdges.contains(e)) ei->setSelected(true); } + + GraphUpdateCommand::undo(); } void DeleteCommand::redo() @@ -131,10 +156,12 @@ void DeleteCommand::redo() _scene->graph()->removeNode(n); } + + GraphUpdateCommand::redo(); } -AddNodeCommand::AddNodeCommand(TikzScene *scene, Node *node, QRectF newBounds) : - _scene(scene), _node(node), _oldBounds(_scene->sceneRect()), _newBounds(newBounds) +AddNodeCommand::AddNodeCommand(TikzScene *scene, Node *node, QRectF newBounds, QUndoCommand *parent) : + GraphUpdateCommand(scene, parent), _node(node), _oldBounds(_scene->sceneRect()), _newBounds(newBounds) { } @@ -148,21 +175,24 @@ void AddNodeCommand::undo() _scene->graph()->removeNode(_node); _scene->setBounds(_oldBounds); + + GraphUpdateCommand::undo(); } void AddNodeCommand::redo() { - // TODO: get the current style + _node->attachStyle(); // in case styles have changed _scene->graph()->addNode(_node); NodeItem *ni = new NodeItem(_node); _scene->nodeItems().insert(_node, ni); _scene->addItem(ni); _scene->setBounds(_newBounds); + GraphUpdateCommand::redo(); } -AddEdgeCommand::AddEdgeCommand(TikzScene *scene, Edge *edge) : - _scene(scene), _edge(edge) +AddEdgeCommand::AddEdgeCommand(TikzScene *scene, Edge *edge, QUndoCommand *parent) : + GraphUpdateCommand(scene, parent), _edge(edge) { } @@ -174,6 +204,7 @@ void AddEdgeCommand::undo() delete ei; _scene->graph()->removeEdge(_edge); + GraphUpdateCommand::undo(); } void AddEdgeCommand::redo() @@ -188,10 +219,12 @@ void AddEdgeCommand::redo() if (!_scene->graph()->nodes().isEmpty()) { ei->stackBefore(_scene->nodeItems()[_scene->graph()->nodes().first()]); } + + GraphUpdateCommand::redo(); } -ChangeEdgeModeCommand::ChangeEdgeModeCommand(TikzScene *scene, Edge *edge) : - _scene(scene), _edge(edge) +ChangeEdgeModeCommand::ChangeEdgeModeCommand(TikzScene *scene, Edge *edge, QUndoCommand *parent) : + GraphUpdateCommand(scene, parent), _edge(edge) { } @@ -199,10 +232,96 @@ void ChangeEdgeModeCommand::undo() { _edge->setBasicBendMode(!_edge->basicBendMode()); _scene->edgeItems()[_edge]->readPos(); + GraphUpdateCommand::undo(); } void ChangeEdgeModeCommand::redo() { _edge->setBasicBendMode(!_edge->basicBendMode()); _scene->edgeItems()[_edge]->readPos(); + GraphUpdateCommand::redo(); +} + +ApplyStyleToNodesCommand::ApplyStyleToNodesCommand(TikzScene *scene, QString style, QUndoCommand *parent) : + GraphUpdateCommand(scene, parent), _style(style), _oldStyles() +{ + foreach (QGraphicsItem *it, scene->selectedItems()) { + if (NodeItem *ni = dynamic_cast(it)) { + _oldStyles.insert(ni->node(), ni->node()->styleName()); + } + } +} + +void ApplyStyleToNodesCommand::undo() +{ + foreach (Node *n, _oldStyles.keys()) { + n->setStyleName(_oldStyles[n]); + n->attachStyle(); + } + + GraphUpdateCommand::undo(); +} + +void ApplyStyleToNodesCommand::redo() +{ + foreach (Node *n, _oldStyles.keys()) { + n->setStyleName(_style); + n->attachStyle(); + } + GraphUpdateCommand::redo(); +} + +PasteCommand::PasteCommand(TikzScene *scene, Graph *graph, QUndoCommand *parent) : + GraphUpdateCommand(scene, parent), _graph(graph) +{ + _oldSelection = scene->selectedItems(); +} + +void PasteCommand::undo() +{ + _scene->clearSelection(); + + foreach (Edge *e, _graph->edges()) { + EdgeItem *ei = _scene->edgeItems()[e]; + _scene->edgeItems().remove(e); + _scene->removeItem(ei); + delete ei; + + _scene->graph()->removeEdge(e); + } + + foreach (Node *n, _graph->nodes()) { + NodeItem *ni = _scene->nodeItems()[n]; + _scene->nodeItems().remove(n); + _scene->removeItem(ni); + delete ni; + + _scene->graph()->removeNode(n); + } + + foreach (auto it, _oldSelection) it->setSelected(true); + + GraphUpdateCommand::undo(); +} + +void PasteCommand::redo() +{ + _scene->clearSelection(); + _scene->graph()->insertGraph(_graph); + + foreach (Node *n, _graph->nodes()) { + n->attachStyle(); // in case styles have changed + NodeItem *ni = new NodeItem(n); + _scene->nodeItems().insert(n, ni); + _scene->addItem(ni); + ni->setSelected(true); + } + + foreach (Edge *e, _graph->edges()) { + EdgeItem *ei = new EdgeItem(e); + _scene->edgeItems().insert(e, ei); + _scene->addItem(ei); + } + + GraphUpdateCommand::redo(); } diff --git a/src/gui/undocommands.h b/src/gui/undocommands.h index eea39ae..354e455 100644 --- a/src/gui/undocommands.h +++ b/src/gui/undocommands.h @@ -14,7 +14,17 @@ #include -class MoveCommand : public QUndoCommand +class GraphUpdateCommand : public QUndoCommand { +public: + explicit GraphUpdateCommand(TikzScene *scene, + QUndoCommand *parent = 0); + void undo() override; + void redo() override; +protected: + TikzScene *_scene; +}; + +class MoveCommand : public GraphUpdateCommand { public: explicit MoveCommand(TikzScene *scene, @@ -24,21 +34,20 @@ public: void undo() override; void redo() override; private: - TikzScene *_scene; QMap _oldNodePositions; QMap _newNodePositions; }; -class EdgeBendCommand : public QUndoCommand +class EdgeBendCommand : public GraphUpdateCommand { public: explicit EdgeBendCommand(TikzScene *scene, Edge *edge, float oldWeight, int oldBend, - int oldInAngle, int oldOutAngle); + int oldInAngle, int oldOutAngle, + QUndoCommand *parent = 0); void undo() override; void redo() override; private: - TikzScene *_scene; Edge *_edge; float _oldWeight; int _oldBend; @@ -50,55 +59,75 @@ private: int _newOutAngle; }; -class DeleteCommand : public QUndoCommand +class DeleteCommand : public GraphUpdateCommand { public: explicit DeleteCommand(TikzScene *scene, QMap deleteNodes, QMap deleteEdges, - QSet selEdges); + QSet selEdges, + QUndoCommand *parent = 0); void undo() override; void redo() override; private: - TikzScene *_scene; QMap _deleteNodes; QMap _deleteEdges; QSet _selEdges; }; -class AddNodeCommand : public QUndoCommand +class AddNodeCommand : public GraphUpdateCommand { public: - explicit AddNodeCommand(TikzScene *scene, Node *node, QRectF newBounds); + explicit AddNodeCommand(TikzScene *scene, Node *node, QRectF newBounds, + QUndoCommand *parent = 0); void undo() override; void redo() override; private: - TikzScene *_scene; Node *_node; QRectF _oldBounds; QRectF _newBounds; }; -class AddEdgeCommand : public QUndoCommand +class AddEdgeCommand : public GraphUpdateCommand { public: - explicit AddEdgeCommand(TikzScene *scene, Edge *edge); + explicit AddEdgeCommand(TikzScene *scene, Edge *edge, QUndoCommand *parent = 0); void undo() override; void redo() override; private: - TikzScene *_scene; Edge *_edge; }; -class ChangeEdgeModeCommand : public QUndoCommand +class ChangeEdgeModeCommand : public GraphUpdateCommand { public: - explicit ChangeEdgeModeCommand(TikzScene *scene, Edge *edge); + explicit ChangeEdgeModeCommand(TikzScene *scene, Edge *edge, QUndoCommand *parent = 0); void undo() override; void redo() override; private: - TikzScene *_scene; Edge *_edge; }; +class ApplyStyleToNodesCommand : public GraphUpdateCommand +{ +public: + explicit ApplyStyleToNodesCommand(TikzScene *scene, QString style, QUndoCommand *parent = 0); + void undo() override; + void redo() override; +private: + QString _style; + QMap _oldStyles; +}; + +class PasteCommand : public GraphUpdateCommand +{ +public: + explicit PasteCommand(TikzScene *scene, Graph *graph, QUndoCommand *parent = 0); + void undo() override; + void redo() override; +private: + Graph *_graph; + QList _oldSelection; +}; + #endif // UNDOCOMMANDS_H diff --git a/src/main.cpp b/src/main.cpp index 49b064d..4433f58 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -11,7 +11,6 @@ - int main(int argc, char *argv[]) { QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); @@ -20,7 +19,7 @@ int main(int argc, char *argv[]) QApplication a(argc, argv); a.setQuitOnLastWindowClosed(false); tikzit = new Tikzit(); - tikzit->init(); + tikzit->init(&a); return a.exec(); } diff --git a/src/tikzit.cpp b/src/tikzit.cpp index 6ef86dd..53e83b6 100644 --- a/src/tikzit.cpp +++ b/src/tikzit.cpp @@ -18,7 +18,7 @@ Tikzit::Tikzit() : _styleFile("[default]"), _activeWindow(0) { } -void Tikzit::init() +void Tikzit::init(QApplication *app) { QSettings settings("tikzit", "tikzit"); _mainMenu = new MainMenu(); @@ -35,6 +35,8 @@ void Tikzit::init() QString styleFile = settings.value("previous-tikzstyles-file").toString(); if (!styleFile.isEmpty()) loadStyles(styleFile); + + connect(app, &QApplication::focusChanged, this, &focusChanged); } //QMenuBar *Tikzit::mainMenu() const @@ -137,9 +139,13 @@ void Tikzit::loadStyles(QString fileName) } _stylePalette->reloadStyles(); + foreach (MainWindow *w, _windows) { + w->tikzScene()->reloadStyles(); + } + } else { settings.setValue("previous-tikzstyles-file", ""); - QMessageBox::warning(0, "Style file not found.", "Could not open style file, reverting to default."); + QMessageBox::warning(0, "Style file not found.", "Could not open style file: '" + fileName + "', reverting to default."); } } @@ -148,6 +154,21 @@ QString Tikzit::styleFile() const return _styleFile; } +void Tikzit::focusChanged(QWidget *old, QWidget *nw) +{ +// foreach (MainWindow *w, _windows) { +// if (w->isActiveWindow()) { +// _stylePalette->raise(); +// break; +// } +// } +} + +StylePalette *Tikzit::stylePalette() const +{ + return _stylePalette; +} + TikzStyles *Tikzit::styles() const { diff --git a/src/tikzit.h b/src/tikzit.h index 802b3ab..51aea20 100644 --- a/src/tikzit.h +++ b/src/tikzit.h @@ -77,15 +77,18 @@ public: void newDoc(); void open(); void quit(); - void init(); + void init(QApplication *app); void openTikzStyles(); + void loadStyles(QString fileName); TikzStyles *styles() const; QString styleFile() const; + StylePalette *stylePalette() const; +public slots: + void focusChanged(QWidget *old, QWidget *nw); private: // void createMenu(); - void loadStyles(QString fileName); MainMenu *_mainMenu; ToolPalette *_toolPalette; -- cgit v1.2.3