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 "diagramscenecontroller.h"
27 
28 #include "qmt/controller/namecontroller.h"
29 #include "qmt/controller/undocontroller.h"
30 #include "qmt/diagram_controller/dfactory.h"
31 #include "qmt/diagram_controller/diagramcontroller.h"
32 #include "qmt/diagram_controller/dselection.h"
33 #include "qmt/diagram/dannotation.h"
34 #include "qmt/diagram/dassociation.h"
35 #include "qmt/diagram/dboundary.h"
36 #include "qmt/diagram/dclass.h"
37 #include "qmt/diagram/dconnection.h"
38 #include "qmt/diagram/ditem.h"
39 #include "qmt/diagram/dpackage.h"
40 #include "qmt/diagram/drelation.h"
41 #include "qmt/diagram/dswimlane.h"
42 #include "qmt/diagram_ui/diagram_mime_types.h"
43 #include "qmt/model_controller/modelcontroller.h"
44 #include "qmt/model_controller/mselection.h"
45 #include "qmt/model_controller/mvoidvisitor.h"
46 #include "qmt/model/massociation.h"
47 #include "qmt/model/mcanvasdiagram.h"
48 #include "qmt/model/mclass.h"
49 #include "qmt/model/mcomponent.h"
50 #include "qmt/model/mconnection.h"
51 #include "qmt/model/mdependency.h"
52 #include "qmt/model/mdiagram.h"
53 #include "qmt/model/minheritance.h"
54 #include "qmt/model/mitem.h"
55 #include "qmt/model/mobject.h"
56 #include "qmt/model/mpackage.h"
57 #include "qmt/model/msourceexpansion.h"
58 #include "qmt/stereotype/customrelation.h"
59 #include "qmt/stereotype/stereotypecontroller.h"
60 #include "qmt/tasks/alignonrastervisitor.h"
61 #include "qmt/tasks/isceneinspector.h"
62 #include "qmt/tasks/voidelementtasks.h"
63 
64 #include <QMenu>
65 #include <QFileInfo>
66 #include <QDir>
67 #include <QQueue>
68 #include <QPair>
69 
70 #include <QtMath>
71 
72 namespace qmt {
73 
74 namespace {
75 VoidElementTasks dummyElementTasks;
76 }
77 
78 class DiagramSceneController::AcceptRelationVisitor : public MVoidConstVisitor
79 {
80 public:
AcceptRelationVisitor(StereotypeController * stereotypeController,const MRelation * relation,RelationEnd relationEnd)81     AcceptRelationVisitor(StereotypeController *stereotypeController, const MRelation *relation,
82                           RelationEnd relationEnd)
83         : m_stereotypeController(stereotypeController),
84           m_relation(relation),
85           m_relationEnd(relationEnd)
86     {
87     }
88 
isAccepted() const89     bool isAccepted() const { return m_accepted; }
90 
visitMObject(const MObject * object)91     void visitMObject(const MObject *object) override
92     {
93         Q_UNUSED(object)
94         if (auto connection = dynamic_cast<const MConnection *>(m_relation)) {
95             CustomRelation customRelation = m_stereotypeController->findCustomRelation(connection->customRelationId());
96             if (!customRelation.isNull()) {
97                 QMT_ASSERT(customRelation.element() == CustomRelation::Element::Relation, return);
98                 CustomRelation::End customEnd = m_relationEnd == EndA ? customRelation.endA() : customRelation.endB();
99                 QStringList endItems = customEnd.endItems();
100                 if (endItems.isEmpty())
101                     endItems = customRelation.endItems();
102                 QString stereotypeIconId = m_stereotypeController->findStereotypeIconId(StereotypeIcon::ElementItem, object->stereotypes());
103                 if (stereotypeIconId.isEmpty() && !m_variety.isEmpty())
104                     stereotypeIconId = m_stereotypeController->findStereotypeIconId(StereotypeIcon::ElementItem, {m_variety});
105                 m_accepted = endItems.contains(stereotypeIconId);
106             }
107         }
108         if (!m_accepted)
109             m_accepted = dynamic_cast<const MDependency *>(m_relation) != nullptr;
110     }
111 
visitMClass(const MClass * klass)112     void visitMClass(const MClass *klass) override
113     {
114         m_accepted = dynamic_cast<const MInheritance *>(m_relation) != nullptr
115                 || dynamic_cast<const MAssociation *>(m_relation) != nullptr;
116         if (!m_accepted)
117             visitMObject(klass);
118     }
119 
visitMItem(const MItem * item)120     void visitMItem(const MItem *item) override
121     {
122         m_variety = item->variety();
123         visitMObject(item);
124     }
125 
126 private:
127     StereotypeController *m_stereotypeController = nullptr;
128     const MRelation *m_relation = nullptr;
129     RelationEnd m_relationEnd = EndA;
130     QString m_variety;
131     bool m_accepted = false;
132 };
133 
DiagramSceneController(QObject * parent)134 DiagramSceneController::DiagramSceneController(QObject *parent)
135     : QObject(parent),
136       m_elementTasks(&dummyElementTasks)
137 {
138 }
139 
~DiagramSceneController()140 DiagramSceneController::~DiagramSceneController()
141 {
142 }
143 
setModelController(ModelController * modelController)144 void DiagramSceneController::setModelController(ModelController *modelController)
145 {
146     if (m_modelController == modelController)
147         return;
148     if (m_modelController) {
149         disconnect(m_modelController, nullptr, this, nullptr);
150         m_modelController = nullptr;
151     }
152     if (modelController)
153         m_modelController = modelController;
154 }
155 
setDiagramController(DiagramController * diagramController)156 void DiagramSceneController::setDiagramController(DiagramController *diagramController)
157 {
158     if (m_diagramController == diagramController)
159         return;
160     if (m_diagramController) {
161         disconnect(m_diagramController, nullptr, this, nullptr);
162         m_diagramController = nullptr;
163     }
164     if (diagramController)
165         m_diagramController = diagramController;
166 }
167 
setStereotypeController(StereotypeController * stereotypeController)168 void DiagramSceneController::setStereotypeController(StereotypeController *stereotypeController)
169 {
170     m_stereotypeController = stereotypeController;
171 }
172 
setElementTasks(IElementTasks * elementTasks)173 void DiagramSceneController::setElementTasks(IElementTasks *elementTasks)
174 {
175     m_elementTasks = elementTasks;
176 }
177 
setSceneInspector(ISceneInspector * sceneInspector)178 void DiagramSceneController::setSceneInspector(ISceneInspector *sceneInspector)
179 {
180     m_sceneInspector = sceneInspector;
181 }
182 
deleteFromDiagram(const DSelection & dselection,MDiagram * diagram)183 void DiagramSceneController::deleteFromDiagram(const DSelection &dselection, MDiagram *diagram)
184 {
185     if (!dselection.isEmpty()) {
186         MSelection mselection;
187         DSelection remainingDselection;
188         foreach (const DSelection::Index &index, dselection.indices()) {
189             DElement *delement = m_diagramController->findElement(index.elementKey(), diagram);
190             QMT_ASSERT(delement, return);
191             if (delement->modelUid().isValid()) {
192                 MElement *melement = m_modelController->findElement(delement->modelUid());
193                 QMT_ASSERT(melement, return);
194                 if (melement->owner())
195                     mselection.append(melement->uid(), melement->owner()->uid());
196             } else {
197                 remainingDselection.append(index);
198             }
199         }
200         if (!remainingDselection.isEmpty())
201             m_diagramController->deleteElements(remainingDselection, diagram);
202         if (!mselection.isEmpty())
203             m_modelController->deleteElements(mselection);
204     }
205 }
206 
createDependency(DObject * endAObject,DObject * endBObject,const QList<QPointF> & intermediatePoints,MDiagram * diagram)207 void DiagramSceneController::createDependency(DObject *endAObject, DObject *endBObject,
208                                               const QList<QPointF> &intermediatePoints, MDiagram *diagram)
209 {
210     m_diagramController->undoController()->beginMergeSequence(tr("Create Dependency"));
211 
212     MObject *endAModelObject = m_modelController->findObject<MObject>(endAObject->modelUid());
213     QMT_ASSERT(endAModelObject, return);
214     MObject *endBModelObject = m_modelController->findObject<MObject>(endBObject->modelUid());
215     QMT_ASSERT(endBModelObject, return);
216 
217     if (endAModelObject == endBModelObject)
218         return;
219 
220     auto modelDependency = new MDependency();
221     modelDependency->setEndAUid(endAModelObject->uid());
222     modelDependency->setEndBUid(endBModelObject->uid());
223     modelDependency->setDirection(MDependency::AToB);
224     m_modelController->addRelation(endAModelObject, modelDependency);
225 
226     DRelation *relation = addRelation(modelDependency, intermediatePoints, diagram);
227 
228     m_diagramController->undoController()->endMergeSequence();
229 
230     if (relation)
231         emit newElementCreated(relation, diagram);
232 }
233 
createInheritance(DClass * derivedClass,DClass * baseClass,const QList<QPointF> & intermediatePoints,MDiagram * diagram)234 void DiagramSceneController::createInheritance(DClass *derivedClass, DClass *baseClass,
235                                                const QList<QPointF> &intermediatePoints, MDiagram *diagram)
236 {
237     m_diagramController->undoController()->beginMergeSequence(tr("Create Inheritance"));
238 
239     MClass *derivedModelClass = m_modelController->findObject<MClass>(derivedClass->modelUid());
240     QMT_ASSERT(derivedModelClass, return);
241     MClass *baseModelClass = m_modelController->findObject<MClass>(baseClass->modelUid());
242     QMT_ASSERT(baseModelClass, return);
243 
244     if (derivedModelClass == baseModelClass)
245         return;
246 
247     auto modelInheritance = new MInheritance();
248     modelInheritance->setDerived(derivedModelClass->uid());
249     modelInheritance->setBase(baseModelClass->uid());
250     m_modelController->addRelation(derivedModelClass, modelInheritance);
251 
252     DRelation *relation = addRelation(modelInheritance, intermediatePoints, diagram);
253 
254     m_diagramController->undoController()->endMergeSequence();
255 
256     if (relation)
257         emit newElementCreated(relation, diagram);
258 }
259 
createAssociation(DClass * endAClass,DClass * endBClass,const QList<QPointF> & intermediatePoints,MDiagram * diagram,std::function<void (MAssociation *,DAssociation *)> custom)260 void DiagramSceneController::createAssociation(DClass *endAClass, DClass *endBClass,
261                                                const QList<QPointF> &intermediatePoints, MDiagram *diagram,
262                                                std::function<void (MAssociation*, DAssociation*)> custom)
263 {
264     m_diagramController->undoController()->beginMergeSequence(tr("Create Association"));
265 
266     MClass *endAModelObject = m_modelController->findObject<MClass>(endAClass->modelUid());
267     QMT_ASSERT(endAModelObject, return);
268     MClass *endBModelObject = m_modelController->findObject<MClass>(endBClass->modelUid());
269     QMT_ASSERT(endBModelObject, return);
270 
271     // TODO allow self assignment with just one intermediate point and a nice round arrow
272     if (endAModelObject == endBModelObject && intermediatePoints.count() < 2)
273         return;
274 
275     auto modelAssociation = new MAssociation();
276     modelAssociation->setEndAUid(endAModelObject->uid());
277     MAssociationEnd endA = modelAssociation->endA();
278     endA.setNavigable(true);
279     modelAssociation->setEndA(endA);
280     modelAssociation->setEndBUid(endBModelObject->uid());
281     m_modelController->addRelation(endAModelObject, modelAssociation);
282 
283     DRelation *relation = addRelation(modelAssociation, intermediatePoints, diagram);
284     DAssociation *diagramAssociation = dynamic_cast<DAssociation *>(relation);
285     QMT_CHECK(diagramAssociation);
286 
287     if (custom)
288         custom(modelAssociation, diagramAssociation);
289 
290     m_diagramController->undoController()->endMergeSequence();
291 
292     if (relation)
293         emit newElementCreated(relation, diagram);
294 }
295 
createConnection(const QString & customRelationId,DObject * endAObject,DObject * endBObject,const QList<QPointF> & intermediatePoints,MDiagram * diagram,std::function<void (MConnection *,DConnection *)> custom)296 void DiagramSceneController::createConnection(const QString &customRelationId,
297                                               DObject *endAObject, DObject *endBObject,
298                                               const QList<QPointF> &intermediatePoints, MDiagram *diagram,
299                                               std::function<void (MConnection *, DConnection *)> custom)
300 {
301     m_diagramController->undoController()->beginMergeSequence(tr("Create Connection"));
302 
303     MObject *endAModelObject = m_modelController->findObject<MObject>(endAObject->modelUid());
304     QMT_CHECK(endAModelObject);
305     MObject *endBModelObject = m_modelController->findObject<MObject>(endBObject->modelUid());
306     QMT_CHECK(endBModelObject);
307 
308     // TODO allow self assignment with just one intermediate point and a nice round arrow
309     if (endAModelObject == endBModelObject && intermediatePoints.count() < 2)
310         return;
311 
312     auto modelConnection = new MConnection();
313     modelConnection->setCustomRelationId(customRelationId);
314     modelConnection->setEndAUid(endAModelObject->uid());
315     MConnectionEnd endA = modelConnection->endA();
316     endA.setNavigable(true);
317     modelConnection->setEndA(endA);
318     modelConnection->setEndBUid(endBModelObject->uid());
319     m_modelController->addRelation(endAModelObject, modelConnection);
320 
321     DRelation *relation = addRelation(modelConnection, intermediatePoints, diagram);
322     DConnection *diagramConnection = dynamic_cast<DConnection *>(relation);
323     QMT_CHECK(diagramConnection);
324 
325     if (custom)
326         custom(modelConnection, diagramConnection);
327 
328     m_diagramController->undoController()->endMergeSequence();
329 
330     if (relation)
331         emit newElementCreated(relation, diagram);
332 }
333 
relocateRelationEndA(DRelation * relation,DObject * targetObject)334 bool DiagramSceneController::relocateRelationEndA(DRelation *relation, DObject *targetObject)
335 {
336     return relocateRelationEnd(relation, targetObject, EndA, &MRelation::endAUid, &MRelation::setEndAUid);
337 }
338 
relocateRelationEndB(DRelation * relation,DObject * targetObject)339 bool DiagramSceneController::relocateRelationEndB(DRelation *relation, DObject *targetObject)
340 {
341     return relocateRelationEnd(relation, targetObject, EndB, &MRelation::endBUid, &MRelation::setEndBUid);
342 }
343 
isAddingAllowed(const Uid & modelElementKey,MDiagram * diagram)344 bool DiagramSceneController::isAddingAllowed(const Uid &modelElementKey, MDiagram *diagram)
345 {
346     MElement *modelElement = m_modelController->findElement(modelElementKey);
347     QMT_ASSERT(modelElement, return false);
348     if (m_diagramController->hasDelegate(modelElement, diagram))
349         return false;
350     if (auto modelRelation = dynamic_cast<MRelation *>(modelElement)) {
351         MObject *endAModelObject = m_modelController->findObject(modelRelation->endAUid());
352         QMT_ASSERT(endAModelObject, return false);
353         DObject *endADiagramObject = m_diagramController->findDelegate<DObject>(endAModelObject, diagram);
354         if (!endADiagramObject)
355             return false;
356 
357         MObject *endBModelObject = m_modelController->findObject(modelRelation->endBUid());
358         QMT_ASSERT(endBModelObject, return false);
359         DObject *endBDiagramObject = m_diagramController->findDelegate<DObject>(endBModelObject, diagram);
360         if (!endBDiagramObject)
361             return false;
362     }
363     return true;
364 }
365 
addExistingModelElement(const Uid & modelElementKey,const QPointF & pos,MDiagram * diagram)366 void DiagramSceneController::addExistingModelElement(const Uid &modelElementKey, const QPointF &pos, MDiagram *diagram)
367 {
368     DElement *element = addModelElement(modelElementKey, pos, diagram);
369     if (element)
370         emit elementAdded(element, diagram);
371 }
372 
dropNewElement(const QString & newElementId,const QString & name,const QString & stereotype,DElement * topMostElementAtPos,const QPointF & pos,MDiagram * diagram,const QPoint & viewPos,const QSize & viewSize)373 void DiagramSceneController::dropNewElement(const QString &newElementId, const QString &name, const QString &stereotype,
374                                             DElement *topMostElementAtPos, const QPointF &pos, MDiagram *diagram,
375                                             const QPoint &viewPos, const QSize &viewSize)
376 {
377     if (newElementId == QLatin1String(ELEMENT_TYPE_ANNOTATION)) {
378         auto annotation = new DAnnotation();
379         annotation->setPos(pos - QPointF(10.0, 10.0));
380         m_diagramController->addElement(annotation, diagram);
381         alignOnRaster(annotation, diagram);
382         emit newElementCreated(annotation, diagram);
383     } else if (newElementId == QLatin1String(ELEMENT_TYPE_BOUNDARY)) {
384         auto boundary = new DBoundary();
385         boundary->setPos(pos);
386         m_diagramController->addElement(boundary, diagram);
387         alignOnRaster(boundary, diagram);
388         emit newElementCreated(boundary, diagram);
389     } else if (newElementId == QLatin1String(ELEMENT_TYPE_SWIMLANE)) {
390         auto swimlane = new DSwimlane();
391         qreal x = static_cast<qreal>(viewPos.x()) / viewSize.width();
392         qreal y = static_cast<qreal>(viewPos.y()) / viewSize.height();
393         bool horizontal = (y > x && (1-y) > x) || (y <= x && (1-y) <= x);
394         swimlane->setHorizontal(horizontal);
395         swimlane->setPos(horizontal ? pos.y() : pos.x());
396         m_diagramController->addElement(swimlane, diagram);
397         alignOnRaster(swimlane, diagram);
398         emit newElementCreated(swimlane, diagram);
399     } else {
400         MPackage *parentPackage = findSuitableParentPackage(topMostElementAtPos, diagram);
401         MObject *newObject = nullptr;
402         if (newElementId == QLatin1String(ELEMENT_TYPE_PACKAGE)) {
403             auto package = new MPackage();
404             if (!stereotype.isEmpty())
405                 package->setStereotypes({stereotype});
406             newObject = package;
407         } else if (newElementId == QLatin1String(ELEMENT_TYPE_COMPONENT)) {
408             auto component = new MComponent();
409             if (!stereotype.isEmpty())
410                 component->setStereotypes({stereotype});
411             newObject = component;
412         } else if (newElementId == QLatin1String(ELEMENT_TYPE_CLASS)) {
413             auto klass = new MClass();
414             if (!stereotype.isEmpty())
415                 klass->setStereotypes({stereotype});
416             newObject = klass;
417         } else if (newElementId == QLatin1String(ELEMENT_TYPE_ITEM)) {
418             auto item = new MItem();
419             if (!stereotype.isEmpty()) {
420                 item->setVariety(stereotype);
421                 item->setVarietyEditable(false);
422             }
423             newObject = item;
424         }
425         if (newObject) {
426             newObject->setName(name);
427             dropNewModelElement(newObject, parentPackage, pos, diagram);
428         }
429     }
430 }
431 
dropNewModelElement(MObject * modelObject,MPackage * parentPackage,const QPointF & pos,MDiagram * diagram)432 void DiagramSceneController::dropNewModelElement(MObject *modelObject, MPackage *parentPackage, const QPointF &pos,
433                                                  MDiagram *diagram)
434 {
435     m_modelController->undoController()->beginMergeSequence(tr("Drop Element"));
436     m_modelController->addObject(parentPackage, modelObject);
437     DElement *element = addObject(modelObject, pos, diagram);
438     m_modelController->undoController()->endMergeSequence();
439     if (element)
440         emit newElementCreated(element, diagram);
441 }
442 
addRelatedElements(const DSelection & selection,MDiagram * diagram)443 void DiagramSceneController::addRelatedElements(const DSelection &selection, MDiagram *diagram)
444 {
445     m_diagramController->undoController()->beginMergeSequence(tr("Add Related Element"));
446     foreach (const DSelection::Index &index, selection.indices()) {
447         DElement *delement = m_diagramController->findElement(index.elementKey(), diagram);
448         QMT_ASSERT(delement, return);
449         DObject *dobject = dynamic_cast<DObject *>(delement);
450         if (dobject && dobject->modelUid().isValid()) {
451             MObject *mobject = m_modelController->findElement<MObject>(delement->modelUid());
452             if (mobject) {
453                 qreal dAngle = 360.0 / 11.5;
454                 qreal dRadius = 100.0;
455                 const QList<MRelation *> relations = m_modelController->findRelationsOfObject(mobject);
456                 int count = 0;
457                 for (MRelation *relation : relations) {
458                     if (relation->endAUid() != mobject->uid() || relation->endBUid() != mobject->uid())
459                         ++count;
460                 }
461                 if (count <= 12) {
462                     dAngle = 360.0 / 12.0;
463                     dRadius = 0.0;
464                 }
465                 qreal radius = 200.0;
466                 qreal angle = 0.0;
467                 for (MRelation *relation : relations) {
468                     QPointF pos(dobject->pos());
469                     pos += QPointF(radius * sin(angle / 180 * M_PI), -radius * cos(angle / 180 * M_PI));
470                     bool added = false;
471                     if (relation->endAUid() != mobject->uid())
472                         added = addModelElement(relation->endAUid(), pos, diagram) != nullptr;
473                     else if (relation->endBUid() != mobject->uid())
474                         added = addModelElement(relation->endBUid(), pos, diagram) != nullptr;
475                     if (added) {
476                         radius += dRadius / (360.0 / dAngle);
477                         angle += dAngle;
478                     }
479                 }
480             }
481         }
482     }
483     m_diagramController->undoController()->endMergeSequence();
484 }
485 
findSuitableParentPackage(DElement * topmostDiagramElement,MDiagram * diagram)486 MPackage *DiagramSceneController::findSuitableParentPackage(DElement *topmostDiagramElement, MDiagram *diagram)
487 {
488     MPackage *parentPackage = nullptr;
489     if (auto diagramPackage = dynamic_cast<DPackage *>(topmostDiagramElement)) {
490         parentPackage = m_modelController->findObject<MPackage>(diagramPackage->modelUid());
491     } else if (auto diagramObject = dynamic_cast<DObject *>(topmostDiagramElement)) {
492         MObject *modelObject = m_modelController->findObject(diagramObject->modelUid());
493         if (modelObject)
494             parentPackage = dynamic_cast<MPackage *>(modelObject->owner());
495     }
496     if (!parentPackage && diagram)
497         parentPackage = dynamic_cast<MPackage *>(diagram->owner());
498     if (!parentPackage)
499         parentPackage = m_modelController->rootPackage();
500     return parentPackage;
501 }
502 
findDiagramBySearchId(MPackage * package,const QString & diagramName)503 MDiagram *DiagramSceneController::findDiagramBySearchId(MPackage *package, const QString &diagramName)
504 {
505     QString diagramSearchId = NameController::calcElementNameSearchId(diagramName);
506     for (const Handle<MObject> &handle : package->children()) {
507         if (handle.hasTarget()) {
508             if (auto diagram = dynamic_cast<MDiagram *>(handle.target())) {
509                 if (NameController::calcElementNameSearchId(diagram->name()) == diagramSearchId)
510                     return diagram;
511             }
512         }
513     }
514     return nullptr;
515 }
516 
517 namespace {
518 
alignObjectLeft(DObject * object,DObject * otherObject)519 QPointF alignObjectLeft(DObject *object, DObject *otherObject)
520 {
521     qreal left = object->pos().x() + object->rect().left();
522     QPointF pos = otherObject->pos();
523     qreal otherLeft = pos.x() + otherObject->rect().left();
524     qreal delta = otherLeft - left;
525     pos.setX(pos.x() - delta);
526     return pos;
527 }
528 
alignObjectRight(DObject * object,DObject * otherObject)529 QPointF alignObjectRight(DObject *object, DObject *otherObject)
530 {
531     qreal right = object->pos().x() + object->rect().right();
532     QPointF pos = otherObject->pos();
533     qreal otherRight = pos.x() + otherObject->rect().right();
534     qreal delta = otherRight - right;
535     pos.setX(pos.x() - delta);
536     return pos;
537 }
538 
alignObjectHCenter(DObject * object,DObject * otherObject)539 QPointF alignObjectHCenter(DObject *object, DObject *otherObject)
540 {
541     qreal center = object->pos().x();
542     QPointF pos = otherObject->pos();
543     qreal otherCenter = pos.x();
544     qreal delta = otherCenter - center;
545     pos.setX(pos.x() - delta);
546     return pos;
547 }
548 
alignObjectTop(DObject * object,DObject * otherObject)549 QPointF alignObjectTop(DObject *object, DObject *otherObject)
550 {
551     qreal top = object->pos().y() + object->rect().top();
552     QPointF pos = otherObject->pos();
553     qreal otherTop = pos.y() + otherObject->rect().top();
554     qreal delta = otherTop - top;
555     pos.setY(pos.y() - delta);
556     return pos;
557 }
558 
alignObjectBottom(DObject * object,DObject * otherObject)559 QPointF alignObjectBottom(DObject *object, DObject *otherObject)
560 {
561     qreal bottom = object->pos().y() + object->rect().bottom();
562     QPointF pos = otherObject->pos();
563     qreal otherBottom = pos.y() + otherObject->rect().bottom();
564     qreal delta = otherBottom - bottom;
565     pos.setY(pos.y() - delta);
566     return pos;
567 }
568 
alignObjectVCenter(DObject * object,DObject * otherObject)569 QPointF alignObjectVCenter(DObject *object, DObject *otherObject)
570 {
571     qreal center = object->pos().y();
572     QPointF pos = otherObject->pos();
573     qreal otherCenter = pos.y();
574     qreal delta = otherCenter - center;
575     pos.setY(pos.y() - delta);
576     return pos;
577 }
578 
alignObjectWidth(DObject * object,const QSizeF & size)579 QRectF alignObjectWidth(DObject *object, const QSizeF &size)
580 {
581     QRectF rect = object->rect();
582     rect.setX(-size.width() / 2.0);
583     rect.setWidth(size.width());
584     return rect;
585 }
586 
alignObjectHeight(DObject * object,const QSizeF & size)587 QRectF alignObjectHeight(DObject *object, const QSizeF &size)
588 {
589     QRectF rect = object->rect();
590     rect.setY(-size.height() / 2.0);
591     rect.setHeight(size.height());
592     return rect;
593 }
594 
alignObjectSize(DObject * object,const QSizeF & size)595 QRectF alignObjectSize(DObject *object, const QSizeF &size)
596 {
597     Q_UNUSED(object)
598 
599     QRectF rect;
600     rect.setX(-size.width() / 2.0);
601     rect.setY(-size.height() / 2.0);
602     rect.setWidth(size.width());
603     rect.setHeight(size.height());
604     return rect;
605 }
606 
607 }
608 
alignLeft(DObject * object,const DSelection & selection,MDiagram * diagram)609 void DiagramSceneController::alignLeft(DObject *object, const DSelection &selection, MDiagram *diagram)
610 {
611     alignPosition(object, selection, alignObjectLeft, diagram);
612 }
613 
alignRight(DObject * object,const DSelection & selection,MDiagram * diagram)614 void DiagramSceneController::alignRight(DObject *object, const DSelection &selection, MDiagram *diagram)
615 {
616     alignPosition(object, selection, alignObjectRight, diagram);
617 }
618 
alignHCenter(DObject * object,const DSelection & selection,MDiagram * diagram)619 void DiagramSceneController::alignHCenter(DObject *object, const DSelection &selection, MDiagram *diagram)
620 {
621     alignPosition(object, selection, alignObjectHCenter, diagram);
622 }
623 
alignTop(DObject * object,const DSelection & selection,MDiagram * diagram)624 void DiagramSceneController::alignTop(DObject *object, const DSelection &selection, MDiagram *diagram)
625 {
626     alignPosition(object, selection, alignObjectTop, diagram);
627 }
628 
alignBottom(DObject * object,const DSelection & selection,MDiagram * diagram)629 void DiagramSceneController::alignBottom(DObject *object, const DSelection &selection, MDiagram *diagram)
630 {
631     alignPosition(object, selection, alignObjectBottom, diagram);
632 }
633 
alignVCenter(DObject * object,const DSelection & selection,MDiagram * diagram)634 void DiagramSceneController::alignVCenter(DObject *object, const DSelection &selection, MDiagram *diagram)
635 {
636     alignPosition(object, selection, alignObjectVCenter, diagram);
637 }
638 
alignWidth(DObject * object,const DSelection & selection,const QSizeF & minimumSize,MDiagram * diagram)639 void DiagramSceneController::alignWidth(DObject *object, const DSelection &selection, const QSizeF &minimumSize,
640                                         MDiagram *diagram)
641 {
642     alignSize(object, selection, minimumSize, alignObjectWidth, diagram);
643 }
644 
alignHeight(DObject * object,const DSelection & selection,const QSizeF & minimumSize,MDiagram * diagram)645 void DiagramSceneController::alignHeight(DObject *object, const DSelection &selection, const QSizeF &minimumSize,
646                                          MDiagram *diagram)
647 {
648     alignSize(object, selection, minimumSize, alignObjectHeight, diagram);
649 }
650 
alignSize(DObject * object,const DSelection & selection,const QSizeF & minimumSize,MDiagram * diagram)651 void DiagramSceneController::alignSize(DObject *object, const DSelection &selection, const QSizeF &minimumSize,
652                                        MDiagram *diagram)
653 {
654     alignSize(object, selection, minimumSize, alignObjectSize, diagram);
655 }
656 
alignHCenterDistance(const DSelection & selection,MDiagram * diagram)657 void DiagramSceneController::alignHCenterDistance(const DSelection &selection, MDiagram *diagram)
658 {
659     QList<DObject *> sortedObjects = collectObjects(selection, diagram);
660     if (sortedObjects.length() > 2) {
661         std::sort(sortedObjects.begin(), sortedObjects.end(), [](const DObject *lhs, const DObject *rhs) {
662             return lhs->pos().x() < rhs->pos().x();
663         });
664         int n = sortedObjects.length() - 1;
665         DObject *leftObject = sortedObjects.at(0);
666         DObject *rightObject = sortedObjects.at(n);
667         double distance = rightObject->pos().x() - leftObject->pos().x();
668         double step = distance / n;
669         double startX = leftObject->pos().x();
670         for (int i = 1; i < n; ++i) {
671             DObject *selectedObject = sortedObjects.at(i);
672             QPointF newPos = selectedObject->pos();
673             newPos.setX(startX + i * step);
674             if (newPos != selectedObject->pos()) {
675                 m_diagramController->startUpdateElement(selectedObject, diagram, DiagramController::UpdateGeometry);
676                 selectedObject->setPos(newPos);
677                 m_diagramController->finishUpdateElement(selectedObject, diagram, false);
678             }
679         }
680     }
681 }
682 
alignVCenterDistance(const DSelection & selection,MDiagram * diagram)683 void DiagramSceneController::alignVCenterDistance(const DSelection &selection, MDiagram *diagram)
684 {
685     QList<DObject *> sortedObjects = collectObjects(selection, diagram);
686     if (sortedObjects.length() > 2) {
687         std::sort(sortedObjects.begin(), sortedObjects.end(), [](const DObject *lhs, const DObject *rhs) {
688             return lhs->pos().y() < rhs->pos().y();
689         });
690         int n = sortedObjects.length() - 1;
691         DObject *topObject = sortedObjects.at(0);
692         DObject *bottomObject = sortedObjects.at(n);
693         double distance = bottomObject->pos().y() - topObject->pos().y();
694         double step = distance / n;
695         double startY = topObject->pos().y();
696         for (int i = 1; i < n; ++i) {
697             DObject *selectedObject = sortedObjects.at(i);
698             QPointF newPos = selectedObject->pos();
699             newPos.setY(startY + i * step);
700             if (newPos != selectedObject->pos()) {
701                 m_diagramController->startUpdateElement(selectedObject, diagram, DiagramController::UpdateGeometry);
702                 selectedObject->setPos(newPos);
703                 m_diagramController->finishUpdateElement(selectedObject, diagram, false);
704             }
705         }
706     }
707 }
708 
alignHBorderDistance(const DSelection & selection,MDiagram * diagram)709 void DiagramSceneController::alignHBorderDistance(const DSelection &selection, MDiagram *diagram)
710 {
711     QList<DObject *> sortedObjects = collectObjects(selection, diagram);
712     if (sortedObjects.length() > 2) {
713         std::sort(sortedObjects.begin(), sortedObjects.end(), [](const DObject *lhs, const DObject *rhs) {
714             return lhs->pos().x() < rhs->pos().x();
715         });
716         int n = sortedObjects.length() - 1;
717         DObject *leftObject = sortedObjects.at(0);
718         DObject *rightObject = sortedObjects.at(n);
719         double space = rightObject->pos().x() + rightObject->rect().left() - (leftObject->pos().x() + leftObject->rect().right());
720         for (int i = 1; i < n; ++i)
721             space -= sortedObjects.at(i)->rect().width();
722         double step = space / n;
723         double x = leftObject->pos().x() + leftObject->rect().right();
724         for (int i = 1 ; i < n; ++i) {
725             DObject *selectedObject = sortedObjects.at(i);
726             QPointF newPos = selectedObject->pos();
727             x += step;
728             newPos.setX(x - selectedObject->rect().left());
729             x += selectedObject->rect().width();
730             if (newPos != selectedObject->pos()) {
731                 m_diagramController->startUpdateElement(selectedObject, diagram, DiagramController::UpdateGeometry);
732                 selectedObject->setPos(newPos);
733                 m_diagramController->finishUpdateElement(selectedObject, diagram, false);
734             }
735         }
736     }
737 }
738 
alignVBorderDistance(const DSelection & selection,MDiagram * diagram)739 void DiagramSceneController::alignVBorderDistance(const DSelection &selection, MDiagram *diagram)
740 {
741     QList<DObject *> sortedObjects = collectObjects(selection, diagram);
742     if (sortedObjects.length() > 2) {
743         std::sort(sortedObjects.begin(), sortedObjects.end(), [](const DObject *lhs, const DObject *rhs) {
744             return lhs->pos().y() < rhs->pos().y();
745         });
746         int n = sortedObjects.length() - 1;
747         DObject *topObject = sortedObjects.at(0);
748         DObject *bottomObject = sortedObjects.at(n);
749         double space = bottomObject->pos().y() + bottomObject->rect().top() - (topObject->pos().y() + topObject->rect().bottom());
750         for (int i = 1; i < n; ++i)
751             space -= sortedObjects.at(i)->rect().height();
752         double step = space / n;
753         double y = topObject->pos().y() + topObject->rect().bottom();
754         for (int i = 1 ; i < n; ++i) {
755             DObject *selectedObject = sortedObjects.at(i);
756             QPointF newPos = selectedObject->pos();
757             y += step;
758             newPos.setY(y - selectedObject->rect().top());
759             y += selectedObject->rect().height();
760             if (newPos != selectedObject->pos()) {
761                 m_diagramController->startUpdateElement(selectedObject, diagram, DiagramController::UpdateGeometry);
762                 selectedObject->setPos(newPos);
763                 m_diagramController->finishUpdateElement(selectedObject, diagram, false);
764             }
765         }
766     }
767 }
768 
alignPosition(DObject * object,const DSelection & selection,QPointF (* aligner)(DObject *,DObject *),MDiagram * diagram)769 void DiagramSceneController::alignPosition(DObject *object, const DSelection &selection,
770                                            QPointF (*aligner)(DObject *, DObject *), MDiagram *diagram)
771 {
772     foreach (const DSelection::Index &index, selection.indices()) {
773         DElement *element = m_diagramController->findElement(index.elementKey(), diagram);
774         if (auto selectedObject = dynamic_cast<DObject *>(element)) {
775             if (selectedObject != object) {
776                 QPointF newPos = aligner(object, selectedObject);
777                 if (newPos != selectedObject->pos()) {
778                     m_diagramController->startUpdateElement(selectedObject, diagram, DiagramController::UpdateGeometry);
779                     selectedObject->setPos(newPos);
780                     m_diagramController->finishUpdateElement(selectedObject, diagram, false);
781                 }
782             }
783         }
784     }
785 }
786 
alignSize(DObject * object,const DSelection & selection,const QSizeF & minimumSize,QRectF (* aligner)(DObject *,const QSizeF &),MDiagram * diagram)787 void DiagramSceneController::alignSize(DObject *object, const DSelection &selection, const QSizeF &minimumSize,
788                                        QRectF (*aligner)(DObject *, const QSizeF &), MDiagram *diagram)
789 {
790     QSizeF size;
791     if (object->rect().width() < minimumSize.width())
792         size.setWidth(minimumSize.width());
793     else
794         size.setWidth(object->rect().width());
795     if (object->rect().height() < minimumSize.height())
796         size.setHeight(minimumSize.height());
797     else
798         size.setHeight(object->rect().height());
799     foreach (const DSelection::Index &index, selection.indices()) {
800         DElement *element = m_diagramController->findElement(index.elementKey(), diagram);
801         if (auto selectedObject = dynamic_cast<DObject *>(element)) {
802             QRectF newRect = aligner(selectedObject, size);
803             if (newRect != selectedObject->rect()) {
804                 m_diagramController->startUpdateElement(selectedObject, diagram, DiagramController::UpdateGeometry);
805                 selectedObject->setAutoSized(false);
806                 selectedObject->setRect(newRect);
807                 m_diagramController->finishUpdateElement(selectedObject, diagram, false);
808             }
809         }
810     }
811 }
812 
alignOnRaster(DElement * element,MDiagram * diagram)813 void DiagramSceneController::alignOnRaster(DElement *element, MDiagram *diagram)
814 {
815     AlignOnRasterVisitor visitor;
816     visitor.setDiagramController(m_diagramController);
817     visitor.setSceneInspector(m_sceneInspector);
818     visitor.setDiagram(diagram);
819     element->accept(&visitor);
820 }
821 
collectObjects(const DSelection & selection,MDiagram * diagram)822 QList<DObject *> DiagramSceneController::collectObjects(const DSelection &selection, MDiagram *diagram)
823 {
824     QList<DObject *> list;
825     foreach (const DSelection::Index &index, selection.indices()) {
826         DObject *object = m_diagramController->findElement<DObject>(index.elementKey(), diagram);
827         if (object)
828             list.append(object);
829     }
830     return list;
831 }
832 
addModelElement(const Uid & modelElementKey,const QPointF & pos,MDiagram * diagram)833 DElement *DiagramSceneController::addModelElement(const Uid &modelElementKey, const QPointF &pos, MDiagram *diagram)
834 {
835     DElement *element = nullptr;
836     if (MObject *modelObject = m_modelController->findObject(modelElementKey)) {
837         element = addObject(modelObject, pos, diagram);
838     } else if (MRelation *modelRelation = m_modelController->findRelation(modelElementKey)) {
839         element = addRelation(modelRelation, QList<QPointF>(), diagram);
840     } else {
841         QMT_CHECK(false);
842     }
843     return element;
844 }
845 
addObject(MObject * modelObject,const QPointF & pos,MDiagram * diagram)846 DObject *DiagramSceneController::addObject(MObject *modelObject, const QPointF &pos, MDiagram *diagram)
847 {
848     QMT_ASSERT(modelObject, return nullptr);
849 
850     if (m_diagramController->hasDelegate(modelObject, diagram))
851         return nullptr;
852 
853     m_diagramController->undoController()->beginMergeSequence(tr("Add Element"));
854 
855     DFactory factory;
856     modelObject->accept(&factory);
857     auto diagramObject = dynamic_cast<DObject *>(factory.product());
858     QMT_ASSERT(diagramObject, return nullptr);
859     diagramObject->setPos(pos);
860     m_diagramController->addElement(diagramObject, diagram);
861     alignOnRaster(diagramObject, diagram);
862 
863     // add all relations between any other element on diagram and new element
864     foreach (DElement *delement, diagram->diagramElements()) {
865         if (delement != diagramObject) {
866             auto dobject = dynamic_cast<DObject *>(delement);
867             if (dobject) {
868                 MObject *mobject = m_modelController->findObject(dobject->modelUid());
869                 if (mobject) {
870                     for (const Handle<MRelation> &handle : mobject->relations()) {
871                         if (handle.hasTarget()
872                                 && ((handle.target()->endAUid() == modelObject->uid()
873                                      && handle.target()->endBUid() == mobject->uid())
874                                     || (handle.target()->endAUid() == mobject->uid()
875                                         && handle.target()->endBUid() == modelObject->uid()))) {
876                             addRelation(handle.target(), QList<QPointF>(), diagram);
877                         }
878                     }
879                     for (const Handle<MRelation> &handle : modelObject->relations()) {
880                         if (handle.hasTarget()
881                                 && ((handle.target()->endAUid() == modelObject->uid()
882                                      && handle.target()->endBUid() == mobject->uid())
883                                     || (handle.target()->endAUid() == mobject->uid()
884                                         && handle.target()->endBUid() == modelObject->uid()))) {
885                             addRelation(handle.target(), QList<QPointF>(), diagram);
886                         }
887                     }
888                 }
889             }
890         }
891     }
892 
893     // add all self relations
894     for (const Handle<MRelation> &handle : modelObject->relations()) {
895         if (handle.hasTarget ()
896                 && handle.target()->endAUid() == modelObject->uid()
897                 && handle.target()->endBUid() == modelObject->uid()) {
898             addRelation(handle.target(), QList<QPointF>(), diagram);
899         }
900     }
901 
902     m_diagramController->undoController()->endMergeSequence();
903 
904     return diagramObject;
905 }
906 
addRelation(MRelation * modelRelation,const QList<QPointF> & intermediatePoints,MDiagram * diagram)907 DRelation *DiagramSceneController::addRelation(MRelation *modelRelation, const QList<QPointF> &intermediatePoints,
908                                                MDiagram *diagram)
909 {
910     QMT_ASSERT(modelRelation, return nullptr);
911 
912     if (m_diagramController->hasDelegate(modelRelation, diagram))
913         return nullptr;
914 
915     DFactory factory;
916     modelRelation->accept(&factory);
917     auto diagramRelation = dynamic_cast<DRelation *>(factory.product());
918     QMT_ASSERT(diagramRelation, return nullptr);
919 
920     MObject *endAModelObject = m_modelController->findObject(modelRelation->endAUid());
921     QMT_ASSERT(endAModelObject, return nullptr);
922     DObject *endADiagramObject = m_diagramController->findDelegate<DObject>(endAModelObject, diagram);
923     QMT_ASSERT(endADiagramObject, return nullptr);
924     diagramRelation->setEndAUid(endADiagramObject->uid());
925 
926     MObject *endBModelObject = m_modelController->findObject(modelRelation->endBUid());
927     QMT_ASSERT(endBModelObject, return nullptr);
928     DObject *endBDiagramObject = m_diagramController->findDelegate<DObject>(endBModelObject, diagram);
929     QMT_ASSERT(endBDiagramObject, return nullptr);
930     diagramRelation->setEndBUid(endBDiagramObject->uid());
931 
932     QList<DRelation::IntermediatePoint> relationPoints;
933     if (endADiagramObject->uid() == endBDiagramObject->uid() && intermediatePoints.isEmpty()) {
934         // create some intermediate points for self-relation
935         QRectF rect = endADiagramObject->rect().translated(endADiagramObject->pos());
936         static const qreal EDGE_RADIUS = 30.0;
937         qreal w = rect.width() * 0.25;
938         if (w > EDGE_RADIUS)
939             w = EDGE_RADIUS;
940         qreal h = rect.height() * 0.25;
941         if (h > EDGE_RADIUS)
942             h = EDGE_RADIUS;
943         QPointF i1(rect.x() - EDGE_RADIUS, rect.bottom() - h);
944         QPointF i2(rect.x() - EDGE_RADIUS, rect.bottom() + EDGE_RADIUS);
945         QPointF i3(rect.x() + w, rect.bottom() + EDGE_RADIUS);
946         relationPoints.append(DRelation::IntermediatePoint(i1));
947         relationPoints.append(DRelation::IntermediatePoint(i2));
948         relationPoints.append(DRelation::IntermediatePoint(i3));
949     } else {
950         foreach (const QPointF &intermediatePoint, intermediatePoints)
951             relationPoints.append(DRelation::IntermediatePoint(intermediatePoint));
952     }
953     diagramRelation->setIntermediatePoints(relationPoints);
954 
955     m_diagramController->addElement(diagramRelation, diagram);
956     alignOnRaster(diagramRelation, diagram);
957 
958     return diagramRelation;
959 }
960 
relocateRelationEnd(DRelation * relation,DObject * targetObject,RelationEnd relationEnd,Uid (MRelation::* endUid)()const,void (MRelation::* setEndUid)(const Uid &))961 bool DiagramSceneController::relocateRelationEnd(DRelation *relation, DObject *targetObject,
962                                                  RelationEnd relationEnd,
963                                                  Uid (MRelation::*endUid)() const,
964                                                  void (MRelation::*setEndUid)(const Uid &))
965 {
966     QMT_ASSERT(relation, return false);
967     if (targetObject && targetObject->uid() != relation->endAUid()) {
968         MRelation *modelRelation = m_modelController->findRelation(relation->modelUid());
969         QMT_ASSERT(modelRelation, return false);
970         MObject *targetMObject = m_modelController->findObject(targetObject->modelUid());
971         QMT_ASSERT(targetMObject, return false);
972         AcceptRelationVisitor visitor(m_stereotypeController, modelRelation, relationEnd);
973         targetMObject->accept(&visitor);
974         if (visitor.isAccepted()) {
975             MObject *currentTargetMObject = m_modelController->findObject((modelRelation->*endUid)());
976             QMT_ASSERT(currentTargetMObject, return false);
977             m_modelController->undoController()->beginMergeSequence(tr("Relocate Relation"));
978             // move relation into new target if it was a child of the old target
979             if (currentTargetMObject == modelRelation->owner())
980                 m_modelController->moveRelation(targetMObject, modelRelation);
981             // remove relation on all diagrams where the new targe element does not exist
982             foreach (MDiagram *diagram, m_diagramController->allDiagrams()) {
983                 if (DElement *diagramRelation = m_diagramController->findDelegate(modelRelation, diagram)) {
984                     if (!m_diagramController->findDelegate(targetMObject, diagram)) {
985                         m_diagramController->removeElement(diagramRelation, diagram);
986                     }
987                 }
988             }
989             // update end of relation
990             m_modelController->startUpdateRelation(modelRelation);
991             (modelRelation->*setEndUid)(targetMObject->uid());
992             m_modelController->finishUpdateRelation(modelRelation, false);
993             m_modelController->undoController()->endMergeSequence();
994             return true;
995         }
996     }
997     return false;
998 }
999 
1000 } // namespace qmt
1001