diff options
Diffstat (limited to 'src/gui')
-rw-r--r-- | src/gui/edgeitem.cpp | 87 | ||||
-rw-r--r-- | src/gui/mainmenu.cpp | 24 | ||||
-rw-r--r-- | src/gui/mainmenu.h | 3 | ||||
-rw-r--r-- | src/gui/mainmenu.ui | 34 | ||||
-rw-r--r-- | src/gui/pathitem.cpp | 68 | ||||
-rw-r--r-- | src/gui/pathitem.h | 28 | ||||
-rw-r--r-- | src/gui/styleeditor.cpp | 81 | ||||
-rw-r--r-- | src/gui/styleeditor.h | 2 | ||||
-rw-r--r-- | src/gui/styleeditor.ui | 38 | ||||
-rw-r--r-- | src/gui/tikzscene.cpp | 244 | ||||
-rw-r--r-- | src/gui/tikzscene.h | 6 | ||||
-rw-r--r-- | src/gui/tikzview.cpp | 2 | ||||
-rw-r--r-- | src/gui/undocommands.cpp | 140 | ||||
-rw-r--r-- | src/gui/undocommands.h | 31 |
14 files changed, 704 insertions, 84 deletions
diff --git a/src/gui/edgeitem.cpp b/src/gui/edgeitem.cpp index 45ae159..675ddd7 100644 --- a/src/gui/edgeitem.cpp +++ b/src/gui/edgeitem.cpp @@ -71,7 +71,8 @@ void EdgeItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidge QPen pen = _edge->style()->pen(); painter->setPen(pen); painter->setBrush(Qt::NoBrush); - painter->drawPath(path()); + + if (!_edge->path()) painter->drawPath(path()); QPointF ht = _edge->headTangent(); QPointF hLeft(-ht.y(), ht.x()); @@ -83,27 +84,27 @@ void EdgeItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidge pen.setStyle(Qt::SolidLine); painter->setPen(pen); - - - switch (_edge->style()->arrowHead()) { - case Style::Flat: - { - painter->drawLine( - toScreen(_edge->head() + hLeft), - toScreen(_edge->head() + hRight)); - break; - } - case Style::Pointer: - { - QPainterPath pth; - pth.moveTo(toScreen(_edge->head() + ht + hLeft)); - pth.lineTo(toScreen(_edge->head())); - pth.lineTo(toScreen(_edge->head() + ht + hRight)); - painter->drawPath(pth); - break; - } - case Style::NoTip: - break; + if (!_edge->path() || _edge->path()->edges().last() == _edge) { + switch (_edge->style()->arrowHead()) { + case Style::Flat: + { + painter->drawLine( + toScreen(_edge->head() + hLeft), + toScreen(_edge->head() + hRight)); + break; + } + case Style::Pointer: + { + QPainterPath pth; + pth.moveTo(toScreen(_edge->head() + ht + hLeft)); + pth.lineTo(toScreen(_edge->head())); + pth.lineTo(toScreen(_edge->head() + ht + hRight)); + painter->drawPath(pth); + break; + } + case Style::NoTip: + break; + } } //QPen outline = QPen(Qt::red); @@ -111,26 +112,28 @@ void EdgeItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidge //painter->drawPath(_expPath); //painter->setPen(pen); - switch (_edge->style()->arrowTail()) { - case Style::Flat: - { - painter->drawLine( - toScreen(_edge->tail() + tLeft), - toScreen(_edge->tail() + tRight)); - break; - } - case Style::Pointer: - { - QPainterPath pth; - pth.moveTo(toScreen(_edge->tail() + tt + tLeft)); - pth.lineTo(toScreen(_edge->tail())); - pth.lineTo(toScreen(_edge->tail() + tt + tRight)); - painter->drawPath(pth); - break; - } - case Style::NoTip: - break; - } + if (!_edge->path() || _edge->path()->edges().first() == _edge) { + switch (_edge->style()->arrowTail()) { + case Style::Flat: + { + painter->drawLine( + toScreen(_edge->tail() + tLeft), + toScreen(_edge->tail() + tRight)); + break; + } + case Style::Pointer: + { + QPainterPath pth; + pth.moveTo(toScreen(_edge->tail() + tt + tLeft)); + pth.lineTo(toScreen(_edge->tail())); + pth.lineTo(toScreen(_edge->tail() + tt + tRight)); + painter->drawPath(pth); + break; + } + case Style::NoTip: + break; + } + } if (isSelected()) { QColor draw; diff --git a/src/gui/mainmenu.cpp b/src/gui/mainmenu.cpp index 7b7623b..efd453d 100644 --- a/src/gui/mainmenu.cpp +++ b/src/gui/mainmenu.cpp @@ -239,6 +239,24 @@ void MainMenu::on_actionMerge_Nodes_triggered() tikzit->activeWindow()->tikzScene()->mergeNodes(); } +void MainMenu::on_actionMake_Path_triggered() +{ + if (tikzit->activeWindow() != 0) + tikzit->activeWindow()->tikzScene()->makePath(false); +} + +void MainMenu::on_actionMake_Path_as_Background_triggered() +{ + if (tikzit->activeWindow() != 0) + tikzit->activeWindow()->tikzScene()->makePath(true); +} + +void MainMenu::on_actionSplit_Path_triggered() +{ + if (tikzit->activeWindow() != 0) + tikzit->activeWindow()->tikzScene()->splitPath(); +} + // Tikz void MainMenu::on_actionParse_triggered() @@ -333,8 +351,10 @@ void MainMenu::on_actionZoom_Out_triggered() void MainMenu::on_actionShow_Node_Labels_triggered() { - tikzit->activeWindow()->tikzScene()->setDrawNodeLabels(ui.actionShow_Node_Labels->isChecked()); - tikzit->activeWindow()->tikzScene()->invalidate(); + if (tikzit->activeWindow() != 0) { + tikzit->activeWindow()->tikzScene()->setDrawNodeLabels(ui.actionShow_Node_Labels->isChecked()); + tikzit->activeWindow()->tikzScene()->invalidate(); + } } void MainMenu::on_actionAbout_triggered() diff --git a/src/gui/mainmenu.h b/src/gui/mainmenu.h index 8268802..d0d73e9 100644 --- a/src/gui/mainmenu.h +++ b/src/gui/mainmenu.h @@ -67,6 +67,9 @@ public slots: void on_actionExtendRight_triggered(); void on_actionReverse_Edge_Direction_triggered(); void on_actionMerge_Nodes_triggered(); + void on_actionMake_Path_triggered(); + void on_actionMake_Path_as_Background_triggered(); + void on_actionSplit_Path_triggered(); // Tools void on_actionParse_triggered(); diff --git a/src/gui/mainmenu.ui b/src/gui/mainmenu.ui index 46f4881..11778db 100644 --- a/src/gui/mainmenu.ui +++ b/src/gui/mainmenu.ui @@ -58,6 +58,14 @@ <addaction name="actionRotateCW"/> <addaction name="actionRotateCCW"/> </widget> + <widget class="QMenu" name="menuPath"> + <property name="title"> + <string>Path</string> + </property> + <addaction name="actionMake_Path"/> + <addaction name="actionSplit_Path"/> + <addaction name="actionMake_Path_as_Background"/> + </widget> <addaction name="actionUndo"/> <addaction name="actionRedo"/> <addaction name="separator"/> @@ -74,6 +82,8 @@ <addaction name="menuTransform"/> <addaction name="actionReverse_Edge_Direction"/> <addaction name="actionMerge_Nodes"/> + <addaction name="separator"/> + <addaction name="menuPath"/> </widget> <widget class="QMenu" name="menuTikz"> <property name="title"> @@ -460,6 +470,30 @@ <string>Ctrl+Shift+Space</string> </property> </action> + <action name="actionMake_Path"> + <property name="text"> + <string>Make Path</string> + </property> + <property name="shortcut"> + <string>Ctrl+P</string> + </property> + </action> + <action name="actionSplit_Path"> + <property name="text"> + <string>Split Path</string> + </property> + <property name="shortcut"> + <string>Ctrl+Shift+P</string> + </property> + </action> + <action name="actionMake_Path_as_Background"> + <property name="text"> + <string>Make Path as Background</string> + </property> + <property name="shortcut"> + <string>Ctrl+B</string> + </property> + </action> <addaction name="menuFile"/> <addaction name="menuEdit"/> <addaction name="menuView"/> diff --git a/src/gui/pathitem.cpp b/src/gui/pathitem.cpp new file mode 100644 index 0000000..b45c0a5 --- /dev/null +++ b/src/gui/pathitem.cpp @@ -0,0 +1,68 @@ +#include "pathitem.h" +#include "tikzit.h" + +PathItem::PathItem(Path *path) +{ + _path = path; + readPos(); +} + +void PathItem::readPos() +{ + QPainterPath painterPath; + + foreach (Edge *e, _path->edges()) { + e->updateControls(); + + if (e == _path->edges().first()) + painterPath.moveTo (toScreen(e->tail())); + + if (e->bend() != 0 || !e->basicBendMode()) { + painterPath.cubicTo(toScreen(e->cp1()), + toScreen(e->cp2()), + toScreen(e->head())); + } else { + painterPath.lineTo(toScreen(e->head())); + } + } + + setPainterPath(painterPath); +} + +void PathItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) +{ + Style *st = _path->edges().first()->style(); + QPen pen = st->pen(); + QBrush brush = st->brush(); + QColor c = brush.color(); + brush.setColor(QColor(c.red(),c.green(),c.blue(),200)); + painter->setPen(pen); + painter->setBrush(brush); + painter->drawPath(painterPath()); +} + +Path *PathItem::path() const +{ + return _path; +} + +QPainterPath PathItem::painterPath() const +{ + return _painterPath; +} + +void PathItem::setPainterPath(const QPainterPath &painterPath) +{ + prepareGeometryChange(); + + _painterPath = painterPath; + float r = GLOBAL_SCALEF * 0.1; + _boundingRect = _painterPath.boundingRect().adjusted(-r,-r,r,r); + + update(); +} + +QRectF PathItem::boundingRect() const +{ + return _boundingRect; +} diff --git a/src/gui/pathitem.h b/src/gui/pathitem.h new file mode 100644 index 0000000..7dc9c80 --- /dev/null +++ b/src/gui/pathitem.h @@ -0,0 +1,28 @@ +#ifndef PATHITEM_H +#define PATHITEM_H + +#include "path.h" + +#include <QGraphicsItem> + +class PathItem : public QGraphicsItem +{ +public: + PathItem(Path *path); + void readPos(); + void paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) override; + + Path *path() const; + + QPainterPath painterPath() const; + void setPainterPath(const QPainterPath &painterPath); + + QRectF boundingRect() const override; + +private: + Path *_path; + QPainterPath _painterPath; + QRectF _boundingRect; +}; + +#endif // PATHITEM_H diff --git a/src/gui/styleeditor.cpp b/src/gui/styleeditor.cpp index e2ade45..f8972b1 100644 --- a/src/gui/styleeditor.cpp +++ b/src/gui/styleeditor.cpp @@ -32,8 +32,8 @@ StyleEditor::StyleEditor(QWidget *parent) : { ui->setupUi(this); _formWidgets << ui->name << ui->category << - ui->fillColor << ui->hasTikzitFillColor << ui->tikzitFillColor << - ui->drawColor << ui->hasTikzitDrawColor << ui->tikzitDrawColor << + ui->fillColor << ui->noFill << ui->hasTikzitFillColor << ui->tikzitFillColor << + ui->drawColor << ui->noDraw << ui->hasTikzitDrawColor << ui->tikzitDrawColor << ui->shape << ui->hasTikzitShape << ui->tikzitShape << ui->leftArrow << ui->rightArrow << ui->properties; @@ -283,8 +283,16 @@ void StyleEditor::refreshDisplay() // draw QColor realDraw = s->strokeColor(false); QColor draw = s->strokeColor(); - ui->drawColor->setEnabled(true); - setColor(ui->drawColor, realDraw); + + ui->noDraw->setEnabled(true); + if (s->hasStroke()) { + ui->drawColor->setEnabled(true); + setColor(ui->drawColor, realDraw); + ui->noDraw->setChecked(false); + } else { + ui->noDraw->setChecked(true); + ui->drawColor->setEnabled(false); + } // tikzit draw bool drawOverride = s->data()->hasProperty("tikzit draw"); @@ -294,6 +302,27 @@ void StyleEditor::refreshDisplay() ui->tikzitDrawColor->setEnabled(drawOverride); if (drawOverride) setColor(ui->tikzitDrawColor, draw); + // fill + QColor realFill = s->fillColor(false); + QColor fill = s->fillColor(); + + ui->noFill->setEnabled(true); + if (s->hasFill()) { + ui->fillColor->setEnabled(true); + setColor(ui->fillColor, realFill); + ui->noFill->setChecked(false); + } else { + ui->noFill->setChecked(true); + ui->fillColor->setEnabled(false); + } + + // tikzit fill + bool fillOverride = s->data()->hasProperty("tikzit fill"); + ui->hasTikzitFillColor->setEnabled(true); + ui->hasTikzitFillColor->setChecked(fillOverride); + ui->tikzitFillColor->setEnabled(fillOverride); + if (fillOverride) setColor(ui->tikzitFillColor, fill); + if (!s->isEdgeStyle()) { // qDebug() << "node style update"; // category @@ -301,19 +330,6 @@ void StyleEditor::refreshDisplay() ui->category->setCurrentText( s->propertyWithDefault("tikzit category", "", false)); - // fill - QColor realFill = s->fillColor(false); - QColor fill = s->fillColor(); - ui->fillColor->setEnabled(true); - setColor(ui->fillColor, realFill); - - // tikzit fill - bool fillOverride = s->data()->hasProperty("tikzit fill"); - ui->hasTikzitFillColor->setEnabled(true); - ui->hasTikzitFillColor->setChecked(fillOverride); - ui->tikzitFillColor->setEnabled(fillOverride); - if (fillOverride) setColor(ui->tikzitFillColor, fill); - // shape QString realShape = s->propertyWithDefault("shape", "", false); QString shape = s->propertyWithDefault("tikzit shape", "", false); @@ -327,13 +343,6 @@ void StyleEditor::refreshDisplay() ui->tikzitShape->setEnabled(shapeOverride); if (shapeOverride) ui->tikzitShape->setCurrentText(shape); } else { -// qDebug() << "edge style update"; - - // set fill to gray (disabled) - ui->fillColor->setEnabled(false); - ui->tikzitFillColor->setEnabled(false); - ui->hasTikzitFillColor->setEnabled(false); - ui->shape->setEnabled(false); ui->tikzitShape->setEnabled(false); ui->hasTikzitShape->setEnabled(false); @@ -425,6 +434,30 @@ void StyleEditor::on_hasTikzitDrawColor_stateChanged(int state) } } +void StyleEditor::on_noFill_stateChanged(int state) +{ + Style *s = activeStyle(); + if (s != nullptr) { + if (state == Qt::Checked) s->data()->setProperty("fill", "none"); + else s->data()->setProperty("fill", "white"); + refreshActiveStyle(); + refreshDisplay(); + setDirty(true); + } +} + +void StyleEditor::on_noDraw_stateChanged(int state) +{ + Style *s = activeStyle(); + if (s != nullptr) { + if (state == Qt::Checked) s->data()->setProperty("draw", "none"); + else s->data()->setProperty("draw", "black"); + refreshActiveStyle(); + refreshDisplay(); + setDirty(true); + } +} + void StyleEditor::on_hasTikzitShape_stateChanged(int state) { Style *s = activeStyle(); diff --git a/src/gui/styleeditor.h b/src/gui/styleeditor.h index 2c35d56..7fc62c0 100644 --- a/src/gui/styleeditor.h +++ b/src/gui/styleeditor.h @@ -68,6 +68,8 @@ public slots: void on_tikzitDrawColor_clicked(); void on_hasTikzitFillColor_stateChanged(int state); void on_hasTikzitDrawColor_stateChanged(int state); + void on_noFill_stateChanged(int state); + void on_noDraw_stateChanged(int state); void on_hasTikzitShape_stateChanged(int state); void on_tikzitShape_currentIndexChanged(int); diff --git a/src/gui/styleeditor.ui b/src/gui/styleeditor.ui index 9c06894..22b04a6 100644 --- a/src/gui/styleeditor.ui +++ b/src/gui/styleeditor.ui @@ -822,6 +822,44 @@ <bool>true</bool> </attribute> </widget> + <widget class="QCheckBox" name="noFill"> + <property name="geometry"> + <rect> + <x>340</x> + <y>110</y> + <width>71</width> + <height>17</height> + </rect> + </property> + <property name="font"> + <font> + <pointsize>9</pointsize> + <italic>false</italic> + </font> + </property> + <property name="text"> + <string>none</string> + </property> + </widget> + <widget class="QCheckBox" name="noDraw"> + <property name="geometry"> + <rect> + <x>340</x> + <y>140</y> + <width>71</width> + <height>17</height> + </rect> + </property> + <property name="font"> + <font> + <pointsize>9</pointsize> + <italic>false</italic> + </font> + </property> + <property name="text"> + <string>none</string> + </property> + </widget> </widget> </widget> <resources/> diff --git a/src/gui/tikzscene.cpp b/src/gui/tikzscene.cpp index 087f621..33e4710 100644 --- a/src/gui/tikzscene.cpp +++ b/src/gui/tikzscene.cpp @@ -27,6 +27,7 @@ #include <QDebug> #include <QClipboard> #include <QInputDialog> +#include <QMessageBox> #include <cmath> #include <delimitedstringvalidator.h> #include <QSettings> @@ -96,12 +97,25 @@ void TikzScene::graphReplaced() } _edgeItems.clear(); + foreach (PathItem *pi, _pathItems) { + removeItem(pi); + delete pi; + } + _pathItems.clear(); + foreach (Edge *e, graph()->edges()) { //e->attachStyle(); //e->updateControls(); EdgeItem *ei = new EdgeItem(e); _edgeItems.insert(e, ei); addItem(ei); + + Path *p = e->path(); + if (p && p->edges().first() == e) { + PathItem *pi = new PathItem(p); + _pathItems.insert(p, pi); + addItem(pi); + } } foreach (Node *n, graph()->nodes()) { @@ -236,13 +250,18 @@ void TikzScene::mergeNodes() Node *n = _tikzDocument->graph()->nodes()[i]; if (m1.contains(n)) delNodes.insert(i, n); } + + QSet<Path*> delPaths; for (int i = 0; i < _tikzDocument->graph()->edges().length(); ++i) { Edge *e = _tikzDocument->graph()->edges()[i]; - if (m1.contains(e->source()) || m1.contains(e->target())) delEdges.insert(i, e); + if (m1.contains(e->source()) || m1.contains(e->target())) { + delEdges.insert(i, e); + if (e->path()) delPaths << e->path(); + } } - DeleteCommand *cmd = new DeleteCommand(this, delNodes, delEdges, - selNodes, selEdges); - _tikzDocument->undoStack()->push(cmd); + _tikzDocument->undoStack()->push(new SplitPathCommand(this, delPaths)); + _tikzDocument->undoStack()->push(new DeleteCommand(this, delNodes, delEdges, + selNodes, selEdges)); _tikzDocument->undoStack()->endMacro(); } @@ -295,11 +314,161 @@ void TikzScene::reverseSelectedEdges() _tikzDocument->undoStack()->push(cmd); } +void TikzScene::makePath(bool duplicateEdges) +{ + QSet<Node*> selNodes; + QSet<Edge*> selEdges; + QSet<Edge*> edges; + getSelection(selNodes, selEdges); + + edges = selEdges; + + // if no edges are selected, try to infer edges from nodes + if (edges.isEmpty()) { + foreach(Edge *e, graph()->edges()) { + if (selNodes.contains(e->source()) && selNodes.contains(e->target())) + edges << e; + } + } + + if (edges.size() < 2) { + //QMessageBox::warning(nullptr, "Error", "Paths must contain at least 2 edges."); + return; + } + + foreach (Edge *e, edges) { + if (e->path() != nullptr && !duplicateEdges) { + //QMessageBox::warning(nullptr, "Error", "Edges must not already be in another path."); + // TODO: maybe we want to automatically split paths if edges are in a path already? + return; + } + } + + _tikzDocument->undoStack()->beginMacro("Make Path"); + + QVector<Edge *> oldEdgeOrder = graph()->edges(); + QSet<Edge *> oldEdges, newEdges; + oldEdges = edges; + + if (duplicateEdges) { + foreach (Edge *e, edges) { + Edge *e1 = e->copy(); + _tikzDocument->undoStack()->push(new AddEdgeCommand(this, e1, false, selNodes, selEdges)); + newEdges << e1; + oldEdgeOrder << e1; + } + edges = newEdges; + } + + // try to turn selected edges into one contiguous chain or cycle, recording + // which edges need to be flipped. + + // n.b. this is O(n^2) in path length. This could be optimised by saving + // vertex neighbourhoods, but probably doesn't win anything for n < 100. + + QSet<Edge*> flip; + QVector<Edge*> p; + int pLen = -1; + + // keep going as long as 'p' grows + while (pLen < p.length()) { + pLen = p.length(); + Edge *e = nullptr; + foreach (e, edges) { + Node *s = e->source(); + Node *t = e->target(); + if (p.isEmpty()) { + p.append(e); + break; + } + + Node *head = (flip.contains(p.first())) ? p.first()->target() : p.first()->source(); + Node *tail = (flip.contains(p.last())) ? p.last()->source() : p.last()->target(); + + if (s == head || t == head) { + if (s == head) flip << e; + p.prepend(e); + break; + } + + if (s == tail || t == tail) { + if (t == tail) flip << e; + p.append(e); + break; + } + } + + if (e) edges.remove(e); + } + + if (!edges.isEmpty()) { + QMessageBox::warning(nullptr, "Error", "Selected edges do not form a path."); + return; + } + + _tikzDocument->undoStack()->push(new ReverseEdgesCommand(this, flip)); + + // order all of the edges together, and in the case of + // duplicate edges, just below the first original. + QVector<Edge*> newEdgeOrder; + bool firstEdge = true; + foreach (Edge *e, oldEdgeOrder) { + if (oldEdges.contains(e)) { + if (firstEdge) { + newEdgeOrder += p; + firstEdge = false; + } + + if (duplicateEdges) newEdgeOrder << e; + } else if (!newEdges.contains(e)) { + newEdgeOrder << e; + } + } + + _tikzDocument->undoStack()->push(new ReorderCommand(this, + graph()->nodes(), graph()->nodes(), oldEdgeOrder, newEdgeOrder)); + + QMap<Edge*, GraphElementData*> oldEdgeData; + foreach (Edge *e, p) { + if (e != p.first()) oldEdgeData[e] = e->data()->copy(); + } + + _tikzDocument->undoStack()->push(new MakePathCommand(this, p, oldEdgeData)); + _tikzDocument->undoStack()->endMacro(); +} + +void TikzScene::splitPath() +{ + QSet<Node*> selNodes; + QSet<Edge*> edges; + getSelection(selNodes, edges); + + // if no edges are selected, try to infer edges from nodes + if (edges.isEmpty()) { + foreach(Edge *e, graph()->edges()) { + if (selNodes.contains(e->source()) && selNodes.contains(e->target())) + edges << e; + } + } + + QSet<Path*> paths; + foreach (Edge *e, edges) { + if (e->path()) paths << e->path(); + } + + _tikzDocument->undoStack()->push(new SplitPathCommand(this, paths)); +} + void TikzScene::refreshZIndices() { qreal z = 0.0; foreach (Edge *e, graph()->edges()) { - edgeItems()[e]->setZValue(z); + if (e->path() && e == e->path()->edges().first()) { + pathItems()[e->path()]->setZValue(z); + edgeItems()[e]->setZValue(z + 0.1); + } else { + edgeItems()[e]->setZValue(z); + } z += 1.0; } @@ -318,6 +487,7 @@ void TikzScene::mousePressEvent(QGraphicsSceneMouseEvent *event) _mouseDownPos = event->scenePos(); _draggingNodes = false; + _selectingEdge = nullptr; // radius of a control point for bezier edges, in scene coordinates qreal cpR = GLOBAL_SCALEF * (0.1); @@ -398,9 +568,19 @@ void TikzScene::mousePressEvent(QGraphicsSceneMouseEvent *event) } } - auto its = items(_mouseDownPos); - if (!its.isEmpty() && dynamic_cast<NodeItem*>(its[0])) - _draggingNodes = true; + QList<QGraphicsItem*> its = items(_mouseDownPos); + if (!its.isEmpty()) { + if (dynamic_cast<NodeItem*>(its[0])) { + _draggingNodes = true; + } else { + foreach (QGraphicsItem *gi, its) { + if (EdgeItem *ei = dynamic_cast<EdgeItem*>(gi)) { + _selectingEdge = ei->edge(); + break; + } + } + } + } } break; @@ -512,6 +692,8 @@ void TikzScene::mouseMoveEvent(QGraphicsSceneMouseEvent *event) } _modifyEdgeItem->readPos(); + Path *p = _modifyEdgeItem->edge()->path(); + if (p) pathItems()[p]->readPos(); } else if (_draggingNodes) { // nodes being dragged QGraphicsScene::mouseMoveEvent(event); @@ -598,6 +780,23 @@ void TikzScene::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) // otherwise, process mouse move normally QGraphicsScene::mouseReleaseEvent(event); + if (_selectingEdge) { + bool sel = edgeItems()[_selectingEdge]->isSelected(); + Path *p = _selectingEdge->path(); + if (p) { + foreach (Edge *e, p->edges()) { + if (e != _selectingEdge) + edgeItems()[e]->setSelected(sel); + nodeItems()[e->source()]->setSelected(sel); + nodeItems()[e->target()]->setSelected(sel); + } + } +// else { +// nodeItems()[_selectingEdge->source()]->setSelected(sel); +// nodeItems()[_selectingEdge->target()]->setSelected(sel); +// } + } + if (_rubberBandItem->isVisible()) { QPainterPath sel; sel.addRect(_rubberBandItem->rect()); @@ -963,6 +1162,7 @@ void TikzScene::deleteSelectedItems() QMap<int,Node*> deleteNodes; QMap<int,Edge*> deleteEdges; + QSet<Path*> deletePaths; for (int i = 0; i < _tikzDocument->graph()->nodes().length(); ++i) { Node *n = _tikzDocument->graph()->nodes()[i]; @@ -973,14 +1173,20 @@ void TikzScene::deleteSelectedItems() Edge *e = _tikzDocument->graph()->edges()[i]; if (selEdges.contains(e) || selNodes.contains(e->source()) || - selNodes.contains(e->target())) deleteEdges.insert(i, e); + selNodes.contains(e->target())) + { + if (e->path()) deletePaths << e->path(); + deleteEdges.insert(i, e); + } } //qDebug() << "nodes:" << deleteNodes; //qDebug() << "edges:" << deleteEdges; - DeleteCommand *cmd = new DeleteCommand(this, deleteNodes, deleteEdges, - selNodes, selEdges); - _tikzDocument->undoStack()->push(cmd); + _tikzDocument->undoStack()->beginMacro("Delete"); + _tikzDocument->undoStack()->push(new SplitPathCommand(this, deletePaths)); + _tikzDocument->undoStack()->push(new DeleteCommand(this, deleteNodes, deleteEdges, + selNodes, selEdges)); + _tikzDocument->undoStack()->endMacro(); } void TikzScene::copyToClipboard() @@ -1159,6 +1365,8 @@ void TikzScene::refreshSceneBounds() { void TikzScene::refreshAdjacentEdges(QList<Node*> nodes) { if (nodes.empty()) return; + + QSet<Path*> paths; foreach (Edge *e, _edgeItems.keys()) { EdgeItem *ei = _edgeItems[e]; @@ -1169,6 +1377,13 @@ void TikzScene::refreshAdjacentEdges(QList<Node*> nodes) ei->readPos(); } } + + // only update paths once + Path *p = ei->edge()->path(); + if (p && !paths.contains(p)) { + pathItems()[p]->readPos(); + paths << p; + } } } @@ -1195,3 +1410,8 @@ QMap<Edge*,EdgeItem*> &TikzScene::edgeItems() { return _edgeItems; } + +QMap<Path *, PathItem *> &TikzScene::pathItems() +{ + return _pathItems; +} diff --git a/src/gui/tikzscene.h b/src/gui/tikzscene.h index 2e7baa5..e1d30d2 100644 --- a/src/gui/tikzscene.h +++ b/src/gui/tikzscene.h @@ -27,6 +27,7 @@ #include "graph.h" #include "nodeitem.h" #include "edgeitem.h" +#include "pathitem.h" #include "tikzdocument.h" #include "toolpalette.h" #include "stylepalette.h" @@ -48,6 +49,7 @@ public: Graph *graph(); QMap<Node*,NodeItem*> &nodeItems(); QMap<Edge*,EdgeItem*> &edgeItems(); + QMap<Path*,PathItem*> &pathItems(); void refreshAdjacentEdges(QList<Node*> nodes); // void setBounds(QRectF bounds); @@ -81,6 +83,8 @@ public: void reverseSelectedEdges(); + void makePath(bool duplicateEdges); + void splitPath(); void getSelection(QSet<Node*> &selNodes, QSet<Edge*> &selEdges) const; QSet<Node*> getSelectedNodes() const; @@ -110,9 +114,11 @@ private: StylePalette *_styles; QMap<Node*,NodeItem*> _nodeItems; QMap<Edge*,EdgeItem*> _edgeItems; + QMap<Path*,PathItem*> _pathItems; QGraphicsLineItem *_drawEdgeItem; QGraphicsRectItem *_rubberBandItem; EdgeItem *_modifyEdgeItem; + Edge *_selectingEdge; NodeItem *_edgeStartNodeItem; NodeItem *_edgeEndNodeItem; bool _firstControlPoint; diff --git a/src/gui/tikzview.cpp b/src/gui/tikzview.cpp index 3615685..eb87002 100644 --- a/src/gui/tikzview.cpp +++ b/src/gui/tikzview.cpp @@ -149,6 +149,8 @@ void TikzView::wheelEvent(QWheelEvent *event) } else if (event->angleDelta().y() < 0) { zoomOut(); } + } else if (event->modifiers() & Qt::ShiftModifier) { + horizontalScrollBar()->setValue(horizontalScrollBar()->value() - event->angleDelta().y()); } } diff --git a/src/gui/undocommands.cpp b/src/gui/undocommands.cpp index c5c26af..35345de 100644 --- a/src/gui/undocommands.cpp +++ b/src/gui/undocommands.cpp @@ -102,6 +102,9 @@ void EdgeBendCommand::undo() foreach(EdgeItem *ei, _scene->edgeItems()) { if (ei->edge() == _edge) { ei->readPos(); + + Path *p = ei->edge()->path(); + if (p) _scene->pathItems()[p]->readPos(); break; } } @@ -118,6 +121,9 @@ void EdgeBendCommand::redo() foreach(EdgeItem *ei, _scene->edgeItems()) { if (ei->edge() == _edge) { ei->readPos(); + + Path *p = ei->edge()->path(); + if (p) _scene->pathItems()[p]->readPos(); break; } } @@ -287,6 +293,9 @@ void ChangeEdgeModeCommand::undo() // FIXME: this act strangely sometimes _edge->setBasicBendMode(!_edge->basicBendMode()); _scene->edgeItems()[_edge]->readPos(); + Path *p = _edge->path(); + if (p) _scene->pathItems()[p]->readPos(); + GraphUpdateCommand::undo(); } @@ -294,6 +303,9 @@ void ChangeEdgeModeCommand::redo() { _edge->setBasicBendMode(!_edge->basicBendMode()); _scene->edgeItems()[_edge]->readPos(); + Path *p = _edge->path(); + if (p) _scene->pathItems()[p]->readPos(); + GraphUpdateCommand::redo(); } @@ -369,6 +381,16 @@ void PasteCommand::undo() { _scene->clearSelection(); + foreach (Path *p, _graph->paths()) { + PathItem *pi = _scene->pathItems()[p]; + _scene->pathItems().remove(p); + _scene->removeItem(pi); + delete pi; + + p->removeEdges(); + _scene->graph()->removePath(p); + } + foreach (Edge *e, _graph->edges()) { EdgeItem *ei = _scene->edgeItems()[e]; _scene->edgeItems().remove(e); @@ -399,6 +421,12 @@ void PasteCommand::redo() _scene->clearSelection(); _scene->graph()->insertGraph(_graph); + foreach (Path *p, _graph->paths()) { + PathItem *pi = new PathItem(p); + _scene->pathItems().insert(p, pi); + _scene->addItem(pi); + } + foreach (Edge *e, _graph->edges()) { e->attachStyle(); // in case styles have changed EdgeItem *ei = new EdgeItem(e); @@ -488,7 +516,7 @@ void ReflectNodesCommand::undo() } } - _scene->refreshAdjacentEdges(_nodes.toList()); + _scene->refreshAdjacentEdges(_nodes.values()); GraphUpdateCommand::undo(); } @@ -501,7 +529,7 @@ void ReflectNodesCommand::redo() } } - _scene->refreshAdjacentEdges(_nodes.toList()); + _scene->refreshAdjacentEdges(_nodes.values()); GraphUpdateCommand::redo(); } @@ -520,7 +548,7 @@ void RotateNodesCommand::undo() } } - _scene->refreshAdjacentEdges(_nodes.toList()); + _scene->refreshAdjacentEdges(_nodes.values()); GraphUpdateCommand::undo(); } @@ -533,7 +561,7 @@ void RotateNodesCommand::redo() } } - _scene->refreshAdjacentEdges(_nodes.toList()); + _scene->refreshAdjacentEdges(_nodes.values()); GraphUpdateCommand::redo(); } @@ -594,3 +622,107 @@ void ReverseEdgesCommand::redo() GraphUpdateCommand::redo(); } + +MakePathCommand::MakePathCommand(TikzScene *scene, + const QVector<Edge *> &edgeList, + const QMap<Edge *, GraphElementData *> &oldEdgeData, + QUndoCommand *parent) : + GraphUpdateCommand(scene, parent), + _edgeList(edgeList), _oldEdgeData(oldEdgeData) +{ +} + +void MakePathCommand::undo() +{ + Path *p = _edgeList.first()->path(); + + PathItem *pi = _scene->pathItems()[p]; + _scene->removeItem(pi); + _scene->pathItems().remove(p); + delete pi; + + p->removeEdges(); + _scene->graph()->removePath(p); + + foreach (Edge *e, _edgeList) { + if (e != _edgeList.first()) { + // setData transfers ownership, so make a copy + e->setData(_oldEdgeData[e]->copy()); + } + } + + _scene->refreshZIndices(); + GraphUpdateCommand::undo(); +} + +void MakePathCommand::redo() +{ + GraphElementData *npd = _edgeList.first()->data()->nonPathData(); + GraphElementData *d; + + Path *p = new Path(); + foreach (Edge *e, _edgeList) { + p->addEdge(e); + + if (e != _edgeList.first()) { + d = e->data()->pathData(); + d->mergeData(npd); + e->setData(d); + } + } + + delete npd; + + _scene->graph()->addPath(p); + + PathItem *pi = new PathItem(p); + _scene->addItem(pi); + _scene->pathItems().insert(p, pi); + pi->readPos(); + + _scene->refreshZIndices(); + GraphUpdateCommand::redo(); +} + +SplitPathCommand::SplitPathCommand(TikzScene *scene, + const QSet<Path *> &paths, + QUndoCommand *parent) : + GraphUpdateCommand(scene, parent), _paths(paths) +{ + foreach (Path *p, paths) _edgeLists[p] = p->edges(); +} + +void SplitPathCommand::undo() +{ + foreach (Path *p, _paths) { + foreach (Edge *e, _edgeLists[p]) { + p->addEdge(e); + } + + _scene->graph()->addPath(p); + + PathItem *pi = new PathItem(p); + _scene->addItem(pi); + _scene->pathItems().insert(p, pi); + pi->readPos(); + } + + _scene->refreshZIndices(); + GraphUpdateCommand::undo(); +} + +void SplitPathCommand::redo() +{ + foreach (Path *p, _paths) { + PathItem *pi = _scene->pathItems()[p]; + _scene->removeItem(pi); + _scene->pathItems().remove(p); + delete pi; + + p->removeEdges(); + _scene->graph()->removePath(p); + } + + _scene->refreshZIndices(); + GraphUpdateCommand::redo(); +} diff --git a/src/gui/undocommands.h b/src/gui/undocommands.h index 40f0a3b..c10eecc 100644 --- a/src/gui/undocommands.h +++ b/src/gui/undocommands.h @@ -255,4 +255,35 @@ private: QVector<Edge*> _newEdgeOrder; }; +class MakePathCommand : public GraphUpdateCommand +{ +public: + explicit MakePathCommand(TikzScene *scene, + const QVector<Edge*> &edgeList, + const QMap<Edge*,GraphElementData*> &oldEdgeData, + QUndoCommand *parent = nullptr); + void undo() override; + void redo() override; +private: + QVector<Edge*> _edgeList; + + // creating path clobbers data on all but first edge + QMap<Edge*,GraphElementData*> _oldEdgeData; +}; + +class SplitPathCommand : public GraphUpdateCommand +{ +public: + explicit SplitPathCommand(TikzScene *scene, + const QSet<Path*> &paths, + QUndoCommand *parent = nullptr); + void undo() override; + void redo() override; +private: + QSet<Path*> _paths; + + // keep a copy of the edge lists so they can be added back to each path in undo() + QMap<Path*,QVector<Edge*>> _edgeLists; +}; + #endif // UNDOCOMMANDS_H |