1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qt Creator.
7 **
8 ** Commercial License Usage
9 ** Licensees holding valid commercial Qt licenses may use this file in
10 ** accordance with the commercial license agreement provided with the
11 ** Software or, alternatively, in accordance with the terms contained in
12 ** a written agreement between you and The Qt Company. For licensing terms
13 ** and conditions see https://www.qt.io/terms-conditions. For further
14 ** information use the contact form at https://www.qt.io/contact-us.
15 **
16 ** GNU General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU
18 ** General Public License version 3 as published by the Free Software
19 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
20 ** included in the packaging of this file. Please review the following
21 ** information to ensure the GNU General Public License requirements will
22 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23 **
24 ****************************************************************************/
25 
26 #include "transitionitem.h"
27 #include "connectableitem.h"
28 #include "cornergrabberitem.h"
29 #include "finalstateitem.h"
30 #include "graphicsitemprovider.h"
31 #include "graphicsscene.h"
32 #include "parallelitem.h"
33 #include "sceneutils.h"
34 #include "scxmldocument.h"
35 #include "scxmleditorconstants.h"
36 #include "scxmltagutils.h"
37 #include "scxmluifactory.h"
38 #include "serializer.h"
39 #include "stateitem.h"
40 #include "tagtextitem.h"
41 
42 #include <QBrush>
43 #include <QDebug>
44 #include <QGraphicsScene>
45 #include <QMenu>
46 #include <QPainter>
47 #include <QPen>
48 #include <QUndoStack>
49 #include <QtMath>
50 
51 using namespace ScxmlEditor::PluginInterface;
52 
53 const qreal SELECTION_DISTANCE = 10;
54 
TransitionItem(BaseItem * parent)55 TransitionItem::TransitionItem(BaseItem *parent)
56     : BaseItem(parent)
57     , m_startTargetFactor(0.5, 0.5)
58     , m_endTargetFactor(0.5, 0.5)
59 {
60     setFlag(ItemIsSelectable, true);
61 
62     m_highlightPen = QPen(QColor(0xff, 0x00, 0x60));
63     m_highlightPen.setWidth(8);
64     m_highlightPen.setJoinStyle(Qt::MiterJoin);
65 
66     m_pen = QPen(QColor(0x12, 0x12, 0x12));
67     m_pen.setWidth(2);
68 
69     m_arrow << QPointF(0, 0)
70             << QPointF(1, 1)
71             << QPointF(0, 1);
72 
73     m_eventTagItem = new TagTextItem(this);
74     connect(m_eventTagItem, &TagTextItem::selected, this, [=](bool sel){
75         setItemSelected(sel);
76     });
77     connect(m_eventTagItem, &TagTextItem::textReady, this, &TransitionItem::textHasChanged);
78     connect(m_eventTagItem, &TagTextItem::movePointChanged, this, &TransitionItem::textItemPositionChanged);
79 
80     checkWarningItems();
81 }
82 
~TransitionItem()83 TransitionItem::~TransitionItem()
84 {
85     setBlockUpdates(true);
86     removeTransition(Start);
87     removeTransition(End);
88     //updateTarget();
89 }
90 
checkWarningItems()91 void TransitionItem::checkWarningItems()
92 {
93     ScxmlUiFactory *uifactory = uiFactory();
94     if (uifactory) {
95         auto provider = static_cast<GraphicsItemProvider*>(uifactory->object("graphicsItemProvider"));
96         if (provider) {
97             if (!m_warningItem)
98                 m_warningItem = static_cast<TransitionWarningItem*>(provider->createWarningItem(Constants::C_STATE_WARNING_TRANSITION, this));
99         }
100     }
101 }
102 
setTag(ScxmlTag * tag)103 void TransitionItem::setTag(ScxmlTag *tag)
104 {
105     BaseItem::setTag(tag);
106     if (tag) {
107         if (tag->tagType() == InitialTransition)
108             m_eventTagItem->setVisible(false);
109     }
110 }
111 
textItemPositionChanged()112 void TransitionItem::textItemPositionChanged()
113 {
114     QPointF p = m_eventTagItem->movePoint();
115     QString data;
116     if (p.toPoint() != QPoint(0, 0)) {
117         Serializer s;
118         s.append(p);
119         data = s.data();
120     }
121     setEditorInfo(Constants::C_SCXML_EDITORINFO_MOVEPOINT, data);
122 
123     updateComponents();
124 }
125 
textHasChanged(const QString & text)126 void TransitionItem::textHasChanged(const QString &text)
127 {
128     setTagValue("event", text);
129 }
130 
createGrabbers()131 void TransitionItem::createGrabbers()
132 {
133     if (m_cornerGrabbers.count() != m_cornerPoints.count()) {
134         int selectedGrabberIndex = m_cornerGrabbers.indexOf(m_selectedCornerGrabber);
135 
136         if (!m_cornerGrabbers.isEmpty()) {
137             qDeleteAll(m_cornerGrabbers);
138             m_cornerGrabbers.clear();
139         }
140 
141         for (int i = 0; i < m_cornerPoints.count(); ++i) {
142             auto cornerGrabber = new CornerGrabberItem(this);
143             cornerGrabber->setGrabberType(CornerGrabberItem::Circle);
144             m_cornerGrabbers << cornerGrabber;
145         }
146 
147         if (selectedGrabberIndex >= 0 && selectedGrabberIndex < m_cornerGrabbers.count())
148             m_selectedCornerGrabber = m_cornerGrabbers[selectedGrabberIndex];
149         else
150             m_selectedCornerGrabber = nullptr;
151     }
152 
153     m_pen.setStyle(Qt::DotLine);
154 
155     m_lineSelected = true;
156     updateGrabberPositions();
157 }
158 
removeGrabbers()159 void TransitionItem::removeGrabbers()
160 {
161     if (!m_cornerGrabbers.isEmpty()) {
162         qDeleteAll(m_cornerGrabbers);
163         m_cornerGrabbers.clear();
164     }
165 
166     m_lineSelected = false;
167     m_pen.setStyle(Qt::SolidLine);
168 }
169 
updateGrabberPositions()170 void TransitionItem::updateGrabberPositions()
171 {
172     for (int i = 0; i < qMin(m_cornerGrabbers.count(), m_cornerPoints.count()); ++i)
173         m_cornerGrabbers[i]->setPos(m_cornerPoints[i]);
174 }
175 
removeUnnecessaryPoints()176 void TransitionItem::removeUnnecessaryPoints()
177 {
178     if (m_cornerPoints.count() > 2) {
179         bool found = true;
180         while (found) {
181             found = false;
182             for (int i = 1; i < (m_cornerPoints.count() - 1); ++i) {
183                 if (QLineF(m_cornerPoints[i], m_cornerPoints[i + 1]).length() <= 20 || QLineF(m_cornerPoints[i], m_cornerPoints[i - 1]).length() <= 20) {
184                     m_cornerPoints.takeAt(i);
185                     if (i < m_cornerGrabbers.count())
186                         delete m_cornerGrabbers.takeAt(i);
187                     found = true;
188                     break;
189                 }
190             }
191         }
192     }
193     storeValues();
194     updateComponents();
195 }
196 
itemChange(GraphicsItemChange change,const QVariant & value)197 QVariant TransitionItem::itemChange(GraphicsItemChange change, const QVariant &value)
198 {
199     QVariant retValue = BaseItem::itemChange(change, value);
200 
201     switch (change) {
202     case ItemSelectedChange:
203         if (!m_mouseGrabbed) {
204             if (value.toBool())
205                 createGrabbers();
206             else
207                 removeGrabbers();
208         }
209         break;
210     case ItemSceneHasChanged:
211         checkWarningItems();
212         break;
213     default:
214         break;
215     }
216 
217     return retValue;
218 }
219 
snapToAnyPoint(int id,const QPointF & newPoint,int diff)220 void TransitionItem::snapToAnyPoint(int id, const QPointF &newPoint, int diff)
221 {
222     // Check snap to grid
223     bool snappedX = false;
224     bool snappedY = false;
225     for (int i = 0; i < m_cornerPoints.count(); ++i) {
226         if (id != i) {
227             if (qAbs(newPoint.x() - m_cornerPoints[i].x()) < diff) {
228                 m_cornerPoints[id].setX(m_cornerPoints[i].x());
229                 snappedX = true;
230             }
231             if (qAbs(newPoint.y() - m_cornerPoints[i].y()) < diff) {
232                 m_cornerPoints[id].setY(m_cornerPoints[i].y());
233                 snappedY = true;
234             }
235         }
236     }
237 
238     if (!snappedX)
239         m_cornerPoints[id].setX(newPoint.x());
240 
241     if (!snappedY)
242         m_cornerPoints[id].setY(newPoint.y());
243 }
244 
snapPointToPoint(int idSnap,const QPointF & p,int diff)245 void TransitionItem::snapPointToPoint(int idSnap, const QPointF &p, int diff)
246 {
247     if (idSnap >= 0 && static_cast<uint>(idSnap) < static_cast<uint>(m_cornerPoints.count())) {
248         if (qAbs(p.x() - m_cornerPoints[idSnap].x()) < diff)
249             m_cornerPoints[idSnap].setX(p.x());
250         if (qAbs(p.y() - m_cornerPoints[idSnap].y()) < diff)
251             m_cornerPoints[idSnap].setY(p.y());
252     }
253 }
254 
mousePressEvent(QGraphicsSceneMouseEvent * event)255 void TransitionItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
256 {
257     // We want to pan scene when Shift is pressed -> that's why ignore mouse-event here
258     if (event->modifiers() & Qt::ShiftModifier) {
259         event->ignore();
260         return;
261     }
262 
263     QPointF p = event->pos();
264     bool bLeftButton = event->button() == Qt::LeftButton;
265     if (m_mouseGrabbed) {
266         if (bLeftButton) {
267             m_cornerPoints.append(p);
268             snapToAnyPoint(m_cornerPoints.count() - 1, p);
269 
270             if (!m_cornerGrabbers.isEmpty()) {
271                 auto corner = new CornerGrabberItem(this);
272                 corner->setGrabberType(CornerGrabberItem::Circle);
273                 corner->setPos(p);
274                 m_cornerGrabbers.append(corner);
275             }
276         }
277         event->accept();
278     } else {
279         // Check QuickTransition
280         if (bLeftButton) {
281             // If we found QuickTransition-item or CornerGrabber at this point, we must ignore mouse press here
282             // So we can press QuickTransition/CornerGrabber item although there is transition lines front of these items
283             foreach (QGraphicsItem *item, scene()->items(event->scenePos())) {
284                 if (item->type() == QuickTransitionType || (item->type() == CornerGrabberType && item->parentItem() != this)) {
285                     event->ignore();
286                     return;
287                 }
288             }
289         }
290 
291         // Check selection
292         bool sel = m_lineSelected;
293         int i;
294         int foundPointIndex = -1;
295         for (i = 0; i < m_cornerPoints.count(); ++i) {
296             if (QLineF(m_cornerPoints[i], p).length() <= SELECTION_DISTANCE) {
297                 // Is pressed point close enough of the elbow-point
298                 foundPointIndex = i;
299                 sel = true;
300                 break;
301             } else {
302                 if (i < m_cornerPoints.count() - 1) {
303                     QLineF line(m_cornerPoints[i], m_cornerPoints[i + 1]);
304 
305                     // Is pressed point close enough of the line
306                     QPointF intersPoint;
307                     QLineF line2(p, p + QPointF(SELECTION_DISTANCE, SELECTION_DISTANCE));
308                     line2.setAngle(line.angle() + 90);
309                     if (line.intersects(line2, &intersPoint) == QLineF::BoundedIntersection) {
310                         sel = true;
311                     } else {
312                         line2.setAngle(line.angle() - 90);
313                         sel = line.intersects(line2, &intersPoint) == QLineF::BoundedIntersection;
314                     }
315 
316                     if (sel)
317                         break;
318                 }
319             }
320         }
321 
322         // Create or remove grabbers
323         if (sel != m_lineSelected) {
324             if (sel)
325                 createGrabbers();
326             else
327                 removeGrabbers();
328 
329             if (foundPointIndex > 0 && foundPointIndex < m_cornerGrabbers.count()) {
330                 m_selectedCornerGrabber = m_cornerGrabbers[foundPointIndex];
331                 m_selectedCornerGrabber->setSelected(true);
332             }
333         } else if (m_lineSelected && bLeftButton) {
334             m_cornerPoints.insert((i + 1), p);
335             m_selectedCornerGrabber = new CornerGrabberItem(this);
336             m_selectedCornerGrabber->setGrabberType(CornerGrabberItem::Circle);
337             m_selectedCornerGrabber->setPos(p);
338             m_cornerGrabbers.insert((i + 1), m_selectedCornerGrabber);
339             m_selectedCornerGrabber->setSelected(true);
340         } else if (m_lineSelected && !bLeftButton) {
341             showContextMenu(event);
342         }
343 
344         if (sel)
345             event->accept();
346         else
347             event->ignore();
348     }
349 }
350 
mouseMoveEvent(QGraphicsSceneMouseEvent * event)351 void TransitionItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
352 {
353     // We want to pan scene when Shift is pressed -> that's why ignore mouse-event here
354     if (event->modifiers() & Qt::ShiftModifier) {
355         event->ignore();
356         return;
357     }
358 
359     if (m_mouseGrabbed) {
360         setEndPos(event->pos());
361         event->ignore();
362     } else if (m_selectedCornerGrabber) {
363         snapToAnyPoint(m_cornerGrabbers.indexOf(m_selectedCornerGrabber), event->pos());
364         updateComponents();
365         storeValues();
366         BaseItem::mouseMoveEvent(event);
367     }
368 }
369 
mouseReleaseEvent(QGraphicsSceneMouseEvent * event)370 void TransitionItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
371 {
372     // We want to pan scene when Shift is pressed -> that's why ignore mouse-event here
373     if (event->modifiers() & Qt::ShiftModifier) {
374         event->ignore();
375         return;
376     }
377 
378     if (m_mouseGrabbed) {
379         if (event->button() == Qt::RightButton) {
380             connectToTopItem(mapToScene(event->pos()), TransitionItem::End, m_grabbedTargetType);
381             setSelected(false);
382             tag()->document()->undoStack()->endMacro();
383             m_mouseGrabbed = false;
384             ungrabMouse();
385             storeValues();
386         }
387         event->accept();
388     } else {
389         if (event->button() == Qt::LeftButton) {
390             if (m_selectedCornerGrabber) {
391                 m_selectedCornerGrabber = nullptr;
392                 setSelected(true);
393             }
394             removeUnnecessaryPoints();
395         }
396         BaseItem::mouseReleaseEvent(event);
397     }
398 }
399 
checkSelectionBeforeContextMenu(QGraphicsSceneMouseEvent * e)400 void TransitionItem::checkSelectionBeforeContextMenu(QGraphicsSceneMouseEvent *e)
401 {
402     int ind = -1;
403     for (int i = 0; i < m_cornerGrabbers.count(); ++i) {
404         if (m_cornerGrabbers[i]->isSelected()) {
405             ind = i;
406             break;
407         }
408     }
409     m_selectedGrabberIndex = ind;
410     BaseItem::checkSelectionBeforeContextMenu(e);
411 }
412 
createContextMenu(QMenu * menu)413 void TransitionItem::createContextMenu(QMenu *menu)
414 {
415     QVariantMap data;
416     if (m_selectedGrabberIndex > 0) {
417         data[Constants::C_SCXMLTAG_ACTIONTYPE] = TagUtils::RemovePoint;
418         data["cornerIndex"] = m_selectedGrabberIndex;
419         menu->addAction(tr("Remove Point"))->setData(data);
420     }
421 
422     menu->addSeparator();
423     BaseItem::createContextMenu(menu);
424 }
425 
selectedMenuAction(const QAction * action)426 void TransitionItem::selectedMenuAction(const QAction *action)
427 {
428     if (action) {
429         QVariantMap data = action->data().toMap();
430         int actionType = data.value(Constants::C_SCXMLTAG_ACTIONTYPE, -1).toInt();
431 
432         switch (actionType) {
433         case TagUtils::RemovePoint: {
434             int ind = data.value("cornerIndex", 0).toInt();
435             if (ind > 0) {
436                 delete m_cornerGrabbers.takeAt(ind);
437                 m_cornerPoints.takeAt(ind);
438                 updateComponents();
439                 storeValues();
440             }
441             break;
442         }
443         default:
444             BaseItem::selectedMenuAction(action);
445             break;
446         }
447     }
448 }
449 
keyPressEvent(QKeyEvent * event)450 void TransitionItem::keyPressEvent(QKeyEvent *event)
451 {
452     if (event->key() == Qt::Key_Delete || event->key() == Qt::Key_Backspace) {
453         bool bFound = false;
454         if (m_cornerGrabbers.count() > 2) {
455             for (int i = m_cornerGrabbers.count() - 1; i >= 1; i--) {
456                 if (m_cornerGrabbers[i]->isSelected()) {
457                     delete m_cornerGrabbers.takeAt(i);
458                     m_cornerPoints.takeAt(i);
459                     bFound = true;
460                 }
461             }
462         }
463         if (bFound) {
464             updateComponents();
465             storeValues();
466             event->accept();
467             return;
468         }
469     }
470 
471     BaseItem::keyPressEvent(event);
472 }
473 
sceneEventFilter(QGraphicsItem * watched,QEvent * event)474 bool TransitionItem::sceneEventFilter(QGraphicsItem *watched, QEvent *event)
475 {
476     if (watched->type() == CornerGrabberType) {
477         auto c = qgraphicsitem_cast<CornerGrabberItem*>(watched);
478         auto mouseEvent = dynamic_cast<QGraphicsSceneMouseEvent*>(event);
479         if (!c || !mouseEvent)
480             return BaseItem::sceneEventFilter(watched, event);
481 
482         int cid = m_cornerGrabbers.indexOf(c);
483         if (mouseEvent->type() == QEvent::GraphicsSceneMouseMove) {
484             if (mouseEvent->buttons() & Qt::LeftButton) {
485                 QPointF movingPoint = c->pressedPoint() - mouseEvent->pos();
486 
487                 if (cid == 0) {
488                     if (!m_movingFirstPoint) {
489                         m_movingFirstPoint = true;
490                         removeTransition(Start);
491                     }
492                 } else if (cid == (m_cornerPoints.count() - 1)) {
493                     if (!m_movingLastPoint) {
494                         m_movingLastPoint = true;
495                         if (m_endItem)
496                             removeTransition(End);
497                         else {
498                             updateZValue();
499                             updateTargetType();
500                         }
501                     }
502                 }
503 
504                 if (cid >= 0 && cid < m_cornerPoints.count())
505                     snapToAnyPoint(cid, m_cornerPoints[cid] - movingPoint);
506 
507                 updateComponents();
508             }
509             return true;
510         } else if (mouseEvent->type() == QEvent::GraphicsSceneMouseRelease) {
511             if (mouseEvent->button() == Qt::LeftButton) {
512                 if (cid == 0 || (cid == m_cornerPoints.count() - 1)) {
513                     m_movingFirstPoint = false;
514                     m_movingLastPoint = false;
515                     connectToTopItem(watched->mapToScene(mouseEvent->pos()), cid == 0 ? Start : End, UnknownType);
516                 }
517                 removeUnnecessaryPoints();
518             } else
519                 showContextMenu(mouseEvent);
520 
521             storeValues();
522             return true;
523         }
524     }
525 
526     return BaseItem::sceneEventFilter(watched, event);
527 }
528 
removeTransition(TransitionPoint p)529 void TransitionItem::removeTransition(TransitionPoint p)
530 {
531     // Remove transition from the item
532     // Private function. Transition can itself remove connection from the item
533     if (p == Start && m_startItem) {
534         m_oldStartItem = m_startItem;
535         m_startItem->removeOutputTransition(this);
536         m_startItem = nullptr;
537         updateZValue();
538         updateTargetType();
539         if (m_oldStartItem)
540             m_oldStartItem->updateTransitions();
541     } else if (p == End && m_endItem) {
542         m_endItem->removeInputTransition(this);
543         m_endItem = nullptr;
544         updateZValue();
545         updateTargetType();
546     }
547 }
548 
disconnectItem(ConnectableItem * item)549 void TransitionItem::disconnectItem(ConnectableItem *item)
550 {
551     // Disconnect item, normally called from the ConnectableItem
552     if (item == m_startItem)
553         removeTransition(Start);
554     if (item == m_endItem)
555         removeTransition(End);
556 
557     updateTarget();
558 }
559 
setStartItem(ConnectableItem * item)560 void TransitionItem::setStartItem(ConnectableItem *item)
561 {
562     m_oldStartItem = nullptr;
563     m_startItem = item;
564 
565     if (item) {
566         if (tag())
567             tag()->document()->changeParent(tag(), m_startItem->tag());
568         item->addOutputTransition(this);
569 
570         if (m_cornerPoints.isEmpty()) {
571             m_cornerPoints << sceneTargetPoint(TransitionPoint::Start);
572             m_cornerPoints << sceneTargetPoint(TransitionPoint::End);
573         }
574     }
575 
576     updateZValue();
577     updateComponents();
578     storeValues();
579 }
580 
startTransitionFrom(ConnectableItem * item,const QPointF & mouseScenePos)581 void TransitionItem::startTransitionFrom(ConnectableItem *item, const QPointF &mouseScenePos)
582 {
583     m_oldStartItem = nullptr;
584     m_startItem = item;
585     m_startItem->addOutputTransition(this);
586     m_cornerPoints.clear();
587     m_cornerPoints << sceneTargetPoint(TransitionPoint::Start);
588     m_cornerPoints << mapFromScene(mouseScenePos);
589 
590     createGrabbers();
591     updateZValue();
592     updateComponents();
593     storeValues();
594     m_cornerGrabbers.last()->setSelected(true);
595 }
596 
connectToTopItem(const QPointF & pos,TransitionPoint tp,ItemType targetType)597 void TransitionItem::connectToTopItem(const QPointF &pos, TransitionPoint tp, ItemType targetType)
598 {
599     int cornerPoints = m_cornerPoints.count();
600 
601     ConnectableItem *parentItem = nullptr;
602     ScxmlTag *parentTag = nullptr;
603     ScxmlDocument *document = tag()->document();
604 
605     snapToAnyPoint(m_cornerPoints.count() - 1, pos);
606     QPointF p(m_cornerPoints.last());
607 
608     // First try to find parentItem
609     QList<QGraphicsItem*> items = scene()->items(p);
610     if (!items.isEmpty()) {
611         for (int i = 0; i < items.count(); ++i) {
612             ItemType type = ItemType(items[i]->type());
613             if ((targetType == UnknownType && type >= FinalStateType) || type >= StateType) {
614                 auto it = qgraphicsitem_cast<ConnectableItem*>(items[i]);
615                 if (it) {
616                     parentItem = it;
617                     parentTag = parentItem->tag();
618                     break;
619                 }
620             }
621         }
622     }
623 
624     if (!parentTag && document)
625         parentTag = document->rootTag();
626 
627     // Connect existing item
628     if (targetType == UnknownType) {
629         switch (tp) {
630         case Start:
631             if (parentItem) {
632                 m_startTargetFactor = calculateTargetFactor(parentItem, pos);
633                 savePoint(m_startTargetFactor * 100, Constants::C_SCXML_EDITORINFO_STARTTARGETFACTORS);
634             }
635             setStartItem(parentItem ? parentItem : m_oldStartItem);
636             break;
637         case End:
638             m_endTargetFactor = calculateTargetFactor(parentItem, pos);
639             savePoint(m_endTargetFactor * 100, Constants::C_SCXML_EDITORINFO_ENDTARGETFACTORS);
640             setEndItem(parentItem);
641             break;
642         default:
643             break;
644         }
645 
646         setSelected(false);
647         if (parentItem)
648             parentItem->setSelected(true);
649         removeGrabbers();
650         if (m_startItem == m_endItem && cornerPoints == 2) {
651             setTagValue("type", "internal");
652             setEndItem(nullptr);
653             m_targetType = InternalNoTarget;
654         }
655 
656         updateEventName();
657         storeValues();
658     } else {
659         // Create new item and connect to it
660         ConnectableItem *newItem = SceneUtils::createItem(targetType, parentItem ? parentItem->mapFromScene(p) : p);
661         if (newItem) {
662             ScxmlTag *newTag = SceneUtils::createTag(targetType, tag()->document());
663             newItem->setTag(newTag);
664             newItem->setParentItem(parentItem);
665             if (!parentItem)
666                 scene()->addItem(newItem);
667 
668             newItem->addInputTransition(this);
669             newItem->updateAttributes();
670             newItem->updateEditorInfo();
671             newItem->updateUIProperties();
672 
673             if (parentItem)
674                 parentItem->updateUIProperties();
675 
676             if (document)
677                 document->addTag(parentTag, newTag);
678 
679             setEndItem(newItem);
680             setSelected(false);
681             newItem->setSelected(true);
682         }
683 
684         removeGrabbers();
685     }
686 
687     updateTargetType();
688 }
689 
setEndPos(const QPointF & endPos,bool snap)690 void TransitionItem::setEndPos(const QPointF &endPos, bool snap)
691 {
692     if (m_cornerPoints.count() >= 2) {
693         m_cornerPoints.last().setX(endPos.x());
694         m_cornerPoints.last().setY(endPos.y());
695 
696         if (snap)
697             snapToAnyPoint(m_cornerPoints.count() - 1, endPos);
698         updateComponents();
699         storeValues();
700     }
701 }
702 
setEndItem(ConnectableItem * item,bool fixValue)703 void TransitionItem::setEndItem(ConnectableItem *item, bool fixValue)
704 {
705 
706     if (item) {
707         m_endItem = item;
708         item->addInputTransition(this);
709         setEndPos(sceneTargetPoint(End), false);
710 
711         if (m_cornerPoints.count() > 2)
712             snapPointToPoint(m_cornerPoints.count() - 2, m_cornerPoints.last(), 15);
713     } else {
714         removeTransition(End);
715         updateComponents();
716         storeValues();
717     }
718 
719     updateZValue();
720     updateTarget(fixValue);
721 }
722 
loadPoint(const QString & name)723 QPointF TransitionItem::loadPoint(const QString &name)
724 {
725     Serializer s;
726     QPointF p;
727     s.setData(editorInfo(name));
728     s.read(p);
729     return p;
730 }
731 
savePoint(const QPointF & p,const QString & name)732 void TransitionItem::savePoint(const QPointF &p, const QString &name)
733 {
734     Serializer s;
735     s.append(p);
736     setEditorInfo(name, s.data(), true);
737 }
738 
calculateTargetFactor(ConnectableItem * item,const QPointF & pos)739 QPointF TransitionItem::calculateTargetFactor(ConnectableItem *item, const QPointF &pos)
740 {
741     if (item) {
742         QRectF r = item->sceneBoundingRect().adjusted(-8, -8, 8, 8);
743         QPointF pixelFactorPoint = pos - r.topLeft();
744         QPointF normalizedPoint(qBound(0.0, pixelFactorPoint.x() / r.width(), 1.0), qBound(0.0, pixelFactorPoint.y() / r.height(), 1.0));
745 
746         // Center point if close enough
747         if (qAbs(normalizedPoint.x() - 0.5) < 0.2 && qAbs(normalizedPoint.y() - 0.5) < 0.2)
748             return QPointF(0.5, 0.5);
749 
750         return normalizedPoint;
751     }
752 
753     return QPointF(0.5, 0.5);
754 }
755 
sceneTargetPoint(TransitionPoint p)756 QPointF TransitionItem::sceneTargetPoint(TransitionPoint p)
757 {
758     ConnectableItem *item = nullptr;
759     QPointF factorPoint;
760     if (p == TransitionPoint::Start) {
761         item = m_startItem;
762         factorPoint = m_startTargetFactor;
763     } else {
764         if (m_endItem) {
765             item = m_endItem;
766             factorPoint = m_endTargetFactor;
767         } else {
768             item = m_startItem;
769             factorPoint = QPointF(0.5, 0.5);
770         }
771     }
772 
773     QRectF r;
774     if (item)
775         r = item->sceneBoundingRect();
776 
777     return r.topLeft() + QPointF(factorPoint.x() * r.width(), factorPoint.y() * r.height());
778 }
779 
findIntersectionPoint(ConnectableItem * item,const QLineF & line,const QPointF & defaultPoint)780 QPointF TransitionItem::findIntersectionPoint(ConnectableItem *item, const QLineF &line, const QPointF &defaultPoint)
781 {
782     // Check circles
783     ItemType t = ItemType(item->type());
784     if (t >= InitialStateType && t <= HistoryType) {
785         QLineF l = item == m_endItem ? line : QLineF(line.p2(), line.p1());
786         l.setLength(l.length() - qMin(item->boundingRect().width(), item->boundingRect().height()) * 0.45);
787         return l.p2();
788     }
789 
790     // Find intersection point between line and target item
791     QPolygonF itemPolygon = item->polygonShape();
792     if (!itemPolygon.isEmpty()) {
793         QPointF intersectPoint;
794         QPointF p1 = itemPolygon.at(0) + item->scenePos();
795         QPointF p2;
796         QLineF checkLine;
797         for (int i = 1; i < itemPolygon.count(); ++i) {
798             p2 = itemPolygon.at(i) + item->scenePos();
799             checkLine = QLineF(p1, p2);
800             if (checkLine.intersects(line, &intersectPoint) == QLineF::BoundedIntersection)
801                 return intersectPoint;
802             p1 = p2;
803         }
804     }
805 
806     return defaultPoint;
807 }
808 
updateComponents()809 void TransitionItem::updateComponents()
810 {
811     if (m_cornerPoints.count() < 2)
812         return;
813 
814     // Check if we must move all points together
815     bool movePoints = (SceneUtils::isSomeSelected(m_startItem) && SceneUtils::isSomeSelected(m_endItem) && m_cornerPoints.count() > 2) || (m_movingFirstPoint && m_targetType == InternalNoTarget);
816 
817     if (!movePoints && !m_mouseGrabbed) {
818         // Check the corners which are in the same line if cornerGrabbers are hidden
819         if (m_cornerGrabbers.isEmpty() && m_cornerPoints.count() > 2) {
820             for (int i = 0; i < m_cornerPoints.count() - 2; ++i) {
821                 if (m_cornerPoints[i] != m_cornerPoints[i + 1] && m_cornerPoints[i] != m_cornerPoints[i + 2]) {
822                     QLineF l1(m_cornerPoints[i], m_cornerPoints[i + 1]);
823                     QLineF l2(m_cornerPoints[i], m_cornerPoints[i + 2]);
824 
825                     if (qRound(l1.angle()) == qRound(l2.angle())) {
826                         m_cornerPoints.takeAt(i + 1);
827                         storeValues();
828                         break;
829                     }
830                 }
831             }
832         }
833     }
834 
835     if ((m_movingFirstPoint || m_movingLastPoint) && !(m_movingFirstPoint && m_targetType == InternalNoTarget))
836         movePoints = false;
837 
838     // Init first line
839     QLineF firstLine(m_cornerPoints[0], m_cornerPoints[1]);
840     if (m_startItem) {
841         if (m_targetType <= InternalNoTarget) {
842             QPointF p = m_startItem->getInternalPosition(this, m_targetType);
843             firstLine.setP1(p);
844             firstLine.setP2(p + QPointF(20, 0));
845             m_cornerPoints[1] = firstLine.p2();
846             if (!(m_movingFirstPoint && m_targetType == InternalNoTarget))
847                 movePoints = false;
848         } else {
849             firstLine.setP1(sceneTargetPoint(TransitionPoint::Start));
850             firstLine.setP1(findIntersectionPoint(m_startItem, firstLine, firstLine.p1()));
851         }
852     }
853 
854     if (movePoints) {
855         if (m_movingFirstPoint && m_targetType == InternalNoTarget)
856             m_cornerPoints[1] = m_cornerPoints[0] + QPointF(20, 0);
857         else {
858             QPointF movingPoint = firstLine.p1() - m_cornerPoints[0];
859             for (int i = 0; i < m_cornerPoints.count(); ++i)
860                 m_cornerPoints[i] += movingPoint;
861 
862             for (int i = 0; i < m_arrow.count(); ++i)
863                 m_arrow[i] += movingPoint;
864         }
865     }
866 
867     m_cornerPoints[0] = firstLine.p1();
868 
869     // Init last line
870     int lastIndex = m_cornerPoints.count() - 1;
871     QLineF lastLine(m_cornerPoints[lastIndex - 1], m_cornerPoints[lastIndex]);
872     if (m_endItem && m_targetType > InternalNoTarget) {
873         lastLine.setP2(sceneTargetPoint(TransitionPoint::End));
874         lastLine.setP2(findIntersectionPoint(m_endItem, lastLine, lastLine.p2()));
875         m_cornerPoints[lastIndex] = lastLine.p2();
876     }
877 
878     m_arrowAngle = 0;
879     if (m_targetType == InternalSameTarget) {
880         m_arrowAngle = M_PI * 0.6;
881     } else {
882         // Calculate angle of the lastLine and update arrow
883         if (lastLine.length() > 0) {
884             m_arrowAngle = qAcos(lastLine.dx() / lastLine.length()) + M_PI;
885             if (lastLine.dy() >= 0)
886                 m_arrowAngle = (2 * M_PI) - m_arrowAngle;
887         }
888     }
889 
890     m_arrow[0] = lastLine.p2() + QPointF(qSin(m_arrowAngle + M_PI / 3) * m_arrowSize, qCos(m_arrowAngle + M_PI / 3) * m_arrowSize);
891     m_arrow[1] = lastLine.p2();
892     m_arrow[2] = lastLine.p2() + QPointF(qSin(m_arrowAngle + M_PI - M_PI / 3) * m_arrowSize, qCos(m_arrowAngle + M_PI - M_PI / 3) * m_arrowSize);
893 
894     setItemBoundingRect(m_cornerPoints.boundingRect().normalized().adjusted(-SELECTION_DISTANCE, -SELECTION_DISTANCE,
895         SELECTION_DISTANCE, SELECTION_DISTANCE));
896 
897     // Set the right place for the name of the transition
898     int ind = m_cornerPoints.count() / 2 - 1;
899     QLineF nameLine(m_cornerPoints[ind], m_cornerPoints[ind + 1]);
900     if (m_targetType <= InternalNoTarget) {
901         m_eventTagItem->setPos(m_cornerPoints[1].x() + 6, m_cornerPoints[1].y() - m_eventTagItem->boundingRect().height() / 3);
902     } else {
903         const qreal w2 = m_eventTagItem->boundingRect().width() / 2;
904         QPointF startPos = nameLine.pointAt(0.5);
905         QLineF targetLine(startPos, startPos + QPointF(SELECTION_DISTANCE, SELECTION_DISTANCE));
906         targetLine.setAngle(nameLine.angle() + 90);
907 
908         m_eventTagItem->setPos(targetLine.p2() + m_eventTagItem->movePoint() - QPointF(w2, m_eventTagItem->boundingRect().height() / 2));
909     }
910 
911     if (m_warningItem)
912         m_warningItem->setPos(m_eventTagItem->pos() - QPointF(WARNING_ITEM_SIZE, 0));
913     updateGrabberPositions();
914     updateZValue();
915 }
916 
grabMouse(ItemType targetType)917 void TransitionItem::grabMouse(ItemType targetType)
918 {
919     m_grabbedTargetType = targetType;
920     m_mouseGrabbed = true;
921     BaseItem::grabMouse();
922 }
923 
paint(QPainter * painter,const QStyleOptionGraphicsItem * option,QWidget * widget)924 void TransitionItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
925 {
926     Q_UNUSED(option)
927     Q_UNUSED(widget)
928 
929     painter->save();
930 
931     painter->setRenderHint(QPainter::Antialiasing, true);
932     painter->setPen(m_pen);
933 
934     if (m_cornerPoints.count() >= 2) {
935         if (m_targetType == InternalSameTarget) {
936             QRectF rect(m_cornerPoints[0].x(), m_cornerPoints[0].y() - SELECTION_DISTANCE,
937                 m_cornerPoints[1].x() - m_cornerPoints[0].x(),
938                 SELECTION_DISTANCE * 2);
939             painter->drawArc(rect, 0, 180 * 16);
940         } else {
941             if (highlight()) {
942                 painter->setPen(m_highlightPen);
943                 painter->drawPolyline(m_cornerPoints);
944             }
945             painter->setPen(m_pen);
946             painter->drawPolyline(m_cornerPoints);
947         }
948     }
949 
950     for (int i = 0; i < m_cornerPoints.count() - 1; ++i)
951         painter->drawEllipse(m_cornerPoints[i], 2, 2);
952 
953     if (highlight()) {
954         painter->setPen(m_highlightPen);
955         painter->drawPolyline(m_arrow);
956     }
957 
958     painter->setPen(m_pen);
959     painter->drawPolyline(m_arrow);
960 
961     painter->restore();
962 }
963 
updateEditorInfo(bool allChilds)964 void TransitionItem::updateEditorInfo(bool allChilds)
965 {
966     BaseItem::updateEditorInfo(allChilds);
967 
968     const QColor fontColor = editorInfo(Constants::C_SCXML_EDITORINFO_FONTCOLOR);
969     m_eventTagItem->setDefaultTextColor(fontColor.isValid() ? fontColor : Qt::black);
970 
971     const QColor stateColor = editorInfo(Constants::C_SCXML_EDITORINFO_STATECOLOR);
972     m_pen.setColor(stateColor.isValid() ? stateColor : qRgb(0x12, 0x12, 0x12));
973 }
974 
updateTarget(bool fixValue)975 void TransitionItem::updateTarget(bool fixValue)
976 {
977     if (fixValue)
978         setTagValue("target", m_endItem ? m_endItem->itemId() : QString());
979     if (m_endItem)
980         m_endItem->checkInitial(true);
981 }
982 
updateAttributes()983 void TransitionItem::updateAttributes()
984 {
985     BaseItem::updateAttributes();
986 
987     // Find new target
988     if (!m_endItem || tagValue("target") != m_endItem->itemId()) {
989         if (m_endItem)
990             m_endItem->removeInputTransition(this);
991 
992         m_endItem = nullptr;
993         findEndItem();
994         updateTarget(false);
995         updateZValue();
996     }
997 
998     // Set event-name
999     updateEventName();
1000     updateTargetType();
1001 }
1002 
init(ScxmlTag * tag,BaseItem * parentItem,bool initChildren,bool blockUpdates)1003 void TransitionItem::init(ScxmlTag *tag, BaseItem *parentItem, bool initChildren, bool blockUpdates)
1004 {
1005     Q_UNUSED(initChildren)
1006     setBlockUpdates(blockUpdates);
1007 
1008     setTag(tag);
1009     setParentItem(parentItem);
1010     updateEditorInfo();
1011 
1012     if (blockUpdates)
1013         setBlockUpdates(false);
1014 }
1015 
readUISpecifiedProperties(const ScxmlTag * tag)1016 void TransitionItem::readUISpecifiedProperties(const ScxmlTag *tag)
1017 {
1018     if (tag) {
1019         if (m_cornerPoints.count() >= 2) {
1020             while (m_cornerPoints.count() > 2)
1021                 m_cornerPoints.takeAt(1);
1022 
1023             Serializer s;
1024 
1025             QPointF p = loadPoint(Constants::C_SCXML_EDITORINFO_STARTTARGETFACTORS);
1026             if (p.isNull())
1027                 p = QPointF(50, 50);
1028             m_startTargetFactor = p / 100;
1029 
1030             p = loadPoint(Constants::C_SCXML_EDITORINFO_ENDTARGETFACTORS);
1031             if (p.isNull())
1032                 p = QPointF(50, 50);
1033             m_endTargetFactor = p / 100;
1034 
1035             QString localPointsData = editorInfo(Constants::C_SCXML_EDITORINFO_LOCALGEOMETRY);
1036             if (!localPointsData.isEmpty()) {
1037                 QPointF startPos = sceneTargetPoint(Start);
1038                 QPolygonF localPoints;
1039                 s.setData(localPointsData);
1040                 s.read(localPoints);
1041                 for (int i = 0; i < localPoints.count(); ++i)
1042                     m_cornerPoints.insert(i + 1, startPos + localPoints[i]);
1043             } else {
1044                 QPolygonF scenePoints;
1045                 s.setData(editorInfo(Constants::C_SCXML_EDITORINFO_GEOMETRY));
1046                 s.read(scenePoints);
1047                 for (int i = 0; i < scenePoints.count(); ++i)
1048                     m_cornerPoints.insert(i + 1, scenePoints[i]);
1049             }
1050 
1051             m_eventTagItem->resetMovePoint(loadPoint(Constants::C_SCXML_EDITORINFO_MOVEPOINT));
1052 
1053             if (m_lineSelected)
1054                 createGrabbers();
1055             updateComponents();
1056         }
1057     }
1058 }
1059 
finalizeCreation()1060 void TransitionItem::finalizeCreation()
1061 {
1062     bool old = blockUpdates();
1063     setBlockUpdates(true);
1064 
1065     updateAttributes();
1066 
1067     if (!old)
1068         setBlockUpdates(false);
1069 }
1070 
checkVisibility(double scaleFactor)1071 void TransitionItem::checkVisibility(double scaleFactor)
1072 {
1073     m_eventTagItem->setVisible(scaleFactor > 0.5);
1074 }
1075 
containsScenePoint(const QPointF & p) const1076 bool TransitionItem::containsScenePoint(const QPointF &p) const
1077 {
1078     QPointF pp = mapFromScene(p);
1079 
1080     for (int i = 0; i < m_cornerPoints.count() - 1; ++i) {
1081         QLineF line(m_cornerPoints[i], m_cornerPoints[i + 1]);
1082 
1083         // Is point close enough of the line
1084         QPointF intersPoint;
1085         QLineF line2(pp, pp + QPointF(SELECTION_DISTANCE, SELECTION_DISTANCE));
1086         line2.setAngle(line.angle() + 90);
1087         if (line.intersects(line2, &intersPoint) == QLineF::BoundedIntersection) {
1088             return true;
1089         } else {
1090             line2.setAngle(line.angle() - 90);
1091             if (line.intersects(line2, &intersPoint) == QLineF::BoundedIntersection)
1092                 return true;
1093         }
1094     }
1095     return false;
1096 }
1097 
findEndItem()1098 void TransitionItem::findEndItem()
1099 {
1100     QString targetId = tagValue("target");
1101     if (!m_endItem && !targetId.isEmpty()) {
1102         QList<QGraphicsItem*> items = scene()->items();
1103         for (int i = 0; i < items.count(); ++i) {
1104             if (items[i]->type() >= FinalStateType) {
1105                 auto item = qgraphicsitem_cast<ConnectableItem*>(items[i]);
1106                 if (item && item->itemId() == targetId) {
1107                     setEndItem(item, false);
1108                     break;
1109                 }
1110             }
1111         }
1112     }
1113 }
1114 
updateEventName()1115 void TransitionItem::updateEventName()
1116 {
1117     m_eventTagItem->setText(tagValue("event"));
1118 }
1119 
storeGeometry(bool block)1120 void TransitionItem::storeGeometry(bool block)
1121 {
1122     if (tag()) {
1123         if (m_cornerPoints.count() <= 2) {
1124             setEditorInfo(Constants::C_SCXML_EDITORINFO_GEOMETRY, QString(), block);
1125             setEditorInfo(Constants::C_SCXML_EDITORINFO_LOCALGEOMETRY, QString(), block);
1126         } else {
1127             QPolygonF pol = m_cornerPoints;
1128             pol.takeFirst();
1129             pol.takeLast();
1130             Serializer s;
1131             for (int i = 0; i < pol.count(); ++i) {
1132                 QPointF spos = sceneTargetPoint(Start);
1133                 pol[i].setX(pol[i].x() - spos.x());
1134                 pol[i].setY(pol[i].y() - spos.y());
1135             }
1136             s.append(pol);
1137 
1138             setEditorInfo(Constants::C_SCXML_EDITORINFO_LOCALGEOMETRY, s.data(), block);
1139         }
1140     }
1141 }
1142 
storeMovePoint(bool block)1143 void TransitionItem::storeMovePoint(bool block)
1144 {
1145     if (m_eventTagItem->movePoint().toPoint() == QPoint(0, 0))
1146         setEditorInfo(Constants::C_SCXML_EDITORINFO_MOVEPOINT, QString(), block);
1147     else
1148         savePoint(m_eventTagItem->movePoint(), Constants::C_SCXML_EDITORINFO_MOVEPOINT);
1149 }
1150 
storeTargetFactors(bool block)1151 void TransitionItem::storeTargetFactors(bool block)
1152 {
1153     if (m_startTargetFactor == QPointF(0.5, 0.5))
1154         setEditorInfo(Constants::C_SCXML_EDITORINFO_STARTTARGETFACTORS, QString(), block);
1155     else
1156         savePoint(m_startTargetFactor * 100, Constants::C_SCXML_EDITORINFO_STARTTARGETFACTORS);
1157 
1158     if (m_endTargetFactor == QPointF(0.5, 0.5))
1159         setEditorInfo(Constants::C_SCXML_EDITORINFO_ENDTARGETFACTORS, QString(), block);
1160     else
1161         savePoint(m_endTargetFactor * 100, Constants::C_SCXML_EDITORINFO_ENDTARGETFACTORS);
1162 }
1163 
storeValues(bool block)1164 void TransitionItem::storeValues(bool block)
1165 {
1166     storeGeometry(block);
1167     storeMovePoint(block);
1168     storeTargetFactors(block);
1169 }
1170 
updateUIProperties()1171 void TransitionItem::updateUIProperties()
1172 {
1173     if (tag() && isActiveScene())
1174         storeValues();
1175 }
1176 
updateTargetType()1177 void TransitionItem::updateTargetType()
1178 {
1179     if (m_movingFirstPoint && m_targetType == InternalNoTarget)
1180         return;
1181 
1182     TransitionTargetType type = ExternalTarget;
1183 
1184     if (m_startItem && m_startItem == m_endItem)
1185         type = InternalSameTarget;
1186     else if (m_startItem && !m_endItem) {
1187         if (m_movingLastPoint) {
1188             type = ExternalNoTarget;
1189         } else {
1190             QRectF srect = m_startItem->sceneBoundingRect();
1191             if (srect.contains(m_cornerPoints.last()))
1192                 type = InternalNoTarget;
1193             else
1194                 type = ExternalNoTarget;
1195         }
1196     } else {
1197         type = ExternalTarget;
1198     }
1199 
1200     if (type <= InternalNoTarget) {
1201         m_eventTagItem->resetMovePoint();
1202         m_arrowSize = 6;
1203         // Remove extra points
1204         while (m_cornerPoints.count() > 2)
1205             m_cornerPoints.takeAt(1);
1206         while (m_cornerGrabbers.count() > 2)
1207             delete m_cornerGrabbers.takeAt(1);
1208 
1209         // Remove editorinfo.geometry
1210         setEditorInfo(Constants::C_SCXML_EDITORINFO_GEOMETRY, QString(), true);
1211         setEditorInfo(Constants::C_SCXML_EDITORINFO_LOCALGEOMETRY, QString(), true);
1212         setEditorInfo(Constants::C_SCXML_EDITORINFO_MOVEPOINT, QString(), true);
1213         setEditorInfo(Constants::C_SCXML_EDITORINFO_STARTTARGETFACTORS, QString(), true);
1214         setEditorInfo(Constants::C_SCXML_EDITORINFO_ENDTARGETFACTORS, QString(), true);
1215     } else {
1216         m_arrowSize = 10;
1217     }
1218 
1219     if (m_targetType != type) {
1220         m_targetType = type;
1221         if (m_warningItem)
1222             m_warningItem->check();
1223         if (m_startItem && !m_startItem->blockUpdates()) {
1224             m_startItem->updateOutputTransitions();
1225             if (m_startItem->type() >= StateType)
1226                 static_cast<StateItem*>(m_startItem)->updateBoundingRect();
1227         }
1228     }
1229 }
1230 
connectedItem(const ConnectableItem * other) const1231 ConnectableItem *TransitionItem::connectedItem(const ConnectableItem *other) const
1232 {
1233     if (other) {
1234         if (other == m_startItem)
1235             return m_endItem;
1236         else if (other == m_endItem)
1237             return m_startItem;
1238     }
1239 
1240     return nullptr;
1241 }
1242 
isStartItem(const ConnectableItem * item) const1243 bool TransitionItem::isStartItem(const ConnectableItem *item) const
1244 {
1245     return m_startItem == item;
1246 }
1247 
isEndItem(const ConnectableItem * item) const1248 bool TransitionItem::isEndItem(const ConnectableItem *item) const
1249 {
1250     return m_endItem == item;
1251 }
1252 
hasStartItem() const1253 bool TransitionItem::hasStartItem() const
1254 {
1255     return m_startItem != nullptr;
1256 }
1257 
hasEndItem() const1258 bool TransitionItem::hasEndItem() const
1259 {
1260     return m_endItem != nullptr;
1261 }
1262 
updateZValue()1263 void TransitionItem::updateZValue()
1264 {
1265     setZValue(qMax(m_startItem ? (m_startItem->zValue() + 1) : 1, m_endItem ? (m_endItem->zValue() + 1) : 1));
1266 }
1267 
textWidth() const1268 qreal TransitionItem::textWidth() const
1269 {
1270     return m_eventTagItem->boundingRect().width();
1271 }
1272 
wholeBoundingRect() const1273 QRectF TransitionItem::wholeBoundingRect() const
1274 {
1275     return boundingRect().united(m_eventTagItem->sceneBoundingRect());
1276 }
1277