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 "connectableitem.h"
27 #include "cornergrabberitem.h"
28 #include "graphicsscene.h"
29 #include "highlightitem.h"
30 #include "quicktransitionitem.h"
31 #include "sceneutils.h"
32 #include "scxmleditorconstants.h"
33 #include "serializer.h"
34 #include "stateitem.h"
35 
36 #include <QDebug>
37 #include <QPainter>
38 #include <QPainterPath>
39 #include <QPen>
40 #include <QStringList>
41 #include <QUndoStack>
42 
43 using namespace ScxmlEditor::PluginInterface;
44 
ConnectableItem(const QPointF & p,BaseItem * parent)45 ConnectableItem::ConnectableItem(const QPointF &p, BaseItem *parent)
46     : BaseItem(parent)
47 {
48     setFlag(QGraphicsItem::ItemIsMovable, true);
49     setFlag(QGraphicsItem::ItemIsSelectable, true);
50     setFlag(QGraphicsItem::ItemSendsGeometryChanges, true);
51     setFlag(QGraphicsItem::ItemSendsScenePositionChanges, true);
52     setAcceptDrops(true);
53 
54     m_selectedPen.setStyle(Qt::DotLine);
55     m_selectedPen.setColor(QColor(0x44, 0x44, 0xed));
56     m_selectedPen.setCosmetic(true);
57     m_releasedFromParentBrush = QBrush(QColor(0x98, 0x98, 0x98));
58 
59     setPos(p);
60     connect(this, &ConnectableItem::geometryChanged, this, &ConnectableItem::updateCornerPositions);
61 }
62 
~ConnectableItem()63 ConnectableItem::~ConnectableItem()
64 {
65     setBlockUpdates(true);
66 
67     foreach (ConnectableItem *item, m_overlappedItems) {
68         item->removeOverlappingItem(this);
69     }
70     m_overlappedItems.clear();
71 
72     foreach (TransitionItem *transition, m_outputTransitions) {
73         transition->disconnectItem(this);
74     }
75     m_outputTransitions.clear();
76 
77     foreach (TransitionItem *transition, m_inputTransitions) {
78         transition->disconnectItem(this);
79     }
80     m_inputTransitions.clear();
81 
82     qDeleteAll(m_quickTransitions);
83     m_quickTransitions.clear();
84 }
85 
createCorners()86 void ConnectableItem::createCorners()
87 {
88     for (int i = 0; i < 8; ++i) {
89         Qt::CursorShape cur = Qt::SizeHorCursor;
90         switch (i) {
91         case 0:
92         case 4:
93             cur = Qt::SizeFDiagCursor;
94             break;
95         case 1:
96         case 5:
97             cur = Qt::SizeVerCursor;
98             break;
99         case 2:
100         case 6:
101             cur = Qt::SizeBDiagCursor;
102             break;
103         case 3:
104         case 7:
105             cur = Qt::SizeHorCursor;
106             break;
107         default:
108             break;
109         }
110         m_corners << new CornerGrabberItem(this, cur);
111     }
112 
113     qDeleteAll(m_quickTransitions);
114     m_quickTransitions.clear();
115     m_quickTransitions << new QuickTransitionItem(0, UnknownType, this);
116     m_quickTransitions << new QuickTransitionItem(1, StateType, this);
117     m_quickTransitions << new QuickTransitionItem(2, ParallelType, this);
118     m_quickTransitions << new QuickTransitionItem(3, HistoryType, this);
119     m_quickTransitions << new QuickTransitionItem(4, FinalStateType, this);
120 
121     updateCornerPositions();
122 }
123 
removeCorners()124 void ConnectableItem::removeCorners()
125 {
126     qDeleteAll(m_corners);
127     m_corners.clear();
128 
129     qDeleteAll(m_quickTransitions);
130     m_quickTransitions.clear();
131 }
132 
updateCornerPositions()133 void ConnectableItem::updateCornerPositions()
134 {
135     QRectF r = boundingRect();
136     if (m_corners.count() == 8) {
137         qreal cx = r.center().x();
138         qreal cy = r.center().y();
139         m_corners[0]->setPos(r.topLeft());
140         m_corners[1]->setPos(cx, r.top());
141         m_corners[2]->setPos(r.topRight());
142         m_corners[3]->setPos(r.right(), cy);
143         m_corners[4]->setPos(r.bottomRight());
144         m_corners[5]->setPos(cx, r.bottom());
145         m_corners[6]->setPos(r.bottomLeft());
146         m_corners[7]->setPos(r.left(), cy);
147     }
148 
149     for (int i = 0; i < m_quickTransitions.count(); ++i) {
150         m_quickTransitions[i]->setPos(r.topLeft());
151         m_quickTransitions[i]->setVisible(!m_releasedFromParent && canStartTransition(m_quickTransitions[i]->connectionType()));
152     }
153     updateShadowClipRegion();
154 }
155 
sceneEventFilter(QGraphicsItem * watched,QEvent * event)156 bool ConnectableItem::sceneEventFilter(QGraphicsItem *watched, QEvent *event)
157 {
158     if (watched->type() == CornerGrabberType) {
159         auto c = qgraphicsitem_cast<CornerGrabberItem*>(watched);
160         auto mouseEvent = dynamic_cast<QGraphicsSceneMouseEvent*>(event);
161         if (!c || !mouseEvent)
162             return BaseItem::sceneEventFilter(watched, event);
163 
164         QRectF r = boundingRect();
165         if (event->type() == QEvent::GraphicsSceneMousePress) {
166             setOpacity(0.5);
167         } else if (event->type() == QEvent::GraphicsSceneMouseMove) {
168             QPointF pw = watched->mapToItem(this, mouseEvent->pos());
169             QRectF rMin;
170             if (type() >= StateType)
171                 rMin = qgraphicsitem_cast<StateItem*>(this)->childItemsBoundingRect();
172             if (rMin.isNull()) {
173                 rMin = QRectF(0, 0, m_minimumWidth, m_minimumHeight);
174                 rMin.moveCenter(r.center());
175             }
176 
177             switch (m_corners.indexOf(c)) {
178             case 0:
179                 r.setTopLeft(QPointF(qMin(pw.x(), rMin.left()), qMin(pw.y(), rMin.top())));
180                 break;
181             case 1:
182                 r.setTop(qMin(pw.y(), rMin.top()));
183                 break;
184             case 2:
185                 r.setTopRight(QPointF(qMax(pw.x(), rMin.right()), qMin(pw.y(), rMin.top())));
186                 break;
187             case 3:
188                 r.setRight(qMax(pw.x(), rMin.right()));
189                 break;
190             case 4:
191                 r.setBottomRight(QPointF(qMax(pw.x(), rMin.right()), qMax(pw.y(), rMin.bottom())));
192                 break;
193             case 5:
194                 r.setBottom(qMax(pw.y(), rMin.bottom()));
195                 break;
196             case 6:
197                 r.setBottomLeft(QPointF(qMin(pw.x(), rMin.left()), qMax(pw.y(), rMin.bottom())));
198                 break;
199             case 7:
200                 r.setLeft(qMin(pw.x(), rMin.left()));
201                 break;
202             default:
203                 break;
204             }
205 
206             setItemBoundingRect(r);
207             updateCornerPositions();
208             updateTransitions();
209 
210             return true;
211         } else if (event->type() == QEvent::GraphicsSceneMouseRelease) {
212             setOpacity(1.0);
213             updateUIProperties();
214             checkOverlapping();
215         }
216     } else if (watched->type() == QuickTransitionType) {
217         auto mouseEvent = dynamic_cast<QGraphicsSceneMouseEvent*>(event);
218         if (!mouseEvent)
219             return BaseItem::sceneEventFilter(watched, event);
220 
221         if (event->type() == QEvent::GraphicsSceneMousePress) {
222             m_newTransitionStartedPoint = mouseEvent->pos();
223             tag()->document()->undoStack()->beginMacro(tr("Add new state"));
224 
225             m_newTransition = new TransitionItem;
226             scene()->addItem(m_newTransition);
227 
228             ScxmlTag *newTag = nullptr;
229             if (tag()->tagType() == Initial || tag()->tagType() == History)
230                 newTag = new ScxmlTag(InitialTransition, tag()->document());
231             else
232                 newTag = new ScxmlTag(Transition, tag()->document());
233             newTag->setAttribute("type", "external");
234             newTag->setAttribute("event", tag()->document()->nextUniqueId("Transition"));
235             m_newTransition->init(newTag);
236 
237             tag()->document()->addTag(tag(), newTag);
238             m_newTransition->startTransitionFrom(this, watched->mapToScene(mouseEvent->pos()));
239             return true;
240         } else if (event->type() == QEvent::GraphicsSceneMouseMove) {
241             if (m_newTransition)
242                 m_newTransition->setEndPos(watched->mapToScene(mouseEvent->pos()));
243         } else if (event->type() == QEvent::GraphicsSceneMouseRelease) {
244             auto tra = qgraphicsitem_cast<QuickTransitionItem*>(watched);
245             if (!tra)
246                 return BaseItem::sceneEventFilter(watched, event);
247 
248             if (m_newTransition) {
249                 QLineF line(m_newTransitionStartedPoint, mouseEvent->pos());
250                 if (line.length() > 30) {
251                     m_newTransition->connectToTopItem(watched->mapToScene(mouseEvent->pos()), TransitionItem::End, tra->connectionType());
252                     m_newTransition = nullptr;
253                     setSelected(false);
254                     tag()->document()->undoStack()->endMacro();
255                 } else {
256                     m_newTransition->grabMouse(tra->connectionType());
257                     m_newTransition = nullptr;
258                     setSelected(false);
259                 }
260                 return true;
261             }
262         }
263     }
264 
265     return BaseItem::sceneEventFilter(watched, event);
266 }
267 
addOutputTransition(TransitionItem * transition)268 void ConnectableItem::addOutputTransition(TransitionItem *transition)
269 {
270     m_outputTransitions.append(transition);
271     transitionCountChanged();
272 }
273 
addInputTransition(TransitionItem * transition)274 void ConnectableItem::addInputTransition(TransitionItem *transition)
275 {
276     m_inputTransitions.append(transition);
277     transitionCountChanged();
278 }
279 
removeOutputTransition(TransitionItem * transition)280 void ConnectableItem::removeOutputTransition(TransitionItem *transition)
281 {
282     m_outputTransitions.removeAll(transition);
283     transitionCountChanged();
284 }
285 
removeInputTransition(TransitionItem * transition)286 void ConnectableItem::removeInputTransition(TransitionItem *transition)
287 {
288     m_inputTransitions.removeAll(transition);
289     transitionCountChanged();
290 }
291 
updateInputTransitions()292 void ConnectableItem::updateInputTransitions()
293 {
294     foreach (TransitionItem *transition, m_inputTransitions) {
295         transition->updateComponents();
296         transition->updateUIProperties();
297     }
298     transitionsChanged();
299 }
300 
updateOutputTransitions()301 void ConnectableItem::updateOutputTransitions()
302 {
303     foreach (TransitionItem *transition, m_outputTransitions) {
304         transition->updateComponents();
305         transition->updateUIProperties();
306     }
307     transitionsChanged();
308 }
309 
updateTransitions(bool allChildren)310 void ConnectableItem::updateTransitions(bool allChildren)
311 {
312     updateOutputTransitions();
313     updateInputTransitions();
314 
315     if (allChildren) {
316         foreach (QGraphicsItem *it, childItems()) {
317             auto item = static_cast<ConnectableItem*>(it);
318             if (item && item->type() >= InitialStateType)
319                 item->updateTransitions(allChildren);
320         }
321     }
322 }
323 
updateTransitionAttributes(bool allChildren)324 void ConnectableItem::updateTransitionAttributes(bool allChildren)
325 {
326     foreach (TransitionItem *transition, m_outputTransitions)
327         transition->updateTarget();
328 
329     foreach (TransitionItem *transition, m_inputTransitions)
330         transition->updateTarget();
331 
332     if (allChildren) {
333         foreach (QGraphicsItem *it, childItems()) {
334             auto item = static_cast<ConnectableItem*>(it);
335             if (item && item->type() >= InitialStateType)
336                 item->updateTransitionAttributes(allChildren);
337         }
338     }
339 }
340 
transitionsChanged()341 void ConnectableItem::transitionsChanged()
342 {
343 }
344 
transitionCountChanged()345 void ConnectableItem::transitionCountChanged()
346 {
347 }
348 
getInternalPosition(const TransitionItem * transition,TransitionItem::TransitionTargetType type) const349 QPointF ConnectableItem::getInternalPosition(const TransitionItem *transition, TransitionItem::TransitionTargetType type) const
350 {
351     const QRectF srect = sceneBoundingRect();
352 
353     int ind = 0;
354     if (type == TransitionItem::InternalNoTarget) {
355         foreach (TransitionItem *item, m_outputTransitions) {
356             if (item->targetType() == TransitionItem::InternalSameTarget)
357                 ind++;
358         }
359     }
360 
361     for (int i = 0; i < m_outputTransitions.count(); ++i) {
362         TransitionItem *item = m_outputTransitions[i];
363         if (item == transition)
364             break;
365         else if (item->targetType() == type)
366             ind++;
367     }
368 
369     return QPointF(srect.left() + 20, srect.top() + srect.height() * 0.06 + 40 + ind * 30);
370 }
371 
mousePressEvent(QGraphicsSceneMouseEvent * event)372 void ConnectableItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
373 {
374     // We want to pan scene when Shift is pressed -> that's why ignore mouse-event here
375     if (event->modifiers() & Qt::ShiftModifier) {
376         event->ignore();
377         return;
378     }
379 
380     BaseItem::mousePressEvent(event);
381 }
382 
mouseMoveEvent(QGraphicsSceneMouseEvent * event)383 void ConnectableItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
384 {
385     // We want to pan scene when Shift is pressed -> that's why ignore mouse-event here
386     if (event->modifiers() & Qt::ShiftModifier) {
387         event->ignore();
388         return;
389     }
390 
391     if (!m_moveMacroStarted) {
392         m_moveMacroStarted = true;
393         tag()->document()->undoStack()->beginMacro(tr("Move State"));
394     }
395 
396     //Restore old behavior if ctrl & alt modifiers are present
397     if (!m_releasedFromParent && !(event->modifiers() & Qt::AltModifier) && !(event->modifiers() & Qt::ControlModifier)) {
398         releaseFromParent();
399         foreach (QGraphicsItem *it, scene()->selectedItems()) {
400             if (it->type() >= InitialStateType && it != this) {
401                 qgraphicsitem_cast<ConnectableItem*>(it)->releaseFromParent();
402             }
403         }
404     } else
405         setOpacity(0.5);
406 
407     BaseItem::mouseMoveEvent(event);
408 }
409 
mouseReleaseEvent(QGraphicsSceneMouseEvent * event)410 void ConnectableItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
411 {
412     // We want to pan scene when Shift is pressed -> that's why ignore mouse-event here
413     if (event->modifiers() & Qt::ShiftModifier) {
414         event->ignore();
415         return;
416     }
417 
418     BaseItem::mouseReleaseEvent(event);
419 
420     if (m_releasedFromParent) {
421         // Try to find parent
422         QList<QGraphicsItem*> parentItems = scene()->items(event->scenePos());
423         BaseItem *parentItem = nullptr;
424         for (int i = 0; i < parentItems.count(); ++i) {
425             auto item = static_cast<BaseItem*>(parentItems[i]);
426             if (item && item != this && !item->isSelected()
427                 && item->type() >= StateType
428                 && SceneUtils::canDrop(item->type(), type())) {
429                 parentItem = item;
430                 break;
431             }
432         }
433         connectToParent(parentItem);
434         foreach (QGraphicsItem *it, scene()->selectedItems()) {
435             if (it->type() >= InitialStateType && it != this)
436                 qgraphicsitem_cast<ConnectableItem*>(it)->connectToParent(parentItem);
437         }
438     } else
439         setOpacity(1.0);
440 
441     if (m_moveMacroStarted) {
442         m_moveMacroStarted = false;
443         tag()->document()->undoStack()->endMacro();
444     }
445 
446     checkOverlapping();
447 }
448 
releaseFromParent()449 void ConnectableItem::releaseFromParent()
450 {
451     m_releasedFromParent = true;
452     setOpacity(0.5);
453     m_releasedIndex = tag()->index();
454     m_releasedParent = parentItem();
455     tag()->document()->changeParent(tag(), nullptr, !m_releasedParent ? m_releasedIndex : -1);
456     setZValue(503);
457 
458     for (int i = 0; i < m_quickTransitions.count(); ++i)
459         m_quickTransitions[i]->setVisible(false);
460     for (int i = 0; i < m_corners.count(); ++i)
461         m_corners[i]->setVisible(false);
462     update();
463 }
464 
connectToParent(BaseItem * parentItem)465 void ConnectableItem::connectToParent(BaseItem *parentItem)
466 {
467     for (int i = 0; i < m_quickTransitions.count(); ++i)
468         m_quickTransitions[i]->setVisible(canStartTransition(m_quickTransitions[i]->connectionType()));
469     for (int i = 0; i < m_corners.count(); ++i)
470         m_corners[i]->setVisible(true);
471 
472     tag()->document()->changeParent(tag(), parentItem ? parentItem->tag() : nullptr,
473                                     parentItem == m_releasedParent ? m_releasedIndex : -1);
474 
475     setZValue(0);
476     m_releasedIndex = -1;
477     m_releasedParent = nullptr;
478     m_releasedFromParent = false;
479     setOpacity(1.0);
480 }
481 
itemChange(GraphicsItemChange change,const QVariant & value)482 QVariant ConnectableItem::itemChange(GraphicsItemChange change, const QVariant &value)
483 {
484     switch (change) {
485     case ItemSelectedHasChanged: {
486         if (value.toBool()) {
487             createCorners();
488             SceneUtils::moveTop(this, static_cast<GraphicsScene*>(scene()));
489         } else
490             removeCorners();
491         break;
492     }
493     case ItemParentHasChanged:
494         updateTransitions(true);
495         updateTransitionAttributes(true);
496         Q_FALLTHROUGH();
497     case ItemPositionHasChanged:
498         if (!m_releasedFromParent && !blockUpdates())
499             checkParentBoundingRect();
500         break;
501     case ItemScenePositionHasChanged:
502         updateTransitions();
503         if (m_highlighItem)
504             m_highlighItem->advance(1);
505         break;
506     default:
507         break;
508     }
509 
510     return BaseItem::itemChange(change, value);
511 }
512 
getOpacity()513 qreal ConnectableItem::getOpacity()
514 {
515     if (opacity() < 1.0)
516         return opacity();
517 
518     if (overlapping())
519         return 0.5;
520 
521     if (parentBaseItem())
522         if (parentBaseItem()->type() >= InitialStateType)
523             return static_cast<ConnectableItem*>(parentBaseItem())->getOpacity();
524 
525     return 1;
526 }
527 
updateShadowClipRegion()528 void ConnectableItem::updateShadowClipRegion()
529 {
530     QPainterPath br, sr;
531     //StateItem Background rounded rectangle
532     br.addRoundedRect(boundingRect().adjusted(5, 5, -5, -5), 10, 10);
533     //Shadow rounded rectangle
534     sr.addRoundedRect(boundingRect().adjusted(10, 10, 0, 0), 10, 10);
535     //Clippath is subtract
536     m_shadowClipPath = sr - br;
537 }
538 
paint(QPainter * painter,const QStyleOptionGraphicsItem * option,QWidget * widget)539 void ConnectableItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
540 {
541     Q_UNUSED(option)
542     Q_UNUSED(widget)
543 
544     painter->save();
545     painter->setRenderHint(QPainter::Antialiasing, true);
546     painter->setOpacity(getOpacity());
547 
548     if (m_releasedFromParent) {
549         painter->setPen(Qt::NoPen);
550         painter->setBrush(m_releasedFromParentBrush);
551         painter->setClipping(true);
552         painter->setClipPath(m_shadowClipPath);
553         //Since the form is already cliped just draw a rectangle
554         painter->drawRect(boundingRect().adjusted(10, 10, 0, 0));
555         painter->setClipping(false);
556     }
557 
558     if (isSelected()) {
559         painter->setPen(m_selectedPen);
560         painter->setBrush(Qt::NoBrush);
561         painter->drawRect(boundingRect());
562     }
563 
564     painter->restore();
565 }
566 
updateUIProperties()567 void ConnectableItem::updateUIProperties()
568 {
569     if (tag() && isActiveScene()) {
570         Serializer s;
571         s.append(pos());
572         s.append(boundingRect());
573         setEditorInfo(Constants::C_SCXML_EDITORINFO_GEOMETRY, s.data());
574         s.clear();
575         s.append(scenePos());
576         s.append(sceneBoundingRect());
577         setEditorInfo("scenegeometry", s.data());
578     }
579 }
580 
updateAttributes()581 void ConnectableItem::updateAttributes()
582 {
583     BaseItem::updateAttributes();
584 
585     foreach (TransitionItem *transition, m_inputTransitions) {
586         if (transition->isEndItem(this))
587             transition->setTagValue("target", itemId());
588     }
589     updateInputTransitions();
590 
591     update();
592 }
593 
updateEditorInfo(bool allChildren)594 void ConnectableItem::updateEditorInfo(bool allChildren)
595 {
596     BaseItem::updateEditorInfo(allChildren);
597     updateTransitions();
598 }
599 
moveStateBy(qreal dx,qreal dy)600 void ConnectableItem::moveStateBy(qreal dx, qreal dy)
601 {
602     moveBy(dx, dy);
603     updateUIProperties();
604     updateTransitions();
605 }
606 
setHighlight(bool hl)607 void ConnectableItem::setHighlight(bool hl)
608 {
609     BaseItem::setHighlight(hl);
610     if (highlight()) {
611         if (!m_highlighItem) {
612             m_highlighItem = new HighlightItem(this);
613             scene()->addItem(m_highlighItem);
614         }
615     } else {
616         delete m_highlighItem;
617         m_highlighItem = nullptr;
618     }
619 
620     if (m_highlighItem)
621         m_highlighItem->advance(0);
622 }
623 
readUISpecifiedProperties(const ScxmlTag * tag)624 void ConnectableItem::readUISpecifiedProperties(const ScxmlTag *tag)
625 {
626     if (tag) {
627         QString data = editorInfo(Constants::C_SCXML_EDITORINFO_GEOMETRY);
628         if (!data.isEmpty()) {
629             QPointF p(0, 0);
630             QRectF r(-60, 50, 120, 100);
631 
632             Serializer s;
633             s.setData(data);
634             s.read(p);
635             s.read(r);
636 
637             setItemBoundingRect(r);
638             setPos(p);
639         }
640     }
641 }
642 
addTransitions(const ScxmlTag * tag)643 void ConnectableItem::addTransitions(const ScxmlTag *tag)
644 {
645     if (scene()) {
646         for (int i = 0; i < tag->childCount(); ++i) {
647             ScxmlTag *child = tag->child(i);
648             if (child->tagType() == Transition || child->tagType() == InitialTransition) {
649                 auto transition = new TransitionItem;
650                 scene()->addItem(transition);
651                 transition->setStartItem(this);
652                 transition->init(child);
653             }
654         }
655     }
656 }
657 
init(ScxmlTag * tag,BaseItem * parentItem,bool initChildren,bool)658 void ConnectableItem::init(ScxmlTag *tag, BaseItem *parentItem, bool initChildren, bool /*blockUpdates*/)
659 {
660     BaseItem::init(tag, parentItem);
661     if (initChildren)
662         addTransitions(tag);
663 }
664 
setMinimumWidth(int width)665 void ConnectableItem::setMinimumWidth(int width)
666 {
667     m_minimumWidth = width;
668     QRectF r = boundingRect();
669     if (r.width() < width) {
670         r.setWidth(width);
671         setItemBoundingRect(r);
672     }
673 }
674 
setMinimumHeight(int height)675 void ConnectableItem::setMinimumHeight(int height)
676 {
677     m_minimumHeight = height;
678     QRectF r = boundingRect();
679     if (r.height() < height) {
680         r.setHeight(height);
681         setItemBoundingRect(r);
682     }
683 }
684 
finalizeCreation()685 void ConnectableItem::finalizeCreation()
686 {
687     bool old = blockUpdates();
688     setBlockUpdates(true);
689 
690     updateAttributes();
691     updateEditorInfo();
692     updateUIProperties();
693     checkInitial(true);
694 
695     if (!old)
696         setBlockUpdates(false);
697 }
698 
hasInputTransitions(const ConnectableItem * parentItem,bool checkChildren) const699 bool ConnectableItem::hasInputTransitions(const ConnectableItem *parentItem, bool checkChildren) const
700 {
701     foreach (const TransitionItem *it, m_inputTransitions) {
702         if (!SceneUtils::isChild(parentItem, it->connectedItem(this)))
703             return true;
704     }
705 
706     if (checkChildren) {
707         foreach (QGraphicsItem *it, childItems()) {
708             if (it->type() >= InitialStateType) {
709                 auto item = qgraphicsitem_cast<ConnectableItem*>(it);
710                 if (item && item->hasInputTransitions(parentItem, checkChildren))
711                     return true;
712             }
713         }
714     }
715 
716     return false;
717 }
718 
hasOutputTransitions(const ConnectableItem * parentItem,bool checkChildren) const719 bool ConnectableItem::hasOutputTransitions(const ConnectableItem *parentItem, bool checkChildren) const
720 {
721     foreach (TransitionItem *it, m_outputTransitions) {
722         if (!SceneUtils::isChild(parentItem, it->connectedItem(this)))
723             return true;
724     }
725 
726     if (checkChildren) {
727         foreach (QGraphicsItem *it, childItems()) {
728             if (it->type() >= InitialStateType) {
729                 auto item = qgraphicsitem_cast<ConnectableItem*>(it);
730                 if (item && item->hasOutputTransitions(parentItem, checkChildren))
731                     return true;
732             }
733         }
734     }
735 
736     return false;
737 }
738 
addOverlappingItem(ConnectableItem * item)739 void ConnectableItem::addOverlappingItem(ConnectableItem *item)
740 {
741     if (!m_overlappedItems.contains(item))
742         m_overlappedItems.append(item);
743 
744     setOverlapping(!m_overlappedItems.isEmpty());
745 }
746 
removeOverlappingItem(ConnectableItem * item)747 void ConnectableItem::removeOverlappingItem(ConnectableItem *item)
748 {
749     if (m_overlappedItems.contains(item))
750         m_overlappedItems.removeAll(item);
751 
752     setOverlapping(!m_overlappedItems.isEmpty());
753 }
754 
checkOverlapping()755 void ConnectableItem::checkOverlapping()
756 {
757     QVector<ConnectableItem*> overlappedItems;
758     foreach (QGraphicsItem *it, collidingItems()) {
759         if (it->type() >= InitialStateType && it->parentItem() == parentItem()) {
760             overlappedItems << qgraphicsitem_cast<ConnectableItem*>(it);
761         }
762     }
763 
764     // Remove unnecessary items
765     for (int i = m_overlappedItems.count(); i--;) {
766         if (!overlappedItems.contains(m_overlappedItems[i])) {
767             m_overlappedItems[i]->removeOverlappingItem(this);
768             m_overlappedItems.removeAt(i);
769         }
770     }
771 
772     // Add new overlapped items
773     foreach (ConnectableItem *it, overlappedItems) {
774         if (!m_overlappedItems.contains(it)) {
775             m_overlappedItems << it;
776             it->addOverlappingItem(this);
777         }
778     }
779 
780     setOverlapping(!m_overlappedItems.isEmpty());
781 }
782 
canStartTransition(ItemType type) const783 bool ConnectableItem::canStartTransition(ItemType type) const
784 {
785     Q_UNUSED(type)
786     return true;
787 }
788 
outputTransitions() const789 QVector<TransitionItem*> ConnectableItem::outputTransitions() const
790 {
791     return m_outputTransitions;
792 }
793 
inputTransitions() const794 QVector<TransitionItem*> ConnectableItem::inputTransitions() const
795 {
796     return m_inputTransitions;
797 }
798 
transitionCount() const799 int ConnectableItem::transitionCount() const
800 {
801     return m_outputTransitions.count() + m_inputTransitions.count();
802 }
803 
outputTransitionCount() const804 int ConnectableItem::outputTransitionCount() const
805 {
806     return m_outputTransitions.count();
807 }
808 
inputTransitionCount() const809 int ConnectableItem::inputTransitionCount() const
810 {
811     return m_inputTransitions.count();
812 }
813