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