1 /*
2 TikZiT - a GUI diagram editor for TikZ
3 Copyright (C) 2018 Aleks Kissinger
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <https://www.gnu.org/licenses/>.
17 */
18
19 #include "tikzit.h"
20 #include "util.h"
21 #include "tikzscene.h"
22 #include "undocommands.h"
23 #include "tikzassembler.h"
24
25 #include <QPen>
26 #include <QBrush>
27 #include <QDebug>
28 #include <QClipboard>
29 #include <QInputDialog>
30 #include <QMessageBox>
31 #include <cmath>
32 #include <delimitedstringvalidator.h>
33 #include <QSettings>
34
35
TikzScene(TikzDocument * tikzDocument,ToolPalette * tools,StylePalette * styles,QObject * parent)36 TikzScene::TikzScene(TikzDocument *tikzDocument, ToolPalette *tools,
37 StylePalette *styles, QObject *parent) :
38 QGraphicsScene(parent), _tikzDocument(tikzDocument), _tools(tools), _styles(styles)
39 {
40 _modifyEdgeItem = nullptr;
41 _edgeStartNodeItem = nullptr;
42 _drawNodeLabels = true;
43 _drawEdgeItem = new QGraphicsLineItem();
44 _rubberBandItem = new QGraphicsRectItem();
45 _enabled = true;
46 //setSceneRect(-310,-230,620,450);
47 //setSceneRect(-2000,-1500,4000,3000);
48 refreshSceneBounds();
49
50 QPen pen;
51 pen.setColor(QColor::fromRgbF(0.5, 0.0, 0.5));
52 //pen.setWidth(3.0f);
53 pen.setCosmetic(true);
54 _drawEdgeItem->setPen(pen);
55 _drawEdgeItem->setLine(0,0,0,0);
56 _drawEdgeItem->setVisible(false);
57 addItem(_drawEdgeItem);
58
59 pen.setColor(QColor::fromRgbF(0.6, 0.6, 0.8));
60 //pen.setWidth(3.0f);
61 //QVector<qreal> dash;
62 //dash << 4.0 << 4.0;
63 pen.setStyle(Qt::DashLine);
64 //pen.setDashPattern(dash);
65 _rubberBandItem->setPen(pen);
66
67 QBrush brush(QColor::fromRgbF(0.6,0.6,0.8,0.2));
68 _rubberBandItem->setBrush(brush);
69
70 _rubberBandItem->setVisible(false);
71 addItem(_rubberBandItem);
72
73 _highlightHeads = false;
74 _highlightTails = false;
75 }
76
~TikzScene()77 TikzScene::~TikzScene() {
78 }
79
graph()80 Graph *TikzScene::graph()
81 {
82 return _tikzDocument->graph();
83 }
84
graphReplaced()85 void TikzScene::graphReplaced()
86 {
87
88 foreach (NodeItem *ni, _nodeItems) {
89 removeItem(ni);
90 delete ni;
91 }
92 _nodeItems.clear();
93
94 foreach (EdgeItem *ei, _edgeItems) {
95 removeItem(ei);
96 delete ei;
97 }
98 _edgeItems.clear();
99
100 foreach (PathItem *pi, _pathItems) {
101 removeItem(pi);
102 delete pi;
103 }
104 _pathItems.clear();
105
106 foreach (Edge *e, graph()->edges()) {
107 //e->attachStyle();
108 //e->updateControls();
109 EdgeItem *ei = new EdgeItem(e);
110 _edgeItems.insert(e, ei);
111 addItem(ei);
112
113 Path *p = e->path();
114 if (p && p->edges().first() == e) {
115 PathItem *pi = new PathItem(p);
116 _pathItems.insert(p, pi);
117 addItem(pi);
118 }
119 }
120
121 foreach (Node *n, graph()->nodes()) {
122 //n->attachStyle();
123 NodeItem *ni = new NodeItem(n);
124 _nodeItems.insert(n, ni);
125 addItem(ni);
126 }
127
128 refreshZIndices();
129 refreshSceneBounds();
130 }
131
extendSelectionUp()132 void TikzScene::extendSelectionUp()
133 {
134 bool found = false;
135 qreal m = 0.0;
136 foreach (Node *n, getSelectedNodes()) {
137 if (!found) {
138 m = n->point().y();
139 found = true;
140 } else {
141 if (n->point().y() > m) m = n->point().y();
142 }
143 }
144
145 foreach (NodeItem *ni, nodeItems().values()) {
146 if (ni->node()->point().y() >= m) ni->setSelected(true);
147 }
148 }
149
extendSelectionDown()150 void TikzScene::extendSelectionDown()
151 {
152 bool found = false;
153 qreal m = 0.0;
154 foreach (Node *n, getSelectedNodes()) {
155 if (!found) {
156 m = n->point().y();
157 found = true;
158 } else {
159 if (n->point().y() < m) m = n->point().y();
160 }
161 }
162
163 foreach (NodeItem *ni, nodeItems().values()) {
164 if (ni->node()->point().y() <= m) ni->setSelected(true);
165 }
166 }
167
extendSelectionLeft()168 void TikzScene::extendSelectionLeft()
169 {
170 bool found = false;
171 qreal m = 0.0;
172 foreach (Node *n, getSelectedNodes()) {
173 if (!found) {
174 m = n->point().x();
175 found = true;
176 } else {
177 if (n->point().x() < m) m = n->point().x();
178 }
179 }
180
181 foreach (NodeItem *ni, nodeItems().values()) {
182 if (ni->node()->point().x() <= m) ni->setSelected(true);
183 }
184 }
185
extendSelectionRight()186 void TikzScene::extendSelectionRight()
187 {
188 bool found = false;
189 qreal m = 0.0;
190 foreach (Node *n, getSelectedNodes()) {
191 if (!found) {
192 m = n->point().x();
193 found = true;
194 } else {
195 if (n->point().x() < m) m = n->point().x();
196 }
197 }
198
199 foreach (NodeItem *ni, nodeItems().values()) {
200 if (ni->node()->point().x() >= m) ni->setSelected(true);
201 }
202 }
203
mergeNodes()204 void TikzScene::mergeNodes()
205 {
206 refreshZIndices();
207 QSet<Node*> selNodes;
208 QSet<Edge*> selEdges;
209 getSelection(selNodes, selEdges);
210
211 // build a map from locations to a chosen node at that location
212 QMap<QPair<int,int>,Node*> m;
213 foreach (Node *n, selNodes) {
214 // used fixed precision for hashing/comparing locations
215 QPair<int,int> fpPoint(
216 static_cast<int>(n->point().x() * 1000.0),
217 static_cast<int>(n->point().y() * 1000.0));
218 if (!m.contains(fpPoint) ||
219 _nodeItems[m[fpPoint]]->zValue() < _nodeItems[n]->zValue())
220 {
221 m.insert(fpPoint, n);
222 }
223 }
224
225 // build a second map from nodes to the node they will be merged with
226 QMap<Node*,Node*> m1;
227 foreach (Node *n, graph()->nodes()) {
228 QPair<int,int> fpPoint(
229 static_cast<int>(n->point().x() * 1000.0),
230 static_cast<int>(n->point().y() * 1000.0));
231 Node *n1 = m[fpPoint];
232 if (n1 != nullptr && n1 != n) m1.insert(n, n1);
233 }
234
235 _tikzDocument->undoStack()->beginMacro("Merge nodes");
236
237 // copy adjacent edges from nodes that will be deleted
238 foreach (Edge *e, graph()->edges()) {
239 if (m1.contains(e->source()) || m1.contains(e->target())) {
240 Edge *e1 = e->copy(&m1);
241 AddEdgeCommand *cmd = new AddEdgeCommand(this, e1);
242 _tikzDocument->undoStack()->push(cmd);
243 }
244 }
245
246 // delete nodes
247 QMap<int,Node*> delNodes;
248 QMap<int,Edge*> delEdges;
249 for (int i = 0; i < _tikzDocument->graph()->nodes().length(); ++i) {
250 Node *n = _tikzDocument->graph()->nodes()[i];
251 if (m1.contains(n)) delNodes.insert(i, n);
252 }
253
254 QSet<Path*> delPaths;
255 for (int i = 0; i < _tikzDocument->graph()->edges().length(); ++i) {
256 Edge *e = _tikzDocument->graph()->edges()[i];
257 if (m1.contains(e->source()) || m1.contains(e->target())) {
258 delEdges.insert(i, e);
259 if (e->path()) delPaths << e->path();
260 }
261 }
262 _tikzDocument->undoStack()->push(new SplitPathCommand(this, delPaths));
263 _tikzDocument->undoStack()->push(new DeleteCommand(this, delNodes, delEdges,
264 selNodes, selEdges));
265
266 _tikzDocument->undoStack()->endMacro();
267 }
268
reorderSelection(bool toFront)269 void TikzScene::reorderSelection(bool toFront)
270 {
271 QVector<Node*> nodeOrd, nodeOrd1;
272 QVector<Edge*> edgeOrd, edgeOrd1;
273 QSet<Node*> selNodes;
274 QSet<Edge*> selEdges;
275 getSelection(selNodes, selEdges);
276 foreach (Node *n, graph()->nodes()) {
277 if (selNodes.contains(n)) nodeOrd1 << n;
278 else nodeOrd << n;
279 }
280
281 foreach (Edge *e, graph()->edges()) {
282 if (selEdges.contains(e)) edgeOrd1 << e;
283 else edgeOrd << e;
284 }
285
286 if (toFront) {
287 nodeOrd += nodeOrd1;
288 edgeOrd += edgeOrd1;
289 } else {
290 nodeOrd = nodeOrd1 + nodeOrd;
291 edgeOrd = edgeOrd1 + edgeOrd;
292 }
293
294 ReorderCommand *cmd = new ReorderCommand(this, graph()->nodes(), nodeOrd, graph()->edges(), edgeOrd);
295 _tikzDocument->undoStack()->push(cmd);
296 }
297
reverseSelectedEdges()298 void TikzScene::reverseSelectedEdges()
299 {
300 // grab all the edges which are either selected themselves, or where
301 // both their source and target nodes are selected
302 QSet<Edge*> es;
303 foreach (Edge *e, graph()->edges()) {
304 if ((_edgeItems[e] && _edgeItems[e]->isSelected()) ||
305 (_nodeItems[e->source()] && _nodeItems[e->target()] &&
306 _nodeItems[e->source()]->isSelected() &&
307 _nodeItems[e->target()]->isSelected()))
308 {
309 es << e;
310 }
311 }
312
313 ReverseEdgesCommand *cmd = new ReverseEdgesCommand(this, es);
314 _tikzDocument->undoStack()->push(cmd);
315 }
316
makePath(bool duplicateEdges)317 void TikzScene::makePath(bool duplicateEdges)
318 {
319 QSet<Node*> selNodes;
320 QSet<Edge*> selEdges;
321 QSet<Edge*> edges;
322 getSelection(selNodes, selEdges);
323
324 edges = selEdges;
325
326 // if no edges are selected, try to infer edges from nodes
327 if (edges.isEmpty()) {
328 foreach(Edge *e, graph()->edges()) {
329 if (selNodes.contains(e->source()) && selNodes.contains(e->target()))
330 edges << e;
331 }
332 }
333
334 if (edges.size() < 2) {
335 //QMessageBox::warning(nullptr, "Error", "Paths must contain at least 2 edges.");
336 return;
337 }
338
339 foreach (Edge *e, edges) {
340 if (e->path() != nullptr && !duplicateEdges) {
341 //QMessageBox::warning(nullptr, "Error", "Edges must not already be in another path.");
342 // TODO: maybe we want to automatically split paths if edges are in a path already?
343 return;
344 }
345 }
346
347 _tikzDocument->undoStack()->beginMacro("Make Path");
348
349 QVector<Edge *> oldEdgeOrder = graph()->edges();
350 QSet<Edge *> oldEdges, newEdges;
351 oldEdges = edges;
352
353 if (duplicateEdges) {
354 foreach (Edge *e, edges) {
355 Edge *e1 = e->copy();
356 _tikzDocument->undoStack()->push(new AddEdgeCommand(this, e1, false, selNodes, selEdges));
357 newEdges << e1;
358 oldEdgeOrder << e1;
359 }
360 edges = newEdges;
361 }
362
363 // try to turn selected edges into one contiguous chain or cycle, recording
364 // which edges need to be flipped.
365
366 // n.b. this is O(n^2) in path length. This could be optimised by saving
367 // vertex neighbourhoods, but probably doesn't win anything for n < 100.
368
369 QSet<Edge*> flip;
370 QVector<Edge*> p;
371 int pLen = -1;
372
373 // keep going as long as 'p' grows
374 while (pLen < p.length()) {
375 pLen = p.length();
376 Edge *e = nullptr;
377 foreach (e, edges) {
378 Node *s = e->source();
379 Node *t = e->target();
380 if (p.isEmpty()) {
381 p.append(e);
382 break;
383 }
384
385 Node *head = (flip.contains(p.first())) ? p.first()->target() : p.first()->source();
386 Node *tail = (flip.contains(p.last())) ? p.last()->source() : p.last()->target();
387
388 if (s == head || t == head) {
389 if (s == head) flip << e;
390 p.prepend(e);
391 break;
392 }
393
394 if (s == tail || t == tail) {
395 if (t == tail) flip << e;
396 p.append(e);
397 break;
398 }
399 }
400
401 if (e) edges.remove(e);
402 }
403
404 if (!edges.isEmpty()) {
405 QMessageBox::warning(nullptr, "Error", "Selected edges do not form a path.");
406 return;
407 }
408
409 _tikzDocument->undoStack()->push(new ReverseEdgesCommand(this, flip));
410
411 // order all of the edges together, and in the case of
412 // duplicate edges, just below the first original.
413 QVector<Edge*> newEdgeOrder;
414 bool firstEdge = true;
415 foreach (Edge *e, oldEdgeOrder) {
416 if (oldEdges.contains(e)) {
417 if (firstEdge) {
418 newEdgeOrder += p;
419 firstEdge = false;
420 }
421
422 if (duplicateEdges) newEdgeOrder << e;
423 } else if (!newEdges.contains(e)) {
424 newEdgeOrder << e;
425 }
426 }
427
428 _tikzDocument->undoStack()->push(new ReorderCommand(this,
429 graph()->nodes(), graph()->nodes(), oldEdgeOrder, newEdgeOrder));
430
431 QMap<Edge*, GraphElementData*> oldEdgeData;
432 foreach (Edge *e, p) {
433 if (e != p.first()) oldEdgeData[e] = e->data()->copy();
434 }
435
436 _tikzDocument->undoStack()->push(new MakePathCommand(this, p, oldEdgeData));
437 _tikzDocument->undoStack()->endMacro();
438 }
439
splitPath()440 void TikzScene::splitPath()
441 {
442 QSet<Node*> selNodes;
443 QSet<Edge*> edges;
444 getSelection(selNodes, edges);
445
446 // if no edges are selected, try to infer edges from nodes
447 if (edges.isEmpty()) {
448 foreach(Edge *e, graph()->edges()) {
449 if (selNodes.contains(e->source()) && selNodes.contains(e->target()))
450 edges << e;
451 }
452 }
453
454 QSet<Path*> paths;
455 foreach (Edge *e, edges) {
456 if (e->path()) paths << e->path();
457 }
458
459 _tikzDocument->undoStack()->push(new SplitPathCommand(this, paths));
460 }
461
refreshZIndices()462 void TikzScene::refreshZIndices()
463 {
464 qreal z = 0.0;
465 foreach (Edge *e, graph()->edges()) {
466 if (e->path() && e == e->path()->edges().first()) {
467 pathItems()[e->path()]->setZValue(z);
468 edgeItems()[e]->setZValue(z + 0.1);
469 } else {
470 edgeItems()[e]->setZValue(z);
471 }
472 z += 1.0;
473 }
474
475 foreach (Node *n, graph()->nodes()) {
476 nodeItems()[n]->setZValue(z);
477 z += 1.0;
478 }
479 }
480
mousePressEvent(QGraphicsSceneMouseEvent * event)481 void TikzScene::mousePressEvent(QGraphicsSceneMouseEvent *event)
482 {
483 QSettings settings("tikzit", "tikzit");
484 if (!_enabled) return;
485
486 // current mouse position, in scene coordinates
487 _mouseDownPos = event->scenePos();
488
489 _draggingNodes = false;
490 _selectingEdge = nullptr;
491
492 // radius of a control point for bezier edges, in scene coordinates
493 qreal cpR = GLOBAL_SCALEF * (0.1);
494 qreal cpR2 = cpR * cpR;
495
496 if (event->button() == Qt::RightButton &&
497 _tools->currentTool() == ToolPalette::SELECT &&
498 settings.value("smart-tool-enabled", true).toBool())
499 {
500 _smartTool = true;
501 if (!items(_mouseDownPos).isEmpty() &&
502 dynamic_cast<NodeItem*>(items(_mouseDownPos)[0]))
503 {
504 _tools->setCurrentTool(ToolPalette::EDGE);
505 } else {
506 _tools->setCurrentTool(ToolPalette::VERTEX);
507 }
508 }
509
510 switch (_tools->currentTool()) {
511 case ToolPalette::SELECT:
512 // check if we grabbed a control point of an edge
513 foreach (QGraphicsItem *gi, selectedItems()) {
514 if (EdgeItem *ei = dynamic_cast<EdgeItem*>(gi)) {
515 qreal dx, dy;
516
517 dx = ei->cp1Item()->pos().x() - _mouseDownPos.x();
518 dy = ei->cp1Item()->pos().y() - _mouseDownPos.y();
519
520 if (dx*dx + dy*dy <= cpR2) {
521 _modifyEdgeItem = ei;
522 _firstControlPoint = true;
523 break;
524 }
525
526 dx = ei->cp2Item()->pos().x() - _mouseDownPos.x();
527 dy = ei->cp2Item()->pos().y() - _mouseDownPos.y();
528
529 if (dx*dx + dy*dy <= cpR2) {
530 _modifyEdgeItem = ei;
531 _firstControlPoint = false;
532 break;
533 }
534 }
535 }
536
537 if (_modifyEdgeItem != nullptr) {
538 // store for undo purposes
539 Edge *e = _modifyEdgeItem->edge();
540 _oldBend = e->bend();
541 _oldInAngle = e->inAngle();
542 _oldOutAngle = e->outAngle();
543 _oldWeight = e->weight();
544 } else {
545 // since we are not dragging a control point, process the click normally
546 //views()[0]->setDragMode(QGraphicsView::RubberBandDrag);
547 QGraphicsScene::mousePressEvent(event);
548
549 if (items(_mouseDownPos).isEmpty()) {
550 _rubberBandItem->setRect(QRectF(_mouseDownPos,_mouseDownPos));
551 _rubberBandItem->setVisible(true);
552 //qDebug() << "starting rubber band drag";
553 }
554
555 // foreach (QGraphicsItem *gi, items()) {
556 // if (EdgeItem *ei = dynamic_cast<EdgeItem*>(gi)) {
557 // //qDebug() << "got an edge item: " << ei;
558 // ei->setFlag(QGraphicsItem::ItemIsSelectable, false);
559 // //ei->setSelected(true);
560 // }
561 // }
562
563 // save current node positions for undo support
564 _oldNodePositions.clear();
565 foreach (QGraphicsItem *gi, selectedItems()) {
566 if (NodeItem *ni = dynamic_cast<NodeItem*>(gi)) {
567 _oldNodePositions.insert(ni->node(), ni->node()->point());
568 }
569 }
570
571 QList<QGraphicsItem*> its = items(_mouseDownPos);
572 if (!its.isEmpty()) {
573 if (dynamic_cast<NodeItem*>(its[0])) {
574 _draggingNodes = true;
575 } else {
576 foreach (QGraphicsItem *gi, its) {
577 if (EdgeItem *ei = dynamic_cast<EdgeItem*>(gi)) {
578 _selectingEdge = ei->edge();
579 break;
580 }
581 }
582 }
583 }
584 }
585
586 break;
587 case ToolPalette::VERTEX:
588 break;
589 case ToolPalette::EDGE:
590 foreach (QGraphicsItem *gi, items(_mouseDownPos)) {
591 if (NodeItem *ni = dynamic_cast<NodeItem*>(gi)){
592 _edgeStartNodeItem = ni;
593 _edgeEndNodeItem = ni;
594 QLineF line(toScreen(ni->node()->point()), _mouseDownPos);
595 _drawEdgeItem->setLine(line);
596 _drawEdgeItem->setVisible(true);
597 break;
598 }
599 }
600 break;
601 case ToolPalette::CROP:
602 break;
603 }
604 }
605
mouseMoveEvent(QGraphicsSceneMouseEvent * event)606 void TikzScene::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
607 {
608 if (!_enabled) return;
609
610 // current mouse position, in scene coordinates
611 QPointF mousePos = event->scenePos();
612 //QRectF rb = views()[0]->rubberBandRect();
613 //invalidate(-800,-800,1600,1600);
614 //invalidate(QRectF(), QGraphicsScene::BackgroundLayer);
615
616 switch (_tools->currentTool()) {
617 case ToolPalette::SELECT:
618 if (_modifyEdgeItem != nullptr) {
619 Edge *e = _modifyEdgeItem->edge();
620
621 // dragging a control point
622 QPointF src = toScreen(e->source()->point());
623 QPointF targ = toScreen(e->target()->point());
624 qreal dx1 = targ.x() - src.x();
625 qreal dy1 = targ.y() - src.y();
626 qreal dx2, dy2;
627 if (_firstControlPoint) {
628 dx2 = mousePos.x() - src.x();
629 dy2 = mousePos.y() - src.y();
630 } else {
631 dx2 = mousePos.x() - targ.x();
632 dy2 = mousePos.y() - targ.y();
633 }
634
635 qreal baseDist = sqrt(dx1*dx1 + dy1*dy1);
636 qreal handleDist = sqrt(dx2*dx2 + dy2*dy2);
637 qreal wcoarseness = 0.1;
638
639 if (!e->isSelfLoop()) {
640 if (baseDist != 0.0) {
641 e->setWeight(roundToNearest(wcoarseness, handleDist/baseDist));
642 } else {
643 e->setWeight(roundToNearest(wcoarseness, handleDist/GLOBAL_SCALEF));
644 }
645 }
646
647 qreal control_angle = atan2(-dy2, dx2);
648
649 int bcoarseness = 15;
650 qreal bcoarsenessi = 1.0/15.0;
651
652 if(e->basicBendMode()) {
653 qreal bnd;
654 qreal base_angle = atan2(-dy1, dx1);
655 if (_firstControlPoint) {
656 bnd = base_angle - control_angle;
657 } else {
658 bnd = control_angle - base_angle + M_PI;
659 if (bnd > M_PI) bnd -= 2*M_PI;
660 }
661
662 e->setBend(static_cast<int>(round(bnd * (180.0 / M_PI) * bcoarsenessi)) * bcoarseness);
663
664 } else {
665 int bnd = static_cast<int>(round(control_angle * (180.0 / M_PI) * bcoarsenessi)) * bcoarseness;
666 if (_firstControlPoint) {
667 // TODO: enable moving both control points
668 // if ([theEvent modifierFlags] & NSAlternateKeyMask) {
669 // if ([modifyEdge isSelfLoop]) {
670 // [modifyEdge setInAngle:[modifyEdge inAngle] +
671 // (bnd - [modifyEdge outAngle])];
672 // } else {
673 // [modifyEdge setInAngle:[modifyEdge inAngle] -
674 // (bnd - [modifyEdge outAngle])];
675 // }
676 // }
677
678 e->setOutAngle(bnd);
679 } else {
680 // if (theEvent.modifierFlags & NSAlternateKeyMask) {
681 // if ([modifyEdge isSelfLoop]) {
682 // [modifyEdge setOutAngle:[modifyEdge outAngle] +
683 // (bnd - [modifyEdge inAngle])];
684 // } else {
685 // [modifyEdge setOutAngle:[modifyEdge outAngle] -
686 // (bnd - [modifyEdge inAngle])];
687 // }
688 // }
689
690 e->setInAngle(bnd);
691 }
692 }
693
694 _modifyEdgeItem->readPos();
695 Path *p = _modifyEdgeItem->edge()->path();
696 if (p) pathItems()[p]->readPos();
697
698 } else if (_draggingNodes) { // nodes being dragged
699 QGraphicsScene::mouseMoveEvent(event);
700
701 // apply the same offset to all nodes, otherwise we get odd rounding behaviour with
702 // multiple selection.
703 QPointF shift = mousePos - _mouseDownPos;
704 shift = QPointF(round(shift.x()/GRID_SEP)*GRID_SEP, round(shift.y()/GRID_SEP)*GRID_SEP);
705
706 foreach (Node *n, _oldNodePositions.keys()) {
707 NodeItem *ni = _nodeItems[n];
708
709 // in (rare) cases, the graph can change while we are dragging
710 if (ni != nullptr) {
711 ni->setPos(toScreen(_oldNodePositions[n]) + shift);
712 ni->writePos();
713 }
714 }
715
716 refreshAdjacentEdges(_oldNodePositions.keys());
717 } else {
718 // otherwise, process mouse move normally
719 QGraphicsScene::mouseMoveEvent(event);
720
721 if (_rubberBandItem->isVisible()) {
722 qreal left = std::min(_mouseDownPos.x(), mousePos.x());
723 qreal top = std::min(_mouseDownPos.y(), mousePos.y());
724 qreal w = std::abs(_mouseDownPos.x() - mousePos.x());
725 qreal h = std::abs(_mouseDownPos.y() - mousePos.y());
726
727 _rubberBandItem->setRect(QRectF(left, top, w, h));
728 }
729 }
730
731 break;
732 case ToolPalette::VERTEX:
733 break;
734 case ToolPalette::EDGE:
735 if (_drawEdgeItem->isVisible()) {
736 _edgeEndNodeItem = nullptr;
737 foreach (QGraphicsItem *gi, items(mousePos)) {
738 if (NodeItem *ni = dynamic_cast<NodeItem*>(gi)){
739 _edgeEndNodeItem = ni;
740 break;
741 }
742 }
743 QPointF p1 = _drawEdgeItem->line().p1();
744 QPointF p2 = (_edgeEndNodeItem != nullptr) ? toScreen(_edgeEndNodeItem->node()->point()) : mousePos;
745 QLineF line(p1, p2);
746
747 _drawEdgeItem->setLine(line);
748 }
749 break;
750 case ToolPalette::CROP:
751 break;
752 }
753 }
754
mouseReleaseEvent(QGraphicsSceneMouseEvent * event)755 void TikzScene::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
756 {
757 if (!_enabled) return;
758 QSettings settings("tikzit", "tikzit");
759
760 // current mouse position, in scene coordinates
761 QPointF mousePos = event->scenePos();
762
763 switch (_tools->currentTool()) {
764 case ToolPalette::SELECT:
765 if (_modifyEdgeItem != nullptr) {
766 // finished dragging a control point
767 Edge *e = _modifyEdgeItem->edge();
768
769 if (!almostEqual(_oldWeight, e->weight()) ||
770 _oldBend != e->bend() ||
771 _oldInAngle != e->inAngle() ||
772 _oldOutAngle != e->outAngle())
773 {
774 EdgeBendCommand *cmd = new EdgeBendCommand(this, e, _oldWeight, _oldBend, _oldInAngle, _oldOutAngle);
775 _tikzDocument->undoStack()->push(cmd);
776 }
777
778 _modifyEdgeItem = nullptr;
779 } else {
780 // otherwise, process mouse move normally
781 QGraphicsScene::mouseReleaseEvent(event);
782
783 if (_selectingEdge) {
784 bool sel = edgeItems()[_selectingEdge]->isSelected();
785 Path *p = _selectingEdge->path();
786 if (p) {
787 foreach (Edge *e, p->edges()) {
788 if (e != _selectingEdge)
789 edgeItems()[e]->setSelected(sel);
790 nodeItems()[e->source()]->setSelected(sel);
791 nodeItems()[e->target()]->setSelected(sel);
792 }
793 }
794 // else {
795 // nodeItems()[_selectingEdge->source()]->setSelected(sel);
796 // nodeItems()[_selectingEdge->target()]->setSelected(sel);
797 // }
798 }
799
800 if (_rubberBandItem->isVisible()) {
801 QPainterPath sel;
802 sel.addRect(_rubberBandItem->rect());
803 foreach (QGraphicsItem *gi, items()) {
804 if (NodeItem *ni = dynamic_cast<NodeItem*>(gi)) {
805 if (sel.contains(toScreen(ni->node()->point()))) ni->setSelected(true);
806 }
807 }
808 //setSelectionArea(sel);
809 }
810
811 _rubberBandItem->setVisible(false);
812
813 if (!_oldNodePositions.empty()) {
814 QPointF shift = mousePos - _mouseDownPos;
815 shift = QPointF(round(shift.x()/GRID_SEP)*GRID_SEP, round(shift.y()/GRID_SEP)*GRID_SEP);
816
817 if (shift.x() != 0.0 || shift.y() != 0.0) {
818 QMap<Node*,QPointF> newNodePositions;
819
820 foreach (QGraphicsItem *gi, selectedItems()) {
821 if (NodeItem *ni = dynamic_cast<NodeItem*>(gi)) {
822 ni->writePos();
823 newNodePositions.insert(ni->node(), ni->node()->point());
824 }
825 }
826
827 //qDebug() << _oldNodePositions;
828 //qDebug() << newNodePositions;
829
830 _tikzDocument->undoStack()->push(new MoveCommand(this, _oldNodePositions, newNodePositions));
831 }
832
833 _oldNodePositions.clear();
834 }
835 }
836
837 break;
838 case ToolPalette::VERTEX:
839 {
840 QPointF gridPos(round(mousePos.x()/GRID_SEP)*GRID_SEP, round(mousePos.y()/GRID_SEP)*GRID_SEP);
841 Node *n = new Node(_tikzDocument);
842 n->setName(graph()->freshNodeName());
843 n->setPoint(fromScreen(gridPos));
844 n->setStyleName(_styles->activeNodeStyleName());
845
846 QRectF grow(gridPos.x() - GLOBAL_SCALEF, gridPos.y() - GLOBAL_SCALEF, 2 * GLOBAL_SCALEF, 2 * GLOBAL_SCALEF);
847 QRectF newBounds = sceneRect().united(grow);
848 //qDebug() << grow;
849 //qDebug() << newBounds;
850
851 AddNodeCommand *cmd = new AddNodeCommand(this, n, newBounds);
852 _tikzDocument->undoStack()->push(cmd);
853 }
854 break;
855 case ToolPalette::EDGE:
856 // add an edge
857 if (_edgeStartNodeItem != nullptr && _edgeEndNodeItem != nullptr) {
858 Edge *e = new Edge(_edgeStartNodeItem->node(), _edgeEndNodeItem->node(), _tikzDocument);
859 e->setStyleName(_styles->activeEdgeStyleName());
860
861 bool selectEdge = settings.value("select-new-edges", false).toBool();
862 QSet<Node*> selNodes;
863 QSet<Edge*> selEdges;
864 if (selectEdge) getSelection(selNodes, selEdges);
865 AddEdgeCommand *cmd = new AddEdgeCommand(this, e, selectEdge,
866 selNodes, selEdges);
867 _tikzDocument->undoStack()->push(cmd);
868 }
869 _edgeStartNodeItem = nullptr;
870 _edgeEndNodeItem = nullptr;
871 _drawEdgeItem->setVisible(false);
872 break;
873 case ToolPalette::CROP:
874 break;
875 }
876
877 if (_smartTool) {
878 _tools->setCurrentTool(ToolPalette::SELECT);
879 }
880
881 _smartTool = false;
882
883 // clear artefacts from rubber band selection
884 invalidate(QRect(), QGraphicsScene::BackgroundLayer);
885 }
886
887
888
keyReleaseEvent(QKeyEvent * event)889 void TikzScene::keyReleaseEvent(QKeyEvent *event)
890 {
891 //qDebug() << "keyrelease:" << QString::number(event->key(), 16);
892 //qDebug() << "modifiers:" << QString::number(QApplication::queryKeyboardModifiers(), 16);
893 if (!_enabled) return;
894
895 // slower, but seems to be more reliable than event->modifiers()
896 Qt::KeyboardModifiers mod = QApplication::queryKeyboardModifiers();
897
898 // clear highlighting for edge bends (if there was any)
899 if (mod & Qt::ControlModifier) {
900 // it could be the case the user has released shift and is still holding control
901 bool head = !(mod & Qt::ShiftModifier);
902 _highlightHeads = head;
903 _highlightTails = !head;
904 } else {
905 _highlightHeads = false;
906 _highlightTails = false;
907 }
908
909
910
911 foreach (QGraphicsItem *it, selectedItems()) it->update();
912 }
913
keyPressEvent(QKeyEvent * event)914 void TikzScene::keyPressEvent(QKeyEvent *event)
915 {
916 //qDebug() << "keypress:" << QString::number(event->key(), 16);
917 //qDebug() << "modifiers:" << QString::number(QApplication::queryKeyboardModifiers(), 16);
918 bool capture = false;
919
920 // slower, but seems to be more reliable than event->modifiers()
921 Qt::KeyboardModifiers mod = QApplication::queryKeyboardModifiers();
922
923 if (mod & Qt::ControlModifier) {
924 QSet<Node*> selNodes;
925 QSet<Edge*> selEdges;
926 getSelection(selNodes, selEdges);
927
928 if (!selNodes.isEmpty()) {
929 QPointF delta(0,0);
930 qreal shift = (mod & Qt::ShiftModifier) ? 1.0 : 10.0;
931 switch(event->key()) {
932 case Qt::Key_Left:
933 delta.setX(-0.025 * shift);
934 break;
935 case Qt::Key_Right:
936 delta.setX(0.025 * shift);
937 break;
938 case Qt::Key_Up:
939 delta.setY(0.025 * shift);
940 break;
941 case Qt::Key_Down:
942 delta.setY(-0.025 * shift);
943 break;
944 }
945
946 if (!delta.isNull()) {
947 capture = true;
948 QMap<Node*,QPointF> oldNodePositions;
949 QMap<Node*,QPointF> newNodePositions;
950 QPointF pos;
951
952 foreach (QGraphicsItem *gi, selectedItems()) {
953 if (NodeItem *ni = dynamic_cast<NodeItem*>(gi)) {
954 pos = ni->node()->point();
955 oldNodePositions.insert(ni->node(), pos);
956 newNodePositions.insert(ni->node(), pos + delta);
957 }
958 }
959
960 MoveCommand *cmd = new MoveCommand(this, oldNodePositions, newNodePositions);
961 _tikzDocument->undoStack()->push(cmd);
962 }
963 } else if (!selEdges.isEmpty()) {
964 int deltaAngle = 0;
965 qreal deltaWeight = 0.0;
966
967 bool head = !(mod & Qt::ShiftModifier);
968 _highlightHeads = head;
969 _highlightTails = !head;
970
971 switch(event->key()) {
972 case Qt::Key_Left:
973 deltaAngle = 15;
974 break;
975 case Qt::Key_Right:
976 deltaAngle = -15;
977 break;
978 case Qt::Key_Down:
979 deltaWeight = -0.1;
980 break;
981 case Qt::Key_Up:
982 deltaWeight = 0.1;
983 break;
984 }
985
986 if (deltaAngle != 0) {
987 capture = true;
988 _tikzDocument->undoStack()->beginMacro("Bend edges");
989
990 // shift bend by deltaAngle or -deltaAngle (see below)
991 int sign = 1;
992
993 foreach (Edge *e, selEdges) {
994 if (e->basicBendMode()) {
995 _tikzDocument->undoStack()->push(new ChangeEdgeModeCommand(this, e));
996 }
997
998 if (head) {
999 int oldInAngle = e->inAngle();
1000 e->setInAngle(oldInAngle + sign * deltaAngle);
1001 EdgeBendCommand *cmd = new EdgeBendCommand(this, e,
1002 e->weight(),
1003 e->bend(),
1004 oldInAngle,
1005 e->outAngle());
1006 _tikzDocument->undoStack()->push(cmd);
1007 } else {
1008 int oldOutAngle = e->outAngle();
1009 e->setOutAngle(oldOutAngle + sign * deltaAngle);
1010 EdgeBendCommand *cmd = new EdgeBendCommand(this, e,
1011 e->weight(),
1012 e->bend(),
1013 e->inAngle(),
1014 oldOutAngle);
1015 _tikzDocument->undoStack()->push(cmd);
1016 }
1017
1018 // in the special case where 2 edges are selected, bend in opposite directions
1019 if (selEdges.size() == 2) sign *= -1;
1020 }
1021
1022 _tikzDocument->undoStack()->endMacro();
1023 } else if (!almostZero(deltaWeight)) {
1024 capture = true;
1025 _tikzDocument->undoStack()->beginMacro("Adjust edges");
1026
1027 foreach (Edge *e, selEdges) {
1028 qreal oldWeight = e->weight();
1029 // don't let weight drop below 0.1
1030 if (oldWeight + deltaWeight > 0.099) {
1031 e->setWeight(oldWeight + deltaWeight);
1032 EdgeBendCommand *cmd = new EdgeBendCommand(this, e,
1033 oldWeight,
1034 e->bend(),
1035 e->inAngle(),
1036 e->outAngle());
1037 _tikzDocument->undoStack()->push(cmd);
1038 }
1039 }
1040
1041 _tikzDocument->undoStack()->endMacro();
1042 }
1043 }
1044 } else { // no CTRL key
1045 if (event->key() == Qt::Key_Backspace || event->key() == Qt::Key_Delete) {
1046 deleteSelectedItems();
1047 } else if (!event->isAutoRepeat()) {
1048 switch(event->key()) {
1049 case Qt::Key_S:
1050 tikzit->activeWindow()->toolPalette()->setCurrentTool(ToolPalette::SELECT);
1051 break;
1052 case Qt::Key_V:
1053 case Qt::Key_N:
1054 tikzit->activeWindow()->toolPalette()->setCurrentTool(ToolPalette::VERTEX);
1055 break;
1056 case Qt::Key_E:
1057 tikzit->activeWindow()->toolPalette()->setCurrentTool(ToolPalette::EDGE);
1058 break;
1059 case Qt::Key_B:
1060 tikzit->activeWindow()->toolPalette()->setCurrentTool(ToolPalette::CROP);
1061 break;
1062 }
1063 }
1064 }
1065
1066 foreach (QGraphicsItem *it, selectedItems()) it->update();
1067 if (!capture) QGraphicsScene::keyPressEvent(event);
1068 }
1069
mouseDoubleClickEvent(QGraphicsSceneMouseEvent * event)1070 void TikzScene::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
1071 {
1072 if (!_enabled) return;
1073
1074 QPointF mousePos = event->scenePos();
1075
1076 foreach (QGraphicsItem *it, items(mousePos)) {
1077 if (EdgeItem *ei = dynamic_cast<EdgeItem*>(it)) {
1078 if (!ei->edge()->isSelfLoop()) {
1079 ChangeEdgeModeCommand *cmd = new ChangeEdgeModeCommand(this, ei->edge());
1080 _tikzDocument->undoStack()->push(cmd);
1081 }
1082 break;
1083 } else if (NodeItem *ni = dynamic_cast<NodeItem*>(it)) {
1084 QInputDialog *d = new QInputDialog(views()[0]);
1085 d->setLabelText(tr("Label:"));
1086 d->setTextValue(ni->node()->label());
1087 d->setWindowTitle(tr("Node label"));
1088
1089 if (QLineEdit *le = d->findChild<QLineEdit*>()) {
1090 le->setValidator(new DelimitedStringValidator(le));
1091 }
1092
1093 if (d->exec()) {
1094 QMap<Node*,QString> oldLabels;
1095 oldLabels.insert(ni->node(), ni->node()->label());
1096 ChangeLabelCommand *cmd = new ChangeLabelCommand(this, oldLabels, d->textValue());
1097 _tikzDocument->undoStack()->push(cmd);
1098 }
1099
1100 d->deleteLater();
1101 break;
1102 }
1103 }
1104 }
1105
drawNodeLabels() const1106 bool TikzScene::drawNodeLabels() const
1107 {
1108 return _drawNodeLabels;
1109 }
1110
setDrawNodeLabels(bool drawNodeLabels)1111 void TikzScene::setDrawNodeLabels(bool drawNodeLabels)
1112 {
1113 _drawNodeLabels = drawNodeLabels;
1114 }
1115
highlightTails() const1116 bool TikzScene::highlightTails() const
1117 {
1118 return _highlightTails && getSelectedNodes().isEmpty();
1119 }
1120
highlightHeads() const1121 bool TikzScene::highlightHeads() const
1122 {
1123 return _highlightHeads && getSelectedNodes().isEmpty();
1124 }
1125
enabled() const1126 bool TikzScene::enabled() const
1127 {
1128 return _enabled;
1129 }
1130
setEnabled(bool enabled)1131 void TikzScene::setEnabled(bool enabled)
1132 {
1133 _enabled = enabled;
1134 update();
1135 }
1136
lineNumberForSelection()1137 int TikzScene::lineNumberForSelection()
1138 {
1139 foreach (QGraphicsItem *gi, selectedItems()) {
1140 if (NodeItem *ni = dynamic_cast<NodeItem*>(gi)) return ni->node()->tikzLine();
1141 if (EdgeItem *ei = dynamic_cast<EdgeItem*>(gi)) return ei->edge()->tikzLine();
1142 }
1143 return 0;
1144 }
1145
1146
applyActiveStyleToNodes()1147 void TikzScene::applyActiveStyleToNodes() {
1148 ApplyStyleToNodesCommand *cmd = new ApplyStyleToNodesCommand(this, _styles->activeNodeStyleName());
1149 _tikzDocument->undoStack()->push(cmd);
1150 }
1151
applyActiveStyleToEdges()1152 void TikzScene::applyActiveStyleToEdges() {
1153 ApplyStyleToEdgesCommand *cmd = new ApplyStyleToEdgesCommand(this, _styles->activeEdgeStyleName());
1154 _tikzDocument->undoStack()->push(cmd);
1155 }
1156
deleteSelectedItems()1157 void TikzScene::deleteSelectedItems()
1158 {
1159 QSet<Node*> selNodes;
1160 QSet<Edge*> selEdges;
1161 getSelection(selNodes, selEdges);
1162
1163 QMap<int,Node*> deleteNodes;
1164 QMap<int,Edge*> deleteEdges;
1165 QSet<Path*> deletePaths;
1166
1167 for (int i = 0; i < _tikzDocument->graph()->nodes().length(); ++i) {
1168 Node *n = _tikzDocument->graph()->nodes()[i];
1169 if (selNodes.contains(n)) deleteNodes.insert(i, n);
1170 }
1171
1172 for (int i = 0; i < _tikzDocument->graph()->edges().length(); ++i) {
1173 Edge *e = _tikzDocument->graph()->edges()[i];
1174 if (selEdges.contains(e) ||
1175 selNodes.contains(e->source()) ||
1176 selNodes.contains(e->target()))
1177 {
1178 if (e->path()) deletePaths << e->path();
1179 deleteEdges.insert(i, e);
1180 }
1181 }
1182
1183 //qDebug() << "nodes:" << deleteNodes;
1184 //qDebug() << "edges:" << deleteEdges;
1185 _tikzDocument->undoStack()->beginMacro("Delete");
1186 _tikzDocument->undoStack()->push(new SplitPathCommand(this, deletePaths));
1187 _tikzDocument->undoStack()->push(new DeleteCommand(this, deleteNodes, deleteEdges,
1188 selNodes, selEdges));
1189 _tikzDocument->undoStack()->endMacro();
1190 }
1191
copyToClipboard()1192 void TikzScene::copyToClipboard()
1193 {
1194 Graph *g = graph()->copyOfSubgraphWithNodes(getSelectedNodes());
1195 //qDebug() << g->tikz();
1196 QGuiApplication::clipboard()->setText(g->tikz());
1197 delete g;
1198 }
1199
cutToClipboard()1200 void TikzScene::cutToClipboard()
1201 {
1202 copyToClipboard();
1203 deleteSelectedItems();
1204 }
1205
1206
pasteFromClipboard()1207 void TikzScene::pasteFromClipboard()
1208 {
1209 QString tikz = QGuiApplication::clipboard()->text();
1210 Graph *g = new Graph();
1211 TikzAssembler ass(g);
1212
1213 // attempt to parse whatever's on the clipboard, if we get a
1214 // non-empty tikz graph, insert it.
1215 if (ass.parse(tikz) && !g->nodes().isEmpty()) {
1216 // make sure names in the new subgraph are fresh
1217 g->renameApart(graph());
1218
1219 QRectF srcRect = g->realBbox();
1220 QRectF tgtRect = graph()->realBbox();
1221 QPointF shift(tgtRect.right() - srcRect.left(), 0.0);
1222
1223 if (shift.x() > 0) {
1224 foreach (Node *n, g->nodes()) {
1225 n->setPoint(n->point() + shift);
1226 }
1227 }
1228
1229 PasteCommand *cmd = new PasteCommand(this, g);
1230 _tikzDocument->undoStack()->push(cmd);
1231 }
1232 }
1233
selectAllNodes()1234 void TikzScene::selectAllNodes()
1235 {
1236 foreach (NodeItem *ni, _nodeItems.values()) {
1237 ni->setSelected(true);
1238 }
1239 }
1240
deselectAll()1241 void TikzScene::deselectAll()
1242 {
1243 foreach (NodeItem *ni, _nodeItems.values()) {
1244 ni->setSelected(false);
1245 }
1246
1247 foreach (EdgeItem *ei, _edgeItems.values()) {
1248 ei->setSelected(false);
1249 }
1250 }
1251
parseTikz(QString tikz)1252 bool TikzScene::parseTikz(QString tikz)
1253 {
1254 Graph *newGraph = new Graph(this);
1255 TikzAssembler ass(newGraph);
1256 if (ass.parse(tikz)) {
1257 ReplaceGraphCommand *cmd = new ReplaceGraphCommand(this, graph(), newGraph);
1258 tikzDocument()->undoStack()->push(cmd);
1259 setEnabled(true);
1260 views()[0]->setFocus();
1261 return true;
1262 } else {
1263 return false;
1264 }
1265 }
1266
reflectNodes(bool horizontal)1267 void TikzScene::reflectNodes(bool horizontal)
1268 {
1269 ReflectNodesCommand *cmd = new ReflectNodesCommand(this, getSelectedNodes(), horizontal);
1270 tikzDocument()->undoStack()->push(cmd);
1271 }
1272
rotateNodes(bool clockwise)1273 void TikzScene::rotateNodes(bool clockwise)
1274 {
1275 RotateNodesCommand *cmd = new RotateNodesCommand(this, getSelectedNodes(), clockwise);
1276 tikzDocument()->undoStack()->push(cmd);
1277 }
1278
1279
getSelection(QSet<Node * > & selNodes,QSet<Edge * > & selEdges) const1280 void TikzScene::getSelection(QSet<Node *> &selNodes, QSet<Edge *> &selEdges) const
1281 {
1282 foreach (QGraphicsItem *gi, selectedItems()) {
1283 if (NodeItem *ni = dynamic_cast<NodeItem*>(gi)) selNodes << ni->node();
1284 if (EdgeItem *ei = dynamic_cast<EdgeItem*>(gi)) selEdges << ei->edge();
1285 }
1286 }
1287
getSelectedNodes() const1288 QSet<Node *> TikzScene::getSelectedNodes() const
1289 {
1290 QSet<Node*> selNodes;
1291 foreach (QGraphicsItem *gi, selectedItems()) {
1292 if (NodeItem *ni = dynamic_cast<NodeItem*>(gi)) selNodes << ni->node();
1293 }
1294 return selNodes;
1295 }
1296
1297
tikzDocument() const1298 TikzDocument *TikzScene::tikzDocument() const
1299 {
1300 return _tikzDocument;
1301 }
1302
setTikzDocument(TikzDocument * tikzDocument)1303 void TikzScene::setTikzDocument(TikzDocument *tikzDocument)
1304 {
1305 _tikzDocument = tikzDocument;
1306 graphReplaced();
1307 }
1308
reloadStyles()1309 void TikzScene::reloadStyles()
1310 {
1311 _styles->reloadStyles();
1312 foreach(EdgeItem *ei, _edgeItems) {
1313 ei->edge()->attachStyle();
1314 ei->readPos(); // trigger a repaint
1315 }
1316
1317 foreach (NodeItem *ni, _nodeItems) {
1318 ni->node()->attachStyle();
1319 ni->readPos(); // trigger a repaint
1320 }
1321 }
1322
refreshSceneBounds()1323 void TikzScene::refreshSceneBounds() {
1324 qreal maxX = 30.0, maxY = 30.0;
1325 qreal increment = 20.0;
1326
1327 foreach (Node *n, graph()->nodes()) {
1328 while (n->point().x() - increment < -maxX || n->point().x() + increment > maxX) {
1329 maxX += increment;
1330 }
1331
1332 while (n->point().y() - increment < -maxY || n->point().y() + increment > maxY) {
1333 maxY += increment;
1334 }
1335 }
1336
1337 QRectF rect(-GLOBAL_SCALEF * maxX, -GLOBAL_SCALEF * maxY, 2.0 * GLOBAL_SCALEF * maxX, 2.0 * GLOBAL_SCALEF * maxY);
1338
1339 if (rect != sceneRect()) {
1340 setSceneRect(rect);
1341 invalidate();
1342 }
1343 }
1344
1345 // void TikzScene::refreshSceneBounds()
1346 // {
1347 // // if (!views().empty()) {
1348 // // QGraphicsView *v = views().first();
1349 // // QRectF viewB = v->mapToScene(v->viewport()->rect()).boundingRect();
1350 // // //QPointF tl = v->mapToScene(viewB.topLeft());
1351 // // //viewB.setTopLeft(tl);
1352 //
1353 // // QRectF bounds = viewB.united(rectToScreen(graph()->realBbox().adjusted(-1.0f, -1.0f, 1.0f, 1.0f)));
1354 // // //qDebug() << viewB;
1355 //
1356 // // if (bounds != sceneRect()) {
1357 // // QPointF c = viewB.center();
1358 // // setSceneRect(bounds);
1359 // // v->centerOn(c);
1360 // // }
1361 // // }
1362 // //setBounds(graphB);
1363 // }
1364
refreshAdjacentEdges(QList<Node * > nodes)1365 void TikzScene::refreshAdjacentEdges(QList<Node*> nodes)
1366 {
1367 if (nodes.empty()) return;
1368
1369 QSet<Path*> paths;
1370 foreach (Edge *e, _edgeItems.keys()) {
1371 EdgeItem *ei = _edgeItems[e];
1372
1373 // the list "nodes" can be out of date, e.g. if the graph changes while dragging
1374 if (ei != nullptr) {
1375 if (nodes.contains(ei->edge()->source()) || nodes.contains(ei->edge()->target())) {
1376 ei->edge()->updateControls();
1377 ei->readPos();
1378 }
1379 }
1380
1381 // only update paths once
1382 Path *p = ei->edge()->path();
1383 if (p && !paths.contains(p)) {
1384 pathItems()[p]->readPos();
1385 paths << p;
1386 }
1387 }
1388 }
1389
1390 //void TikzScene::setBounds(QRectF bounds)
1391 //{
1392 // if (bounds != sceneRect()) {
1393 // if (!views().empty()) {
1394 // QGraphicsView *v = views().first();
1395 // QPointF c = v->mapToScene(v->viewport()->rect().center());
1396 // setSceneRect(bounds);
1397 // v->centerOn(c);
1398 // } else {
1399 // setSceneRect(bounds);
1400 // }
1401 // }
1402 //}
1403
nodeItems()1404 QMap<Node*,NodeItem *> &TikzScene::nodeItems()
1405 {
1406 return _nodeItems;
1407 }
1408
edgeItems()1409 QMap<Edge*,EdgeItem*> &TikzScene::edgeItems()
1410 {
1411 return _edgeItems;
1412 }
1413
pathItems()1414 QMap<Path *, PathItem *> &TikzScene::pathItems()
1415 {
1416 return _pathItems;
1417 }
1418