1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 Jochen Becher
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 "diagramscenemodel.h"
27 
28 #include "diagramgraphicsscene.h"
29 #include "diagramsceneconstants.h"
30 #include "diagramscenemodelitemvisitors.h"
31 #include "latchcontroller.h"
32 #include "capabilities/moveable.h"
33 #include "capabilities/resizable.h"
34 #include "capabilities/selectable.h"
35 #include "capabilities/editable.h"
36 
37 #include "qmt/diagram/dobject.h"
38 #include "qmt/diagram/drelation.h"
39 #include "qmt/diagram/dswimlane.h"
40 #include "qmt/diagram_controller/diagramcontroller.h"
41 #include "qmt/diagram_controller/dselection.h"
42 #include "qmt/diagram_scene/items/objectitem.h"
43 #include "qmt/diagram_scene/items/swimlaneitem.h"
44 #include "qmt/model/mdiagram.h"
45 #include "qmt/model/mobject.h"
46 #include "qmt/model/mpackage.h"
47 #include "qmt/model_controller/modelcontroller.h"
48 #include "qmt/stereotype/stereotypecontroller.h"
49 #include "qmt/style/stylecontroller.h"
50 #include "qmt/tasks/diagramscenecontroller.h"
51 #include "qmt/tasks/ielementtasks.h"
52 
53 #include <utils/algorithm.h>
54 
55 #include <QSet>
56 #include <QGraphicsItem>
57 #include <QGraphicsSceneMouseEvent>
58 #include <QGraphicsView>
59 #include <QApplication>
60 #include <QClipboard>
61 #include <QMimeData>
62 
63 #include <QBuffer>
64 #include <QPdfWriter>
65 #include <QFile>
66 
67 #ifndef QT_NO_SVG
68 #include <QtSvg/QSvgGenerator>
69 #endif
70 
71 namespace qmt {
72 
73 class DiagramSceneModel::OriginItem : public QGraphicsItem
74 {
75 public:
OriginItem(QGraphicsItem * parent=nullptr)76     explicit OriginItem(QGraphicsItem *parent = nullptr)
77         : QGraphicsItem(parent)
78     {
79     }
80 
boundingRect() const81     QRectF boundingRect() const final
82     {
83         return QRectF(0.0, 0.0, 20.0, 20.0);
84     }
85 
paint(QPainter * painter,const QStyleOptionGraphicsItem * option,QWidget * widget)86     void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) final
87     {
88         Q_UNUSED(option)
89         Q_UNUSED(widget)
90 
91         QPen pen(QBrush(Qt::lightGray), 1.0, Qt::DotLine);
92         painter->setPen(pen);
93         painter->drawLine(QLineF(0.0, 0.0, 20.0, 0.0));
94         painter->drawLine(QLineF(0.0, 0.0, 0.0, 20.0));
95     }
96 };
97 
98 class DiagramSceneModel::SelectionStatus {
99 public:
100     QSet<QGraphicsItem *> m_selectedItems;
101     QSet<QGraphicsItem *> m_secondarySelectedItems;
102     QGraphicsItem *m_focusItem = nullptr;
103     IEditable *m_editItem = nullptr;
104     bool m_exportSelectedElements = false;
105     QRectF m_sceneBoundingRect;
106 };
107 
DiagramSceneModel(QObject * parent)108 DiagramSceneModel::DiagramSceneModel(QObject *parent)
109     : QObject(parent),
110       m_graphicsScene(new DiagramGraphicsScene(this)),
111       m_latchController(new LatchController(this)),
112       m_originItem(new OriginItem())
113 {
114     m_latchController->setDiagramSceneModel(this);
115     connect(m_graphicsScene, &QGraphicsScene::selectionChanged,
116             this, &DiagramSceneModel::onSelectionChanged);
117 
118     // add one item at origin to force scene rect to include origin always
119     m_graphicsScene->addItem(m_originItem);
120 
121     m_latchController->addToGraphicsScene(m_graphicsScene);
122 }
123 
~DiagramSceneModel()124 DiagramSceneModel::~DiagramSceneModel()
125 {
126     QMT_CHECK(m_busyState == NotBusy);
127     m_latchController->removeFromGraphicsScene(m_graphicsScene);
128     disconnect();
129     if (m_diagramController)
130         disconnect(m_diagramController, nullptr, this, nullptr);
131     m_graphicsScene->deleteLater();
132 }
133 
setDiagramController(DiagramController * diagramController)134 void DiagramSceneModel::setDiagramController(DiagramController *diagramController)
135 {
136     if (m_diagramController == diagramController)
137         return;
138     if (m_diagramController) {
139         disconnect(m_diagramController, nullptr, this, nullptr);
140         m_diagramController = nullptr;
141     }
142     m_diagramController = diagramController;
143     if (diagramController) {
144         connect(m_diagramController, &DiagramController::beginResetAllDiagrams,
145                 this, &DiagramSceneModel::onBeginResetAllDiagrams);
146         connect(m_diagramController, &DiagramController::endResetAllDiagrams,
147                 this, &DiagramSceneModel::onEndResetAllDiagrams);
148         connect(m_diagramController, &DiagramController::beginResetDiagram,
149                 this, &DiagramSceneModel::onBeginResetDiagram);
150         connect(m_diagramController, &DiagramController::endResetDiagram,
151                 this, &DiagramSceneModel::onEndResetDiagram);
152         connect(m_diagramController, &DiagramController::beginUpdateElement,
153                 this, &DiagramSceneModel::onBeginUpdateElement);
154         connect(m_diagramController, &DiagramController::endUpdateElement,
155                 this, &DiagramSceneModel::onEndUpdateElement);
156         connect(m_diagramController, &DiagramController::beginInsertElement,
157                 this, &DiagramSceneModel::onBeginInsertElement);
158         connect(m_diagramController, &DiagramController::endInsertElement,
159                 this, &DiagramSceneModel::onEndInsertElement);
160         connect(m_diagramController, &DiagramController::beginRemoveElement,
161                 this, &DiagramSceneModel::onBeginRemoveElement);
162         connect(m_diagramController, &DiagramController::endRemoveElement,
163                 this, &DiagramSceneModel::onEndRemoveElement);
164     }
165 }
166 
setDiagramSceneController(DiagramSceneController * diagramSceneController)167 void DiagramSceneModel::setDiagramSceneController(DiagramSceneController *diagramSceneController)
168 {
169     m_diagramSceneController = diagramSceneController;
170 }
171 
setStyleController(StyleController * styleController)172 void DiagramSceneModel::setStyleController(StyleController *styleController)
173 {
174     m_styleController = styleController;
175 }
176 
setStereotypeController(StereotypeController * stereotypeController)177 void DiagramSceneModel::setStereotypeController(StereotypeController *stereotypeController)
178 {
179     m_stereotypeController = stereotypeController;
180 }
181 
setDiagram(MDiagram * diagram)182 void DiagramSceneModel::setDiagram(MDiagram *diagram)
183 {
184     if (m_diagram != diagram) {
185         onBeginResetDiagram(diagram);
186         m_diagram = diagram;
187         onEndResetDiagram(diagram);
188     }
189 }
190 
graphicsScene() const191 QGraphicsScene *DiagramSceneModel::graphicsScene() const
192 {
193     return m_graphicsScene;
194 }
195 
sceneRect() const196 QRectF DiagramSceneModel::sceneRect() const
197 {
198     return m_sceneRect;
199 }
200 
hasSelection() const201 bool DiagramSceneModel::hasSelection() const
202 {
203     return !m_graphicsScene->selectedItems().isEmpty();
204 }
205 
hasMultiObjectsSelection() const206 bool DiagramSceneModel::hasMultiObjectsSelection() const
207 {
208     int count = 0;
209     foreach (QGraphicsItem *item, m_graphicsScene->selectedItems()) {
210         DElement *element = m_itemToElementMap.value(item);
211         QMT_CHECK(element);
212         if (dynamic_cast<DObject *>(element)) {
213             ++count;
214             if (count > 1)
215                 return true;
216         }
217     }
218     return false;
219 }
220 
selectedElements() const221 DSelection DiagramSceneModel::selectedElements() const
222 {
223     DSelection selection;
224     foreach (QGraphicsItem *item, m_graphicsScene->selectedItems()) {
225         DElement *element = m_itemToElementMap.value(item);
226         QMT_ASSERT(element, return selection);
227         selection.append(element->uid(), m_diagram->uid());
228     }
229     return selection;
230 }
231 
findTopmostElement(const QPointF & scenePos) const232 DElement *DiagramSceneModel::findTopmostElement(const QPointF &scenePos) const
233 {
234     // fetch affected items from scene in correct drawing order to find topmost element
235     QList<QGraphicsItem *> items = m_graphicsScene->items(scenePos);
236     foreach (QGraphicsItem *item, items) {
237         if (m_graphicsItems.contains(item))
238             return m_itemToElementMap.value(item);
239     }
240     return nullptr;
241 }
242 
findTopmostObject(const QPointF & scenePos) const243 DObject *DiagramSceneModel::findTopmostObject(const QPointF &scenePos) const
244 {
245     ObjectItem *item = findTopmostObjectItem(scenePos);
246     if (!item)
247         return nullptr;
248     return item->object();
249 }
250 
findTopmostObjectItem(const QPointF & scenePos) const251 ObjectItem *DiagramSceneModel::findTopmostObjectItem(const QPointF &scenePos) const
252 {
253     // fetch affected items from scene in correct drawing order to find topmost element
254     const QList<QGraphicsItem *> items = m_graphicsScene->items(scenePos);
255     for (QGraphicsItem *item : qAsConst(items)) {
256         if (m_graphicsItems.contains(item)) {
257             DObject *object = dynamic_cast<DObject *>(m_itemToElementMap.value(item));
258             if (object)
259                 return dynamic_cast<ObjectItem *>(item);
260         }
261     }
262     return nullptr;
263 }
264 
graphicsItem(DElement * element) const265 QGraphicsItem *DiagramSceneModel::graphicsItem(DElement *element) const
266 {
267     return m_elementToItemMap.value(element);
268 }
269 
graphicsItem(const Uid & uid) const270 QGraphicsItem *DiagramSceneModel::graphicsItem(const Uid &uid) const
271 {
272     return m_elementToItemMap.value(m_diagramController->findElement(uid, m_diagram));
273 }
274 
isSelectedItem(QGraphicsItem * item) const275 bool DiagramSceneModel::isSelectedItem(QGraphicsItem *item) const
276 {
277     return m_selectedItems.contains(item);
278 }
279 
element(QGraphicsItem * item) const280 DElement *DiagramSceneModel::element(QGraphicsItem *item) const
281 {
282     return m_itemToElementMap.value(item);
283 }
284 
isElementEditable(const DElement * element) const285 bool DiagramSceneModel::isElementEditable(const DElement *element) const
286 {
287     auto editable = dynamic_cast<IEditable *>(m_elementToItemMap.value(element));
288     return editable && editable->isEditable();
289 }
290 
isInFrontOf(const QGraphicsItem * frontItem,const QGraphicsItem * backItem)291 bool DiagramSceneModel::isInFrontOf(const QGraphicsItem *frontItem, const QGraphicsItem *backItem)
292 {
293     QMT_ASSERT(frontItem, return false);
294     QMT_ASSERT(backItem, return false);
295 
296     // shortcut for usual case of root items
297     if (!frontItem->parentItem() && !backItem->parentItem()) {
298         foreach (const QGraphicsItem *item, m_graphicsScene->items()) {
299             if (item == frontItem)
300                 return true;
301             else if (item == backItem)
302                 return false;
303         }
304         QMT_CHECK(false);
305         return false;
306     }
307 
308     // collect all anchestors of front item
309     QList<const QGraphicsItem *> frontStack;
310     const QGraphicsItem *iterator = frontItem;
311     while (iterator) {
312         frontStack.append(iterator);
313         iterator = iterator->parentItem();
314     }
315 
316     // collect all anchestors of back item
317     QList<const QGraphicsItem *> backStack;
318     iterator = backItem;
319     while (iterator) {
320         backStack.append(iterator);
321         iterator = iterator->parentItem();
322     }
323 
324     // search lowest common anchestor
325     int frontIndex = frontStack.size() - 1;
326     int backIndex = backStack.size() - 1;
327     while (frontIndex >= 0 && backIndex >= 0 && frontStack.at(frontIndex) == backStack.at(backIndex)) {
328         --frontIndex;
329         --backIndex;
330     }
331 
332     if (frontIndex < 0 && backIndex < 0) {
333         QMT_CHECK(frontItem == backItem);
334         return false;
335     } else if (frontIndex < 0) {
336         // front item is higher in hierarchy and thus behind back item
337         return false;
338     } else if (backIndex < 0) {
339         // back item is higher in hierarchy and thus in behind front item
340         return true;
341     } else {
342         frontItem = frontStack.at(frontIndex);
343         backItem = backStack.at(backIndex);
344         QMT_CHECK(frontItem != backItem);
345 
346         if (frontItem->zValue() != backItem->zValue()) {
347             return frontItem->zValue() > backItem->zValue();
348         } else {
349             QList<QGraphicsItem *> children;
350             if (frontIndex + 1 < frontStack.size())
351                 children = frontStack.at(frontIndex + 1)->childItems();
352             else
353                 children = m_graphicsScene->items(Qt::AscendingOrder);
354             foreach (const QGraphicsItem *item, children) {
355                 if (item == frontItem)
356                     return false;
357                 else if (item == backItem)
358                     return true;
359             }
360             QMT_CHECK(false);
361             return false;
362         }
363     }
364 }
365 
selectAllElements()366 void DiagramSceneModel::selectAllElements()
367 {
368     foreach (QGraphicsItem *item, m_graphicsItems)
369         item->setSelected(true);
370 }
371 
selectElement(DElement * element)372 void DiagramSceneModel::selectElement(DElement *element)
373 {
374     QGraphicsItem *selectItem = m_elementToItemMap.value(element);
375     foreach (QGraphicsItem *item, m_selectedItems) {
376         if (item != selectItem)
377             item->setSelected(false);
378     }
379     if (selectItem)
380         selectItem->setSelected(true);
381 }
382 
editElement(DElement * element)383 void DiagramSceneModel::editElement(DElement *element)
384 {
385     auto editable = dynamic_cast<IEditable *>(m_elementToItemMap.value(element));
386     if (editable && editable->isEditable())
387         editable->edit();
388 }
389 
copyToClipboard()390 void DiagramSceneModel::copyToClipboard()
391 {
392     SelectionStatus status;
393     saveSelectionStatusBeforeExport(!(m_selectedItems.isEmpty() && m_secondarySelectedItems.isEmpty()), &status);
394 
395     auto mimeData = new QMimeData();
396     // Create the image with the size of the shrunk scene
397     const int scaleFactor = 1;
398     const int border = 5;
399     const int baseDpi = 75;
400     const int dotsPerMeter = 10000 * baseDpi / 254;
401     QSize imageSize = status.m_sceneBoundingRect.size().toSize();
402     imageSize += QSize(2 * border, 2 * border);
403     imageSize *= scaleFactor;
404     QImage image(imageSize, QImage::Format_ARGB32);
405     image.setDotsPerMeterX(dotsPerMeter * scaleFactor);
406     image.setDotsPerMeterY(dotsPerMeter * scaleFactor);
407     image.fill(Qt::white);
408     QPainter painter;
409     painter.begin(&image);
410     painter.setRenderHint(QPainter::Antialiasing);
411     m_graphicsScene->render(&painter,
412                             QRectF(border, border,
413                                    painter.device()->width() - 2 * border,
414                                    painter.device()->height() - 2 * border),
415                             status.m_sceneBoundingRect);
416     painter.end();
417     mimeData->setImageData(image);
418     QApplication::clipboard()->setMimeData(mimeData, QClipboard::Clipboard);
419 
420     restoreSelectedStatusAfterExport(status);
421 }
422 
exportImage(const QString & fileName,bool selectedElements)423 bool DiagramSceneModel::exportImage(const QString &fileName, bool selectedElements)
424 {
425     SelectionStatus status;
426     saveSelectionStatusBeforeExport(selectedElements, &status);
427 
428     // Create the image with the size of the shrunk scene
429     const int scaleFactor = 1;
430     const int border = 5;
431     const int baseDpi = 75;
432     const int dotsPerMeter = 10000 * baseDpi / 254;
433 
434     QSize imageSize = status.m_sceneBoundingRect.size().toSize();
435     imageSize += QSize(2 * border, 2 * border);
436     imageSize *= scaleFactor;
437 
438     QImage image(imageSize, QImage::Format_ARGB32);
439     image.setDotsPerMeterX(dotsPerMeter * scaleFactor);
440     image.setDotsPerMeterY(dotsPerMeter * scaleFactor);
441     image.fill(Qt::white);
442 
443     QPainter painter;
444     painter.begin(&image);
445     painter.setRenderHint(QPainter::Antialiasing);
446     m_graphicsScene->render(&painter,
447                             QRectF(border, border,
448                                    painter.device()->width() - 2 * border,
449                                    painter.device()->height() - 2 * border),
450                             status.m_sceneBoundingRect);
451     painter.end();
452 
453     bool success = image.save(fileName);
454 
455     restoreSelectedStatusAfterExport(status);
456 
457     return success;
458 }
459 
exportPdf(const QString & fileName,bool selectedElements)460 bool DiagramSceneModel::exportPdf(const QString &fileName, bool selectedElements)
461 {
462     SelectionStatus status;
463     saveSelectionStatusBeforeExport(selectedElements, &status);
464 
465     const double scaleFactor = 1.0;
466     const double border = 5;
467     const double baseDpi = 100;
468     const double dotsPerMm = 25.4 / baseDpi;
469 
470     QSizeF pageSize = status.m_sceneBoundingRect.size();
471     pageSize += QSizeF(2.0 * border, 2.0 * border);
472     pageSize *= scaleFactor;
473     pageSize *= dotsPerMm;
474 
475     QPdfWriter pdfWriter(fileName);
476     pdfWriter.setPageSize(QPageSize(pageSize, QPageSize::Millimeter));
477 
478     QPainter pdfPainter;
479     pdfPainter.begin(&pdfWriter);
480     m_graphicsScene->render(&pdfPainter,
481                             QRectF(border, border,
482                                    pdfPainter.device()->width() - 2 * border,
483                                    pdfPainter.device()->height() - 2 * border),
484                             status.m_sceneBoundingRect);
485     pdfPainter.end();
486 
487     restoreSelectedStatusAfterExport(status);
488 
489     return true;
490 }
491 
exportSvg(const QString & fileName,bool selectedElements)492 bool DiagramSceneModel::exportSvg(const QString &fileName, bool selectedElements)
493 {
494 #ifndef QT_NO_SVG
495     SelectionStatus status;
496     saveSelectionStatusBeforeExport(selectedElements, &status);
497 
498     const double border = 5;
499 
500     QSvgGenerator svgGenerator;
501     svgGenerator.setFileName(fileName);
502     QSize svgSceneSize = status.m_sceneBoundingRect.size().toSize();
503     svgGenerator.setSize(svgSceneSize);
504     svgGenerator.setViewBox(QRect(QPoint(0,0), svgSceneSize));
505     QPainter svgPainter;
506     svgPainter.begin(&svgGenerator);
507     svgPainter.setRenderHint(QPainter::Antialiasing);
508     m_graphicsScene->render(&svgPainter,
509                             QRectF(border, border,
510                                    svgPainter.device()->width() - 2 * border,
511                                    svgPainter.device()->height() - 2 * border),
512                             status.m_sceneBoundingRect);
513     svgPainter.end();
514 
515     restoreSelectedStatusAfterExport(status);
516 
517     return true;
518 #else // QT_NO_SVG
519     Q_UNUSED(fileName)
520     Q_UNUSED(selectedElements)
521     return false;
522 #endif // QT_NO_SVG
523 }
524 
selectItem(QGraphicsItem * item,bool multiSelect)525 void DiagramSceneModel::selectItem(QGraphicsItem *item, bool multiSelect)
526 {
527     if (!multiSelect) {
528         if (!item->isSelected()) {
529             foreach (QGraphicsItem *selectedItem, m_selectedItems) {
530                 if (selectedItem != item)
531                     selectedItem->setSelected(false);
532             }
533             item->setSelected(true);
534         }
535     } else {
536         item->setSelected(!item->isSelected());
537     }
538 }
539 
moveSelectedItems(QGraphicsItem * grabbedItem,const QPointF & delta)540 void DiagramSceneModel::moveSelectedItems(QGraphicsItem *grabbedItem, const QPointF &delta)
541 {
542     Q_UNUSED(grabbedItem)
543 
544     if (delta != QPointF(0.0, 0.0)) {
545         foreach (QGraphicsItem *item, m_selectedItems) {
546             if (auto moveable = dynamic_cast<IMoveable *>(item))
547                 moveable->moveDelta(delta);
548         }
549         foreach (QGraphicsItem *item, m_secondarySelectedItems) {
550             if (auto moveable = dynamic_cast<IMoveable *>(item))
551                 moveable->moveDelta(delta);
552         }
553     }
554 }
555 
alignSelectedItemsPositionOnRaster()556 void DiagramSceneModel::alignSelectedItemsPositionOnRaster()
557 {
558     foreach (QGraphicsItem *item, m_selectedItems) {
559         if (auto moveable = dynamic_cast<IMoveable *>(item))
560             moveable->alignItemPositionToRaster(RASTER_WIDTH, RASTER_HEIGHT);
561     }
562     foreach (QGraphicsItem *item, m_secondarySelectedItems) {
563         if (auto moveable = dynamic_cast<IMoveable *>(item))
564             moveable->alignItemPositionToRaster(RASTER_WIDTH, RASTER_HEIGHT);
565     }
566 }
567 
onDoubleClickedItem(QGraphicsItem * item)568 void DiagramSceneModel::onDoubleClickedItem(QGraphicsItem *item)
569 {
570     DElement *element = m_itemToElementMap.value(item);
571     if (item)
572         m_diagramSceneController->elementTasks()->openElement(element, m_diagram);
573 }
574 
collectCollidingObjectItems(const QGraphicsItem * item,CollidingMode collidingMode) const575 QList<QGraphicsItem *> DiagramSceneModel::collectCollidingObjectItems(const QGraphicsItem *item,
576                                                                       CollidingMode collidingMode) const
577 {
578     QList<QGraphicsItem *> collidingItems;
579 
580     auto resizable = dynamic_cast<const IResizable *>(item);
581     if (!resizable)
582         return collidingItems;
583     QRectF rect = resizable->rect();
584     rect.translate(resizable->pos());
585 
586     switch (collidingMode) {
587     case CollidingInnerItems:
588         foreach (QGraphicsItem *candidate, m_graphicsItems) {
589             if (auto candidateResizable = dynamic_cast<const IResizable *>(candidate)) {
590                 QRectF candidateRect = candidateResizable->rect();
591                 candidateRect.translate(candidateResizable->pos());
592                 if (candidateRect.left() >= rect.left() && candidateRect.right() <= rect.right()
593                         && candidateRect.top() >= rect.top() && candidateRect.bottom() <= rect.bottom()) {
594                     collidingItems.append(candidate);
595                 }
596             }
597         }
598         break;
599     case CollidingItems:
600         foreach (QGraphicsItem *candidate, m_graphicsItems) {
601             if (auto candidateResizable = dynamic_cast<const IResizable *>(candidate)) {
602                 QRectF candidateRect = candidateResizable->rect();
603                 candidateRect.translate(candidateResizable->pos());
604                 if (candidateRect.left() <= rect.right() && candidateRect.right() >= rect.left()
605                         && candidateRect.top() <= rect.bottom() && candidateRect.bottom() >= rect.top()) {
606                     collidingItems.append(candidate);
607                 }
608             }
609         }
610         break;
611     case CollidingOuterItems:
612         foreach (QGraphicsItem *candidate, m_graphicsItems) {
613             if (auto candidateResizable = dynamic_cast<const IResizable *>(candidate)) {
614                 QRectF candidateRect = candidateResizable->rect();
615                 candidateRect.translate(candidateResizable->pos());
616                 if (candidateRect.left() <= rect.left() && candidateRect.right() >= rect.right()
617                         && candidateRect.top() <= rect.top() && candidateRect.bottom() >= rect.bottom()) {
618                     collidingItems.append(candidate);
619                 }
620             }
621         }
622         break;
623     }
624     return collidingItems;
625 }
626 
sceneActivated()627 void DiagramSceneModel::sceneActivated()
628 {
629     emit diagramSceneActivated(m_diagram);
630 }
631 
keyPressEvent(QKeyEvent * event)632 void DiagramSceneModel::keyPressEvent(QKeyEvent *event)
633 {
634     m_latchController->keyPressEventLatching(event);
635 }
636 
keyReleaseEvent(QKeyEvent * event)637 void DiagramSceneModel::keyReleaseEvent(QKeyEvent *event)
638 {
639     m_latchController->keyReleaseEventLatching(event);
640 }
641 
mousePressEvent(QGraphicsSceneMouseEvent * event)642 void DiagramSceneModel::mousePressEvent(QGraphicsSceneMouseEvent *event)
643 {
644     updateFocusItem(Utils::toSet(m_graphicsScene->selectedItems()));
645     m_latchController->mousePressEventLatching(event);
646     mousePressEventReparenting(event);
647 }
648 
mousePressEventReparenting(QGraphicsSceneMouseEvent * event)649 void DiagramSceneModel::mousePressEventReparenting(QGraphicsSceneMouseEvent *event)
650 {
651     // TODO add keyboard event handler to change cursor also on modifier change without move
652     mouseMoveEventReparenting(event);
653 }
654 
mouseMoveEvent(QGraphicsSceneMouseEvent * event)655 void DiagramSceneModel::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
656 {
657     m_latchController->mouseMoveEventLatching(event);
658     mouseMoveEventReparenting(event);
659 }
660 
mouseMoveEventReparenting(QGraphicsSceneMouseEvent * event)661 void DiagramSceneModel::mouseMoveEventReparenting(QGraphicsSceneMouseEvent *event)
662 {
663     if (event->modifiers() & Qt::AltModifier) {
664         // TODO show move cursor only if elements can be moved to underlaying element
665         foreach (QGraphicsView *view, m_graphicsScene->views()) {
666             // TODO find a better cursor that signals "move to this package"
667             view->setCursor(QCursor(Qt::OpenHandCursor));
668         }
669     } else {
670         foreach (QGraphicsView *view, m_graphicsScene->views())
671             view->unsetCursor();
672     }
673 }
674 
mouseReleaseEvent(QGraphicsSceneMouseEvent * event)675 void DiagramSceneModel::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
676 {
677     m_latchController->mouseReleaseEventLatching(event);
678     mouseReleaseEventReparenting(event);
679 }
680 
mouseReleaseEventReparenting(QGraphicsSceneMouseEvent * event)681 void DiagramSceneModel::mouseReleaseEventReparenting(QGraphicsSceneMouseEvent *event)
682 {
683     if (event->modifiers() & Qt::AltModifier) {
684         ModelController *modelController = diagramController()->modelController();
685         MPackage *newOwner = nullptr;
686         QSet<QGraphicsItem *> selectedItemSet = Utils::toSet(m_graphicsScene->selectedItems());
687         QList<QGraphicsItem *> itemsUnderMouse = m_graphicsScene->items(event->scenePos());
688         foreach (QGraphicsItem *item, itemsUnderMouse) {
689             if (!selectedItemSet.contains(item)) {
690                 // the item may be any graphics item not matching to a DElement
691                 DElement *element = m_itemToElementMap.value(item);
692                 if (element && element->modelUid().isValid())
693                     newOwner = modelController->findElement<MPackage>(element->modelUid());
694             }
695             if (newOwner)
696                 break;
697         }
698         if (newOwner) {
699             foreach (QGraphicsItem *item, m_graphicsScene->selectedItems()) {
700                 DElement *element = m_itemToElementMap.value(item);
701                 QMT_ASSERT(element, return);
702                 if (element->modelUid().isValid()) {
703                     MObject *modelObject = modelController->findObject(element->modelUid());
704                     if (modelObject) {
705                         if (newOwner != modelObject->owner())
706                             modelController->moveObject(newOwner, modelObject);
707                     }
708                 }
709             }
710         }
711     }
712     foreach (QGraphicsView *view, m_graphicsScene->views())
713         view->unsetCursor();
714 }
715 
onBeginResetAllDiagrams()716 void DiagramSceneModel::onBeginResetAllDiagrams()
717 {
718     onBeginResetDiagram(m_diagram);
719 }
720 
onEndResetAllDiagrams()721 void DiagramSceneModel::onEndResetAllDiagrams()
722 {
723     onEndResetDiagram(m_diagram);
724 }
725 
onBeginResetDiagram(const MDiagram * diagram)726 void DiagramSceneModel::onBeginResetDiagram(const MDiagram *diagram)
727 {
728     QMT_CHECK(m_busyState == NotBusy);
729     m_busyState = ResetDiagram;
730     if (diagram == m_diagram)
731         clearGraphicsScene();
732 }
733 
onEndResetDiagram(const MDiagram * diagram)734 void DiagramSceneModel::onEndResetDiagram(const MDiagram *diagram)
735 {
736     QMT_CHECK(m_busyState == ResetDiagram);
737     if (diagram == m_diagram) {
738         QMT_CHECK(m_graphicsItems.size() == 0);
739         // create all items and update graphics item from element initially
740         foreach (DElement *element, diagram->diagramElements()) {
741             QGraphicsItem *item = createGraphicsItem(element);
742             m_graphicsItems.append(item);
743             updateGraphicsItem(item, element);
744         }
745         // invalidate scene
746         m_graphicsScene->invalidate();
747         // update graphics items again so every item gets a correct list of colliding items
748         foreach (DElement *element, diagram->diagramElements())
749             updateGraphicsItem(m_elementToItemMap.value(element), element);
750         recalcSceneRectSize();
751     }
752     m_busyState = NotBusy;
753 }
754 
onBeginUpdateElement(int row,const MDiagram * diagram)755 void DiagramSceneModel::onBeginUpdateElement(int row, const MDiagram *diagram)
756 {
757     Q_UNUSED(row)
758     Q_UNUSED(diagram)
759     QMT_CHECK(m_busyState == NotBusy);
760     m_busyState = UpdateElement;
761 
762 }
763 
onEndUpdateElement(int row,const MDiagram * diagram)764 void DiagramSceneModel::onEndUpdateElement(int row, const MDiagram *diagram)
765 {
766     QMT_CHECK(m_busyState == UpdateElement);
767     if (diagram == m_diagram) {
768         QGraphicsItem *item = m_graphicsItems.at(row);
769         updateGraphicsItem(item, diagram->diagramElements().at(row));
770         // TODO update all relations and their other end's (e.g. class name may have changed)
771         recalcSceneRectSize();
772     }
773     m_busyState = NotBusy;
774 }
775 
onBeginInsertElement(int row,const MDiagram * diagram)776 void DiagramSceneModel::onBeginInsertElement(int row, const MDiagram *diagram)
777 {
778     Q_UNUSED(row)
779     Q_UNUSED(diagram)
780     QMT_CHECK(m_busyState == NotBusy);
781     m_busyState = InsertElement;
782 }
783 
onEndInsertElement(int row,const MDiagram * diagram)784 void DiagramSceneModel::onEndInsertElement(int row, const MDiagram *diagram)
785 {
786     QMT_CHECK(m_busyState == InsertElement);
787     QGraphicsItem *item = nullptr;
788     if (diagram == m_diagram) {
789         DElement *element = diagram->diagramElements().at(row);
790         item = createGraphicsItem(element);
791         m_graphicsItems.insert(row, item);
792         updateGraphicsItem(item, element);
793         m_graphicsScene->invalidate();
794         updateGraphicsItem(item, element);
795         // if element is relation update ends
796         if (DRelation *dRelation = dynamic_cast<DRelation *>(element)) {
797             DElement *dEnd = m_diagramController->findElement(dRelation->endAUid(), diagram);
798             if (dEnd)
799                 updateGraphicsItem(graphicsItem(dEnd), dEnd);
800             dEnd = m_diagramController->findElement(dRelation->endBUid(), diagram);
801             if (dEnd)
802                 updateGraphicsItem(graphicsItem(dEnd), dEnd);
803         }
804         recalcSceneRectSize();
805     }
806     m_busyState = NotBusy;
807 }
808 
onBeginRemoveElement(int row,const MDiagram * diagram)809 void DiagramSceneModel::onBeginRemoveElement(int row, const MDiagram *diagram)
810 {
811     QMT_CHECK(m_busyState == NotBusy);
812     if (diagram == m_diagram) {
813         // if element is relation store end's uid
814         m_relationEndsUid.clear();
815         if (DRelation *relation = dynamic_cast<DRelation *>(diagram->diagramElements().at(row))) {
816             m_relationEndsUid.append(relation->endAUid());
817             m_relationEndsUid.append(relation->endBUid());
818         }
819         QGraphicsItem *item = m_graphicsItems.takeAt(row);
820         deleteGraphicsItem(item, diagram->diagramElements().at(row));
821         recalcSceneRectSize();
822     }
823     m_busyState = RemoveElement;
824 }
825 
onEndRemoveElement(int row,const MDiagram * diagram)826 void DiagramSceneModel::onEndRemoveElement(int row, const MDiagram *diagram)
827 {
828     Q_UNUSED(row)
829     Q_UNUSED(diagram)
830     QMT_CHECK(m_busyState == RemoveElement);
831     // update elements from store (see above)
832     for (const Uid &end_uid : qAsConst(m_relationEndsUid)) {
833         DElement *dEnd = m_diagramController->findElement(end_uid, diagram);
834         if (dEnd)
835             updateGraphicsItem(graphicsItem(dEnd), dEnd);
836     }
837     m_busyState = NotBusy;
838 }
839 
onSelectionChanged()840 void DiagramSceneModel::onSelectionChanged()
841 {
842     bool selectionChanged = false;
843 
844     // collect and update all primary selected items (selected by user)
845     QSet<QGraphicsItem *> newSelectedItems = Utils::toSet(m_graphicsScene->selectedItems());
846     updateFocusItem(newSelectedItems);
847     foreach (QGraphicsItem *item, m_selectedItems) {
848         if (!newSelectedItems.contains(item)) {
849             DElement *element = m_itemToElementMap.value(item);
850             updateGraphicsItem(item, element);
851             selectionChanged = true;
852         }
853     }
854     foreach (QGraphicsItem *item, newSelectedItems) {
855         if (!m_selectedItems.contains(item)) {
856             DElement *element = m_itemToElementMap.value(item);
857             updateGraphicsItem(item, element);
858             selectionChanged = true;
859         }
860     }
861     m_selectedItems = newSelectedItems;
862 
863     // collect and update all secondary selected items
864     QSet<QGraphicsItem *> newSecondarySelectedItems;
865 
866     // select all contained objects secondarily
867     foreach (QGraphicsItem *selectedItem, m_selectedItems) {
868         foreach (QGraphicsItem *item, collectCollidingObjectItems(selectedItem, CollidingInnerItems)) {
869             if (!item->isSelected() && dynamic_cast<ISelectable *>(item)
870                     && item->collidesWithItem(selectedItem, Qt::ContainsItemBoundingRect)
871                     && isInFrontOf(item, selectedItem)) {
872                 QMT_CHECK(!m_selectedItems.contains(item));
873                 newSecondarySelectedItems.insert(item);
874             }
875         }
876     }
877 
878     // select more items secondarily
879     for (QGraphicsItem *selectedItem : qAsConst(m_selectedItems)) {
880         if (auto selectable = dynamic_cast<ISelectable *>(selectedItem)) {
881             QRectF boundary = selectable->getSecondarySelectionBoundary();
882             if (!boundary.isEmpty()) {
883                 for (QGraphicsItem *item : qAsConst(m_graphicsItems)) {
884                     if (auto secondarySelectable = dynamic_cast<ISelectable *>(item)) {
885                         if (!item->isSelected() && !secondarySelectable->isSecondarySelected()) {
886                             secondarySelectable->setBoundarySelected(boundary, true);
887                             QMT_CHECK(!m_selectedItems.contains(item));
888                             QMT_CHECK(!m_secondarySelectedItems.contains(item));
889                             if (secondarySelectable->isSecondarySelected())
890                                 newSecondarySelectedItems.insert(item);
891                         }
892                     }
893                 }
894             }
895         }
896     }
897 
898 
899     // select all relations where both ends are primary or secondary selected
900     foreach (DElement *element, m_diagram->diagramElements()) {
901         auto relation = dynamic_cast<DRelation *>(element);
902         if (relation) {
903             QGraphicsItem *relationItem = m_elementToItemMap.value(relation);
904             QMT_ASSERT(relationItem, return);
905             DObject *endAObject = m_diagramController->findElement<DObject>(relation->endAUid(), m_diagram);
906             QMT_ASSERT(endAObject, return);
907             QGraphicsItem *endAItem = m_elementToItemMap.value(endAObject);
908             QMT_ASSERT(endAItem, return);
909             DObject *endBObject = m_diagramController->findElement<DObject>(relation->endBUid(), m_diagram);
910             QMT_ASSERT(endBObject, return);
911             QGraphicsItem *endBItem = m_elementToItemMap.value(endBObject);
912             QMT_ASSERT(endBItem, return);
913             if (relationItem && !relationItem->isSelected()
914                     && (m_selectedItems.contains(endAItem) || newSecondarySelectedItems.contains(endAItem))
915                     && (m_selectedItems.contains(endBItem) || newSecondarySelectedItems.contains(endBItem))) {
916                 QMT_CHECK(!m_selectedItems.contains(relationItem));
917                 newSecondarySelectedItems.insert(relationItem);
918             }
919         }
920     }
921 
922     foreach (QGraphicsItem *item, m_secondarySelectedItems) {
923         if (!newSecondarySelectedItems.contains(item)) {
924             auto selectable = dynamic_cast<ISelectable *>(item);
925             QMT_ASSERT(selectable, return);
926             selectable->setSecondarySelected(false);
927             selectionChanged = true;
928         }
929     }
930     foreach (QGraphicsItem *item, newSecondarySelectedItems) {
931         if (!m_secondarySelectedItems.contains(item)) {
932             auto selectable = dynamic_cast<ISelectable *>(item);
933             QMT_ASSERT(selectable, return);
934             selectable->setSecondarySelected(true);
935             selectionChanged = true;
936         }
937     }
938     m_secondarySelectedItems = newSecondarySelectedItems;
939 
940     QMT_CHECK((m_selectedItems & m_secondarySelectedItems).isEmpty());
941 
942     if (selectionChanged) {
943         m_diagramController->breakUndoChain();
944         emit selectionHasChanged(m_diagram);
945     }
946 }
947 
clearGraphicsScene()948 void DiagramSceneModel::clearGraphicsScene()
949 {
950     m_graphicsScene->clearSelection();
951     m_graphicsItems.clear();
952     m_itemToElementMap.clear();
953     m_elementToItemMap.clear();
954     m_selectedItems.clear();
955     m_secondarySelectedItems.clear();
956     m_focusItem = nullptr;
957     // save extra items from being deleted
958     removeExtraSceneItems();
959     m_graphicsScene->clear();
960     addExtraSceneItems();
961 }
962 
removeExtraSceneItems()963 void DiagramSceneModel::removeExtraSceneItems()
964 {
965     m_latchController->removeFromGraphicsScene(m_graphicsScene);
966     m_graphicsScene->removeItem(m_originItem);
967 }
968 
addExtraSceneItems()969 void DiagramSceneModel::addExtraSceneItems()
970 {
971     m_graphicsScene->addItem(m_originItem);
972     m_latchController->addToGraphicsScene(m_graphicsScene);
973 }
974 
saveSelectionStatusBeforeExport(bool exportSelectedElements,DiagramSceneModel::SelectionStatus * status)975 void DiagramSceneModel::saveSelectionStatusBeforeExport(bool exportSelectedElements, DiagramSceneModel::SelectionStatus *status)
976 {
977     status->m_selectedItems = m_selectedItems;
978     status->m_secondarySelectedItems = m_secondarySelectedItems;
979     status->m_focusItem = m_focusItem;
980     status->m_exportSelectedElements = exportSelectedElements;
981 
982     // Selections would also render to the clipboard
983     m_graphicsScene->clearSelection();
984     foreach (QGraphicsItem *item, m_graphicsItems) {
985         if (IEditable *editItem = dynamic_cast<IEditable *>(item)) {
986             if (editItem->isEditing()) {
987                 status->m_editItem = editItem;
988                 editItem->finishEdit();
989             }
990         }
991     }
992     removeExtraSceneItems();
993 
994     foreach (QGraphicsItem *item, m_graphicsItems) {
995         if (!exportSelectedElements
996                 || status->m_selectedItems.contains(item)
997                 || status->m_secondarySelectedItems.contains(item)) {
998             // TODO introduce interface for calculating export boundary
999             if (SwimlaneItem *swimlane = dynamic_cast<SwimlaneItem *>(item)) {
1000                 QRectF boundary = item->mapRectToScene(swimlane->boundingRect());
1001                 if (swimlane->swimlane()->isHorizontal()) {
1002                     boundary.setLeft(status->m_sceneBoundingRect.left());
1003                     boundary.setRight(status->m_sceneBoundingRect.right());
1004                 } else {
1005                     boundary.setTop(status->m_sceneBoundingRect.top());
1006                     boundary.setBottom(status->m_sceneBoundingRect.bottom());
1007                 }
1008                 status->m_sceneBoundingRect |= boundary;
1009             } else {
1010                 status->m_sceneBoundingRect |= item->mapRectToScene(item->boundingRect());
1011             }
1012         } else {
1013             item->hide();
1014         }
1015     }
1016 }
1017 
restoreSelectedStatusAfterExport(const DiagramSceneModel::SelectionStatus & status)1018 void DiagramSceneModel::restoreSelectedStatusAfterExport(const DiagramSceneModel::SelectionStatus &status)
1019 {
1020     if (status.m_exportSelectedElements) {
1021         // TODO once an annotation item had focus the call to show() will give it focus again. Bug in Qt?
1022         foreach (QGraphicsItem *item, m_graphicsItems)
1023             item->show();
1024     }
1025 
1026     addExtraSceneItems();
1027 
1028     foreach (QGraphicsItem *item, status.m_selectedItems)
1029         item->setSelected(true);
1030 
1031     // reset focus item
1032     if (status.m_focusItem) {
1033         ISelectable *selectable = dynamic_cast<ISelectable *>(status.m_focusItem);
1034         if (selectable) {
1035             selectable->setFocusSelected(true);
1036             m_focusItem = status.m_focusItem;
1037         }
1038     }
1039 
1040     // reset edit item
1041     if (status.m_editItem)
1042         status.m_editItem->edit();
1043 }
1044 
recalcSceneRectSize()1045 void DiagramSceneModel::recalcSceneRectSize()
1046 {
1047     QRectF sceneRect = m_originItem->mapRectToScene(m_originItem->boundingRect());
1048     for (QGraphicsItem *item : qAsConst(m_graphicsItems)) {
1049         // TODO use an interface to update sceneRect by item
1050         if (!dynamic_cast<SwimlaneItem *>(item))
1051             sceneRect |= item->mapRectToScene(item->boundingRect());
1052     }
1053     m_sceneRect = sceneRect;
1054     emit sceneRectChanged(sceneRect);
1055 }
1056 
createGraphicsItem(DElement * element)1057 QGraphicsItem *DiagramSceneModel::createGraphicsItem(DElement *element)
1058 {
1059     QMT_ASSERT(element, return nullptr);
1060     QMT_CHECK(!m_elementToItemMap.contains(element));
1061 
1062     CreationVisitor visitor(this);
1063     element->accept(&visitor);
1064     QGraphicsItem *item = visitor.createdGraphicsItem();
1065     m_itemToElementMap.insert(item, element);
1066     m_elementToItemMap.insert(element, item);
1067     m_graphicsScene->addItem(item);
1068     return item;
1069 }
1070 
updateGraphicsItem(QGraphicsItem * item,DElement * element)1071 void DiagramSceneModel::updateGraphicsItem(QGraphicsItem *item, DElement *element)
1072 {
1073     QMT_ASSERT(item, return);
1074     QMT_ASSERT(element, return);
1075 
1076     UpdateVisitor visitor(item, this);
1077     element->accept(&visitor);
1078 }
1079 
deleteGraphicsItem(QGraphicsItem * item,DElement * element)1080 void DiagramSceneModel::deleteGraphicsItem(QGraphicsItem *item, DElement *element)
1081 {
1082     QMT_CHECK(m_elementToItemMap.contains(element));
1083     QMT_CHECK(m_itemToElementMap.contains(item));
1084     if (item == m_focusItem)
1085         unsetFocusItem();
1086     m_graphicsScene->removeItem(item);
1087     m_elementToItemMap.remove(element);
1088     m_itemToElementMap.remove(item);
1089     m_selectedItems.remove(item);
1090     m_secondarySelectedItems.remove(item);
1091     delete item;
1092 }
1093 
updateFocusItem(const QSet<QGraphicsItem * > & selectedItems)1094 void DiagramSceneModel::updateFocusItem(const QSet<QGraphicsItem *> &selectedItems)
1095 {
1096     QGraphicsItem *mouseGrabberItem = m_graphicsScene->mouseGrabberItem();
1097     QGraphicsItem *focusItem = nullptr;
1098     ISelectable *selectable = nullptr;
1099 
1100     if (mouseGrabberItem && selectedItems.contains(mouseGrabberItem)) {
1101         selectable = dynamic_cast<ISelectable *>(mouseGrabberItem);
1102         if (selectable)
1103             focusItem = mouseGrabberItem;
1104     }
1105     if (focusItem && focusItem != m_focusItem) {
1106         unsetFocusItem();
1107         selectable->setFocusSelected(true);
1108         m_focusItem = focusItem;
1109     } else if (m_focusItem && !selectedItems.contains(m_focusItem)) {
1110         unsetFocusItem();
1111     }
1112 }
1113 
unsetFocusItem()1114 void DiagramSceneModel::unsetFocusItem()
1115 {
1116     if (m_focusItem) {
1117         if (auto oldSelectable = dynamic_cast<ISelectable *>(m_focusItem))
1118             oldSelectable->setFocusSelected(false);
1119         else
1120             QMT_CHECK(false);
1121         m_focusItem = nullptr;
1122     }
1123 }
1124 
1125 } // namespace qmt
1126