1 /*
2     SPDX-License-Identifier: GPL-2.0-or-later
3     SPDX-FileCopyrightText: 2002-2021 Umbrello UML Modeller Authors <umbrello-devel@kde.org>
4 */
5 
6 // own header
7 #include "associationwidget.h"
8 
9 // app includes
10 #include "association.h"
11 #include "associationline.h"
12 #include "associationpropertiesdialog.h"
13 #include "associationwidgetpopupmenu.h"
14 #include "assocrules.h"
15 #include "attribute.h"
16 #include "classifier.h"
17 #include "classifierwidget.h"
18 #include "debug_utils.h"
19 #include "dialog_utils.h"
20 #include "docwindow.h"
21 #include "entity.h"
22 #include "floatingtextwidget.h"
23 #include "messagewidget.h"
24 #include "objectwidget.h"
25 #include "operation.h"
26 #include "optionstate.h"
27 #include "uml.h"
28 #include "umldoc.h"
29 #include "umlscene.h"
30 #include "umlview.h"
31 #include "umlwidget.h"
32 #include "widget_utils.h"
33 #include "instance.h"
34 #include "instanceattribute.h"
35 
36 // kde includes
37 #if QT_VERSION < 0x050000
38 #include <kcolordialog.h>
39 #include <kfontdialog.h>
40 #endif
41 #include <KLocalizedString>
42 
43 // qt includes
44 #if QT_VERSION >= 0x050000
45 #include <QColorDialog>
46 #include <QFontDialog>
47 #endif
48 #include <QPainterPath>
49 #include <QPointer>
50 #include <QApplication>
51 #include <QXmlStreamWriter>
52 
53 // system includes
54 #include <cmath>
55 
56 #define DBG_AW() DEBUG(QLatin1String("AssociationWidget"))
57 DEBUG_REGISTER_DISABLED(AssociationWidget)
58 
59 using namespace Uml;
60 
61 /**
62   * Constructor is private because the static create() methods shall
63   * be used for constructing AssociationWidgets.
64   *
65   * @param scene   The parent view of this widget.
66   */
AssociationWidget(UMLScene * scene)67 AssociationWidget::AssociationWidget(UMLScene *scene)
68   : WidgetBase(scene, WidgetBase::wt_Association),
69     m_positions_len(0),
70     m_activated(false),
71     m_unNameLineSegment(-1),
72     m_nLinePathSegmentIndex(-1),
73     m_pAssocClassLine(0),
74     m_pAssocClassLineSel0(0),
75     m_pAssocClassLineSel1(0),
76     m_associationLine(new AssociationLine(this)),
77     m_associationClass(0),
78     m_associationType(Uml::AssociationType::Association),
79     m_nameWidget(0)
80 {
81     m_role[0].setParent(this);
82     m_role[1].setParent(this);
83     // propagate line color and width set by base class constructor
84     // which does not call the virtual methods from this class.
85     setLineColor(lineColor());
86     setLineWidth(lineWidth());
87 
88     setFlag(QGraphicsLineItem::ItemIsSelectable);
89     setAcceptHoverEvents(true);
90 }
91 
92 /**
93  * This constructor is really only for loading from XMI, otherwise it
94  * should not be allowed as it creates an incomplete associationwidget.
95   *
96   * @param scene   The parent view of this widget.
97  */
create(UMLScene * scene)98 AssociationWidget* AssociationWidget::create(UMLScene *scene)
99 {
100     AssociationWidget* instance = new AssociationWidget(scene);
101     return instance;
102 }
103 
104 /**
105   * Preferred constructor (static factory method.)
106   *
107   * @param scene      The parent view of this widget.
108   * @param pWidgetA   Pointer to the role A widget for the association.
109   * @param assocType  The AssociationType::Enum for this association.
110   * @param pWidgetB   Pointer to the role B widget for the association.
111   * @param umlobject  Pointer to the underlying UMLObject (if applicable.)
112   */
create(UMLScene * scene,UMLWidget * pWidgetA,Uml::AssociationType::Enum assocType,UMLWidget * pWidgetB,UMLObject * umlobject)113 AssociationWidget* AssociationWidget::create
114                                     (UMLScene *scene, UMLWidget* pWidgetA,
115                                      Uml::AssociationType::Enum assocType, UMLWidget* pWidgetB,
116                                      UMLObject *umlobject /* = 0 */)
117 {
118     AssociationWidget* instance = new AssociationWidget(scene);
119     if (umlobject) {
120         instance->setUMLObject(umlobject);
121     } else {
122         // set up UMLAssociation obj if assoc is represented and both roles are UML objects
123         if (Uml::AssociationType::hasUMLRepresentation(assocType)) {
124             UMLObject* umlRoleA = pWidgetA->umlObject();
125             UMLObject* umlRoleB = pWidgetB->umlObject();
126             if (umlRoleA != 0 && umlRoleB != 0) {
127                 bool swap;
128 
129                 // This is not correct. We could very easily have more than one
130                 // of the same type of association between the same two objects.
131                 // Just create the association. This search should have been
132                 // done BEFORE creation of the widget, if it mattered to the code.
133                 // But lets leave check in here for the time being so that debugging
134                 // output is shown, in case there is a collision with code elsewhere.
135                 UMLDoc *doc = UMLApp::app()->document();
136                 UMLAssociation *myAssoc = doc->findAssociation(assocType, umlRoleA, umlRoleB, &swap);
137                 if (myAssoc != 0) {
138                     switch (assocType) {
139                         case Uml::AssociationType::Generalization:
140                         case Uml::AssociationType::Dependency:
141                         case Uml::AssociationType::Association_Self:
142                         case Uml::AssociationType::Coll_Mesg_Self:
143                         case Uml::AssociationType::Seq_Message_Self:
144                         case Uml::AssociationType::Containment:
145                         case Uml::AssociationType::Realization:
146                             DBG_AW() << "Ignoring second construction of same assoctype "
147                                      << assocType << " between " << umlRoleA->name()
148                                      << " and " << umlRoleB->name();
149                             break;
150                         default:
151                             DBG_AW() << "constructing a similar or exact same assoctype "
152                                      << assocType << " between " << umlRoleA->name() << " and "
153                                      << umlRoleB->name() << "as an already existing assoc (swap="
154                                      << swap << ")";
155                             // now, just create a new association anyways
156                             myAssoc = 0;
157                             break;
158                     }
159                 }
160                 if (myAssoc == 0) {
161                     myAssoc = new UMLAssociation(assocType, umlRoleA, umlRoleB);
162                     // CHECK: myAssoc is not yet inserted at any parent UMLPackage -
163                     // need to check carefully that all callers do this, lest it be
164                     // orphaned.
165                     // ToolBarStateAssociation::addAssociationInViewAndDoc() is
166                     // okay in this regard.
167                 }
168                 instance->setUMLAssociation(myAssoc);
169             }
170         }
171     }
172 
173     instance->setWidgetForRole(pWidgetA, RoleType::A);
174     instance->setWidgetForRole(pWidgetB, RoleType::B);
175 
176     instance->setAssociationType(assocType);
177 
178     instance->calculateEndingPoints();
179 
180     instance->associationLine()->calculateInitialEndPoints();
181     instance->associationLine()->reconstructSymbols();
182 
183     //The AssociationWidget is set to Activated because it already has its side widgets
184     instance->setActivated(true);
185 
186     // sync UML meta-data to settings here
187     instance->mergeAssociationDataIntoUMLRepresentation();
188 
189     // Collaboration messages need a name label because it's that
190     // which lets operator== distinguish them, which in turn
191     // permits us to have more than one message between two objects.
192     if (instance->isCollaboration()) {
193         // Create a temporary name to bring on setName()
194         int collabID = instance->m_scene->generateCollaborationId();
195         instance->setName(QLatin1Char('m') + QString::number(collabID));
196     }
197 
198     return instance;
199 }
200 
201 /**
202  * Destructor.
203  */
~AssociationWidget()204 AssociationWidget::~AssociationWidget()
205 {
206     cleanup();
207     delete m_associationLine;
208 }
209 
210 /**
211  * Overriding the method from WidgetBase because we need to do
212  * something extra in case this AssociationWidget represents
213  * an attribute of a classifier.
214  */
setUMLObject(UMLObject * obj)215 void AssociationWidget::setUMLObject(UMLObject *obj)
216 {
217     WidgetBase::setUMLObject(obj);
218     if (obj == 0)
219         return;
220     UMLClassifier *klass = 0;
221     UMLAttribute *attr = 0;
222     UMLEntity *ent = 0;
223     const UMLObject::ObjectType ot = obj->baseType();
224     switch (ot) {
225         case UMLObject::ot_Association:
226             setUMLAssociation(obj->asUMLAssociation());
227             break;
228         case UMLObject::ot_Operation:
229             setOperation(obj->asUMLOperation());
230             break;
231         case UMLObject::ot_Attribute:
232             klass = obj->umlParent()->asUMLClassifier();
233             connect(klass, SIGNAL(attributeRemoved(UMLClassifierListItem*)),
234                     this, SLOT(slotClassifierListItemRemoved(UMLClassifierListItem*)));
235             attr = obj->asUMLAttribute();
236             connect(attr, SIGNAL(attributeChanged()), this, SLOT(slotAttributeChanged()));
237             break;
238         case UMLObject::ot_EntityAttribute:
239             ent = obj->umlParent()->asUMLEntity();
240             connect(ent, SIGNAL(entityAttributeRemoved(UMLClassifierListItem*)),
241                     this, SLOT(slotClassifierListItemRemoved(UMLClassifierListItem*)));
242             break;
243         case UMLObject::ot_ForeignKeyConstraint:
244             ent = obj->umlParent()->asUMLEntity();
245             connect(ent, SIGNAL(entityConstraintRemoved(UMLClassifierListItem*)),
246                     this, SLOT(slotClassifierListItemRemoved(UMLClassifierListItem*)));
247             break;
248         /* TODO  It is not clear that we need associations to InstanceAttribute,
249                  associations to Attribute should suffice ?
250         case UMLObject::ot_InstanceAttribute:
251             connect(obj->umlParent(), SIGNAL(attributeRemoved()), this, SLOT(slotClassifierListItemRemoved()));
252             attr = obj->asUMLInstanceAttribute();
253             connect(attr, SIGNAL(attributeChanged()), this, SLOT(slotAttributeChanged()));
254             break;
255          */
256         default:
257             uError() << "cannot associate UMLObject of type " << UMLObject::toString(ot);
258             break;
259     }
260 }
261 
262 /**
263  * Set all 'owned' child widgets to this font.
264  */
lwSetFont(QFont font)265 void AssociationWidget::lwSetFont (QFont font)
266 {
267     if (m_nameWidget) {
268         m_nameWidget->setFont(font);
269     }
270     m_role[RoleType::A].setFont(font);
271     m_role[RoleType::B].setFont(font);
272 }
273 
274 /**
275  * Overrides operation from LinkWidget.
276  * Required by FloatingTextWidget.
277  * @todo Move to LinkWidget.
278  */
operationOwner()279 UMLClassifier *AssociationWidget::operationOwner()
280 {
281     Uml::RoleType::Enum role = (isCollaboration() ? Uml::RoleType::B : Uml::RoleType::A);
282     UMLObject *o = widgetForRole(role)->umlObject();
283     if (!o) {
284         return 0;
285     }
286     UMLClassifier *c = o->asUMLClassifier();
287     if (!c) {
288         uError() << "widgetForRole(" << role << ") is not a classifier";
289     }
290     return c;
291 }
292 
293 /**
294  * Implements operation from LinkWidget.
295  * Motivated by FloatingTextWidget.
296  */
operation()297 UMLOperation *AssociationWidget::operation()
298 {
299     return m_umlObject->asUMLOperation();
300 }
301 
302 /**
303  * Implements operation from LinkWidget.
304  * Motivated by FloatingTextWidget.
305  */
setOperation(UMLOperation * op)306 void AssociationWidget::setOperation(UMLOperation *op)
307 {
308     if (m_umlObject)
309         disconnect(m_umlObject, SIGNAL(modified()), m_nameWidget, SLOT(setMessageText()));
310     m_umlObject = op;
311     if (m_umlObject)
312         connect(m_umlObject, SIGNAL(modified()), m_nameWidget, SLOT(setMessageText()));
313     if (m_nameWidget)
314         m_nameWidget->setMessageText();
315 }
316 
317 /**
318  * Overrides operation from LinkWidget.
319  * Required by FloatingTextWidget.
320  */
customOpText()321 QString AssociationWidget::customOpText()
322 {
323     return name();
324 }
325 
326 /**
327  * Overrides operation from LinkWidget.
328  * Required by FloatingTextWidget.
329  */
setCustomOpText(const QString & opText)330 void AssociationWidget::setCustomOpText(const QString &opText)
331 {
332     setName(opText);
333 }
334 
335 /**
336  * Calls setTextPosition on all the labels.
337  * Overrides operation from LinkWidget.
338  */
resetTextPositions()339 void AssociationWidget::resetTextPositions()
340 {
341     if (m_role[RoleType::A].multiplicityWidget) {
342         setTextPosition(TextRole::MultiA);
343     }
344     if (m_role[RoleType::B].multiplicityWidget) {
345         setTextPosition(Uml::TextRole::MultiB);
346     }
347     if (m_role[RoleType::A].changeabilityWidget) {
348         setTextPosition(Uml::TextRole::ChangeA);
349     }
350     if (m_role[RoleType::B].changeabilityWidget) {
351         setTextPosition(Uml::TextRole::ChangeB);
352     }
353     if (m_nameWidget) {
354         setTextPosition(Uml::TextRole::Name);
355     }
356     if (m_role[RoleType::A].roleWidget) {
357         setTextPosition(Uml::TextRole::RoleAName);
358     }
359     if (m_role[RoleType::B].roleWidget) {
360         setTextPosition(Uml::TextRole::RoleBName);
361     }
362 }
363 
364 /**
365  * Overrides operation from LinkWidget.
366  * Required by FloatingTextWidget.
367  *
368  * @param ft   The text widget which to update.
369  */
setMessageText(FloatingTextWidget * ft)370 void AssociationWidget::setMessageText(FloatingTextWidget *ft)
371 {
372     if (isCollaboration()) {
373         ft->setSequenceNumber(m_SequenceNumber);
374         if (m_umlObject != 0) {
375             ft->setText(operationText(m_scene));
376         } else {
377             ft->setText(name());
378         }
379     } else {
380         ft->setText(name());
381     }
382 }
383 
384 /**
385  * Sets the text of the given FloatingTextWidget.
386  * Overrides operation from LinkWidget.
387  * Required by FloatingTextWidget.
388  */
setText(FloatingTextWidget * ft,const QString & text)389 void AssociationWidget::setText(FloatingTextWidget *ft, const QString &text)
390 {
391     Uml::TextRole::Enum role = ft->textRole();
392     switch (role) {
393         case Uml::TextRole::Name:
394             setName(text);
395             break;
396         case Uml::TextRole::RoleAName:
397             setRoleName(text, RoleType::A);
398             break;
399         case Uml::TextRole::RoleBName:
400             setRoleName(text, RoleType::B);
401             break;
402         case Uml::TextRole::MultiA:
403             setMultiplicity(text, RoleType::A);
404             break;
405         case Uml::TextRole::MultiB:
406             setMultiplicity(text, RoleType::B);
407             break;
408         default:
409             uWarning() << "Unhandled TextRole: " << Uml::TextRole::toString(role);
410             break;
411     }
412 }
413 
414 /**
415  * Shows the association properties dialog and updates the
416  * corresponding texts if its execution is successful.
417  */
showPropertiesDialog()418 bool AssociationWidget::showPropertiesDialog()
419 {
420     bool result = false;
421     UMLApp::app()->docWindow()->updateDocumentation();
422     QPointer<AssociationPropertiesDialog> dlg = new AssociationPropertiesDialog(static_cast<QWidget*>(m_scene->activeView()), this);
423     if (dlg->exec()) {
424         UMLApp::app()->docWindow()->showDocumentation(this, true);
425         result = true;
426     }
427     delete dlg;
428     return result;
429 }
430 
431 /**
432  * Overrides operation from LinkWidget.
433  * Required by FloatingTextWidget.
434  */
lwOperationText()435 QString AssociationWidget::lwOperationText()
436 {
437     return name();
438 }
439 
440 /**
441  * Overrides operation from LinkWidget.
442  * Required by FloatingTextWidget.
443  *
444  * @return classifier
445  */
lwClassifier()446 UMLClassifier* AssociationWidget::lwClassifier()
447 {
448     UMLObject *o = widgetForRole(RoleType::B)->umlObject();
449     UMLClassifier *c = o->asUMLClassifier();
450     return c;
451 }
452 
453 /**
454  * Overrides operation from LinkWidget.
455  * Required by FloatingTextWidget.
456  *
457  * @param op       The new operation string to set.
458  */
setOperationText(const QString & op)459 void AssociationWidget::setOperationText(const QString &op)
460 {
461     if (!op.isEmpty()) {
462         setName(op);
463     }
464 }
465 
466 /**
467  * Calculates the m_unNameLineSegment value according to the new
468  * NameText topleft corner PT.
469  * It iterates through all AssociationLine's segments and for each one
470  * calculates the sum of PT's distance to the start point + PT's
471  * distance to the end point. The segment with the smallest sum will
472  * be the RoleTextSegment (if this segment moves then the RoleText
473  * will move with it). It sets m_unNameLineSegment to the start point
474  * of the chosen segment.
475  *
476  * Overrides operation from LinkWidget (i.e. this method is also
477  * required by FloatingTextWidget.)
478  */
calculateNameTextSegment()479 void AssociationWidget::calculateNameTextSegment()
480 {
481     if (!m_nameWidget) {
482         return;
483     }
484     //changed to use the middle of the text
485     //i think this will give a better result.
486     //never know what sort of lines people come up with
487     //and text could be long to give a false reading
488     qreal xt = m_nameWidget->x();
489     qreal yt = m_nameWidget->y();
490     xt += m_nameWidget->width() / 2;
491     yt += m_nameWidget->height() / 2;
492     int size = m_associationLine->count();
493     //sum of length(PTP1) and length(PTP2)
494     qreal total_length = 0;
495     qreal smallest_length = 0;
496     for (int i = 0; i < size - 1; ++i) {
497         QPointF pi = m_associationLine->point( i );
498         QPointF pj = m_associationLine->point( i+1 );
499         qreal xtiDiff = xt - pi.x();
500         qreal xtjDiff = xt - pj.x();
501         qreal ytiDiff = yt - pi.y();
502         qreal ytjDiff = yt - pj.y();
503         total_length =  sqrt( double(xtiDiff * xtiDiff + ytiDiff * ytiDiff) )
504                         + sqrt( double(xtjDiff * xtjDiff + ytjDiff * ytjDiff) );
505         //this gives the closest point
506         if (total_length < smallest_length || i == 0) {
507             smallest_length = total_length;
508             m_unNameLineSegment = i;
509         }
510     }
511 }
512 
513 /**
514  * Returns the UMLAssociation representation of this object.
515  *
516  * @return  Pointer to the UMLAssociation that is represented by
517  *          this AsociationWidget.
518  */
association() const519 UMLAssociation* AssociationWidget::association() const
520 {
521     if (m_umlObject == 0 || m_umlObject->baseType() != UMLObject::ot_Association)
522         return 0;
523     return m_umlObject->asUMLAssociation();
524 }
525 
526 /**
527  * Returns the UMLAttribute representation of this object.
528  *
529  * @return  Pointer to the UMLAttribute that is represented by
530  *          this AsociationWidget.
531  */
attribute() const532 UMLAttribute* AssociationWidget::attribute() const
533 {
534     if (m_umlObject == 0)
535         return 0;
536     UMLObject::ObjectType ot = m_umlObject->baseType();
537     if (ot != UMLObject::ot_Attribute && ot != UMLObject::ot_EntityAttribute && ot != UMLObject::ot_InstanceAttribute)
538         return 0;
539     return m_umlObject->asUMLAttribute();
540 }
541 
542 #if 0  //:TODO:
543 /**
544  * Overrides the assignment operator.
545  */
546 AssociationWidget& AssociationWidget::operator=(const AssociationWidget& other)
547 {
548     *m_associationLine = *other.m_associationLine;
549 
550     if (other.m_nameWidget) {
551         m_nameWidget = new FloatingTextWidget(m_scene);
552         *m_nameWidget = *(other.m_nameWidget);
553     } else {
554         m_nameWidget = 0;
555     }
556 
557     for (unsigned r = (unsigned)A; r <= (unsigned)B; ++r) {
558         WidgetRole& lhs = m_role[r];
559         const WidgetRole& rhs = other.m_role[r];
560         lhs.m_nIndex = rhs.m_nIndex;
561         lhs.m_nTotalCount = rhs.m_nTotalCount;
562 
563         if (rhs.multiplicityWidget) {
564             lhs.multiplicityWidget = new FloatingTextWidget(m_scene);
565             *(lhs.multiplicityWidget) = *(rhs.multiplicityWidget);
566         } else {
567             lhs.multiplicityWidget = 0;
568         }
569 
570         if (rhs.roleWidget) {
571             lhs.roleWidget = new FloatingTextWidget(m_scene);
572             *(lhs.roleWidget) = *(rhs.roleWidget);
573         } else {
574             lhs.roleWidget = 0;
575         }
576 
577         if (rhs.changeabilityWidget) {
578             lhs.changeabilityWidget = new FloatingTextWidget(m_scene);
579             *(lhs.changeabilityWidget) = *(rhs.changeabilityWidget);
580         } else {
581             lhs.changeabilityWidget = 0;
582         }
583 
584         lhs.umlWidget = rhs.umlWidget;
585         lhs.m_WidgetRegion = rhs.m_WidgetRegion;
586     }
587 
588     m_activated = other.m_activated;
589     m_unNameLineSegment = other.m_unNameLineSegment;
590     setUMLAssociation(other.association());
591     setSelected(other.isSelected());
592 
593     return *this;
594 }
595 #endif  //:TODO:
596 
597 /**
598  * Overrides the equality test operator.
599  */
operator ==(const AssociationWidget & other) const600 bool AssociationWidget::operator==(const AssociationWidget& other) const
601 {
602     if (this == &other)
603         return true;
604 
605     // if no model representation exists, then the widgets are not equal
606     if (association() == 0 && other.association() == 0)
607         return false;
608 
609     if (!m_umlObject || !other.m_umlObject ) {
610         if (!other.m_umlObject && m_umlObject)
611             return false;
612         if (other.m_umlObject && !m_umlObject)
613             return false;
614 
615     } else if (m_umlObject != other.m_umlObject)
616         return false;
617 
618     if (associationType() != other.associationType())
619         return false;
620 
621     if (widgetIDForRole(RoleType::A) != other.widgetIDForRole(RoleType::A))
622         return false;
623 
624     if (widgetIDForRole(RoleType::B) != other.widgetIDForRole(RoleType::B))
625         return false;
626 
627     if (widgetForRole(RoleType::A)->isObjectWidget() &&
628             other.widgetForRole(RoleType::A)->isObjectWidget()) {
629         if (widgetForRole(RoleType::A)->localID() != other.widgetForRole(RoleType::A)->localID())
630             return false;
631     }
632 
633     if (widgetForRole(RoleType::B)->isObjectWidget() &&
634             other.widgetForRole(RoleType::B)->isObjectWidget()) {
635         if (widgetForRole(RoleType::B)->localID() != other.widgetForRole(RoleType::B)->localID())
636             return false;
637     }
638 
639     // Two objects in a collaboration can have multiple messages between each other.
640     // Here we depend on the messages having names, and the names must be different.
641     // That is the reason why collaboration messages have strange initial names like
642     // "m29997" or similar.
643     return (name() == other.name());
644 }
645 
646 /**
647  * Overrides the != operator.
648  */
operator !=(AssociationWidget & other) const649 bool AssociationWidget::operator!=(AssociationWidget& other) const
650 {
651     return !(*this == other);
652 }
653 
654 /**
655  * Returns a pointer to the association widget's line path.
656  */
associationLine() const657 AssociationLine* AssociationWidget::associationLine() const
658 {
659     return m_associationLine;
660 }
661 
662 /**
663  * Activates the AssociationWidget after a load.
664  *
665  * @return  true for success
666  */
activate(IDChangeLog * changeLog)667 bool AssociationWidget::activate(IDChangeLog *changeLog)
668 {
669     Q_UNUSED(changeLog);
670 
671     if (m_umlObject == 0 &&
672         AssociationType::hasUMLRepresentation(m_associationType)) {
673         UMLObject *myObj = umlDoc()->findObjectById(m_nId);
674         if (myObj == 0) {
675             uError() << "cannot find UMLObject " << Uml::ID::toString(m_nId);
676             return false;
677         } else {
678             const UMLObject::ObjectType ot = myObj->baseType();
679             if (ot == UMLObject::ot_Association) {
680                 UMLAssociation * myAssoc = myObj->asUMLAssociation();
681                 setUMLAssociation(myAssoc);
682             } else {
683                 setUMLObject(myObj);
684                 setAssociationType(m_associationType);
685             }
686         }
687     }
688 
689     if (m_activated)
690         return true;
691 
692     Uml::AssociationType::Enum type = associationType();
693 
694     if (m_role[RoleType::A].umlWidget == 0)
695         setWidgetForRole(m_scene->findWidget(widgetIDForRole(RoleType::A)), RoleType::A);
696     if (m_role[RoleType::B].umlWidget == 0)
697         setWidgetForRole(m_scene->findWidget(widgetIDForRole(RoleType::B)), RoleType::B);
698 
699     if (!m_role[RoleType::A].umlWidget || !m_role[RoleType::B].umlWidget) {
700         DEBUG(DBG_SRC) << "Cannot make association!";
701         return false;
702     }
703 
704     calculateEndingPoints();
705 
706     if (AssocRules::allowRole(type)) {
707         for (unsigned r = RoleType::A; r <= RoleType::B; ++r) {
708             AssociationWidgetRole& robj = m_role[r];
709             if (robj.roleWidget == 0)
710                 continue;
711             robj.roleWidget->setLink(this);
712             TextRole::Enum tr = (r == RoleType::A ? TextRole::RoleAName : TextRole::RoleBName);
713             robj.roleWidget->setTextRole(tr);
714             Uml::Visibility::Enum vis = visibility(Uml::RoleType::fromInt(r));
715             robj.roleWidget->setPreText(Uml::Visibility::toString(vis, true));
716 
717             if (FloatingTextWidget::isTextValid(robj.roleWidget->text()))
718                 robj.roleWidget->show();
719             else
720                 robj.roleWidget->hide();
721             if (m_scene->type() == DiagramType::Collaboration)
722                 robj.roleWidget->setUMLObject(robj.umlWidget->umlObject());
723             robj.roleWidget->activate();
724         }
725     }
726 
727     if (m_nameWidget != 0) {
728         m_nameWidget->setLink(this);
729         m_nameWidget->setTextRole(calculateNameType(TextRole::Name));
730 
731         if (FloatingTextWidget::isTextValid(m_nameWidget->text())) {
732             m_nameWidget->show();
733         } else {
734             m_nameWidget->hide();
735         }
736         m_nameWidget->activate();
737         calculateNameTextSegment();
738     }
739 
740     for (unsigned r = RoleType::A; r <= RoleType::B; ++r) {
741         AssociationWidgetRole& robj = m_role[r];
742 
743         FloatingTextWidget* pMulti = robj.multiplicityWidget;
744         if (pMulti != 0 &&
745                 AssocRules::allowMultiplicity(type, robj.umlWidget->baseType())) {
746             pMulti->setLink(this);
747             TextRole::Enum tr = (r == RoleType::A ? TextRole::MultiA : TextRole::MultiB);
748             pMulti->setTextRole(tr);
749             if (FloatingTextWidget::isTextValid(pMulti->text()))
750                 pMulti->show();
751             else
752                 pMulti->hide();
753             pMulti->activate();
754         }
755 
756         FloatingTextWidget* pChangeWidget = robj.changeabilityWidget;
757         if (pChangeWidget != 0) {
758             pChangeWidget->setLink(this);
759             TextRole::Enum tr = (r == RoleType::A ? TextRole::ChangeA : TextRole::ChangeB);
760             pChangeWidget->setTextRole(tr);
761             if (FloatingTextWidget::isTextValid(pChangeWidget->text()))
762                 pChangeWidget->show();
763             else
764                 pChangeWidget->hide ();
765             pChangeWidget->activate();
766         }
767     }
768 
769     // Prepare the association class line if needed.
770     if (m_associationClass && !m_pAssocClassLine) {
771         createAssocClassLine();
772     }
773 
774     m_activated = true;
775     return true;
776 }
777 
778 /**
779  * Set the widget of the given role.
780  * Add this AssociationWidget at the widget.
781  * If this AssociationWidget has an underlying UMLAssociation then set
782  * the widget's underlying UMLObject at the UMLAssociation's role object.
783  *
784  * @param widget    Pointer to the UMLWidget.
785  * @param role      Role for which to set the widget.
786  */
setWidgetForRole(UMLWidget * widget,Uml::RoleType::Enum role)787 void AssociationWidget::setWidgetForRole(UMLWidget* widget, Uml::RoleType::Enum role)
788 {
789     m_role[role].umlWidget = widget;
790     if (widget) {
791         m_role[role].umlWidget->addAssoc(this);
792         if (m_umlObject && m_umlObject->baseType() == UMLObject::ot_Association)
793             association()->setObject(widget->umlObject(), role);
794     }
795 }
796 
797 /**
798  * Return the multiplicity FloatingTextWidget widget of the given role.
799  *
800  * @return  Pointer to the multiplicity FloatingTextWidget object.
801  */
multiplicityWidget(Uml::RoleType::Enum role) const802 FloatingTextWidget* AssociationWidget::multiplicityWidget(Uml::RoleType::Enum role) const
803 {
804     return m_role[role].multiplicityWidget;
805 }
806 
807 /**
808  * Read property of FloatingTextWidget* m_nameWidget.
809  *
810  * @return  Pointer to the FloatingTextWidget name widget.
811  */
nameWidget() const812 FloatingTextWidget* AssociationWidget::nameWidget() const
813 {
814     return m_nameWidget;
815 }
816 
817 /**
818  * Return the given role's FloatingTextWidget object.
819  *
820  * @return  Pointer to the role's FloatingTextWidget widget.
821  */
roleWidget(Uml::RoleType::Enum role) const822 FloatingTextWidget* AssociationWidget::roleWidget(Uml::RoleType::Enum role) const
823 {
824     return m_role[role].roleWidget;
825 }
826 
827 /**
828  * Return the given role's changeability FloatingTextWidget widget.
829  */
changeabilityWidget(Uml::RoleType::Enum role) const830 FloatingTextWidget* AssociationWidget::changeabilityWidget(Uml::RoleType::Enum role) const
831 {
832     return m_role[role].changeabilityWidget;
833 }
834 
835 /**
836  * Return the FloatingTextWidget object indicated by the given TextRole::Enum.
837  *
838  * @return  Pointer to the text role's FloatingTextWidget widget.
839  */
textWidgetByRole(Uml::TextRole::Enum tr) const840 FloatingTextWidget* AssociationWidget::textWidgetByRole(Uml::TextRole::Enum tr) const
841 {
842     switch (tr) {
843         case Uml::TextRole::MultiA:
844             return m_role[RoleType::A].multiplicityWidget;
845         case Uml::TextRole::MultiB:
846             return m_role[RoleType::B].multiplicityWidget;
847         case Uml::TextRole::Name:
848         case Uml::TextRole::Coll_Message:
849             return m_nameWidget;
850         case Uml::TextRole::RoleAName:
851             return m_role[RoleType::A].roleWidget;
852         case Uml::TextRole::RoleBName:
853             return m_role[RoleType::B].roleWidget;
854         case Uml::TextRole::ChangeA:
855             return m_role[RoleType::A].changeabilityWidget;
856         case Uml::TextRole::ChangeB:
857             return m_role[RoleType::B].changeabilityWidget;
858         default:
859             break;
860     }
861     return 0;
862 }
863 
864 /**
865  * Returns the m_nameWidget's text.
866  *
867  * @return  Text of the FloatingTextWidget name widget.
868  */
name() const869 QString AssociationWidget::name() const
870 {
871     if (m_nameWidget == 0)
872         return QString();
873     return m_nameWidget->text();
874 }
875 
876 /**
877  * Sets the text in the FloatingTextWidget widget representing the Name
878  * of this association.
879  */
setName(const QString & strName)880 void AssociationWidget::setName(const QString &strName)
881 {
882     // set attribute of UMLAssociation associated with this associationwidget
883     UMLAssociation *umla = association();
884     if (umla)
885         umla->setName(strName);
886 
887     bool newLabel = false;
888     if (!m_nameWidget) {
889         // Don't construct the FloatingTextWidget if the string is empty.
890         if (! FloatingTextWidget::isTextValid(strName))
891             return;
892 
893         newLabel = true;
894         m_nameWidget = new FloatingTextWidget(m_scene, calculateNameType(Uml::TextRole::Name), strName);
895         m_nameWidget->setParentItem(this);
896         m_nameWidget->setLink(this);
897     } else {
898         m_nameWidget->setText(strName);
899         if (! FloatingTextWidget::isTextValid(strName)) {
900             //m_nameWidget->hide();
901             m_scene->removeWidget(m_nameWidget);
902             m_nameWidget = 0;
903             return;
904         }
905     }
906 
907     setTextPosition(Uml::TextRole::Name);
908     if (newLabel) {
909         m_nameWidget->setActivated();
910         m_scene->addFloatingTextWidget(m_nameWidget);
911     }
912 
913     m_nameWidget->show();
914 }
915 
setStereotype(const QString & stereo)916 void AssociationWidget::setStereotype(const QString &stereo) {
917     UMLAssociation *umlassoc = association();
918     if (umlassoc) {
919         umlassoc->setStereotype(stereo);
920         if (!m_nameWidget) {
921             QString text = umlassoc->stereotype(true);
922             // Don't construct the FloatingTextWidget if the string is empty.
923             if (! FloatingTextWidget::isTextValid(text))
924                 return;
925 
926             m_nameWidget = new FloatingTextWidget(m_scene, calculateNameType(Uml::TextRole::Name), text);
927             m_nameWidget->setParentItem(this);
928             m_nameWidget->setLink(this);
929             m_nameWidget->activate();
930             setTextPosition(calculateNameType(Uml::TextRole::Name));
931         } else {
932             m_nameWidget->setText(umlassoc->stereotype(true));
933         }
934     } else {
935         uDebug() << "not setting " << stereo << " because association is NULL";
936     }
937 }
938 
939 /**
940  * Return the given role's FloatingTextWidget widget text.
941  *
942  * @return  The name set at the FloatingTextWidget.
943  */
roleName(Uml::RoleType::Enum role) const944 QString AssociationWidget::roleName(Uml::RoleType::Enum role) const
945 {
946     if (m_role[role].roleWidget == 0)
947         return QString();
948     return m_role[role].roleWidget->text();
949 }
950 
951 /**
952  * Sets the text to the FloatingTextWidget that display the Role text of this
953  * association.
954  * For this function to work properly, the associated widget
955  *  should already be set.
956  */
setRoleName(const QString & strRole,Uml::RoleType::Enum role)957 void AssociationWidget::setRoleName(const QString &strRole, Uml::RoleType::Enum role)
958 {
959     Uml::AssociationType::Enum type = associationType();
960     //if the association is not supposed to have a Role FloatingTextWidget
961     if (!AssocRules::allowRole(type))  {
962         return;
963     }
964 
965     TextRole::Enum tr = (role == RoleType::A ? TextRole::RoleAName : TextRole::RoleBName);
966     setFloatingText(tr, strRole, m_role[role].roleWidget);
967     if (m_role[role].roleWidget) {
968         Uml::Visibility::Enum vis = visibility(role);
969         if (FloatingTextWidget::isTextValid(m_role[role].roleWidget->text())) {
970             m_role[role].roleWidget->setPreText(Uml::Visibility::toString(vis, true));
971             //m_role[role].roleWidget->show();
972         } else {
973             m_role[role].roleWidget->setPreText(QString());
974             //m_role[role].roleWidget->hide();
975         }
976     }
977 
978     // set attribute of UMLAssociation associated with this associationwidget
979     if (m_umlObject && m_umlObject->baseType() == UMLObject::ot_Association)
980         association()->setRoleName(strRole, role);
981 }
982 
983 /**
984  * Set the documentation on the given role.
985  */
setRoleDocumentation(const QString & doc,Uml::RoleType::Enum role)986 void AssociationWidget::setRoleDocumentation(const QString &doc, Uml::RoleType::Enum role)
987 {
988     if (m_umlObject && m_umlObject->baseType() == UMLObject::ot_Association)
989         association()->setRoleDoc(doc, role);
990     else
991         m_role[role].roleDocumentation = doc;
992 }
993 
994 /**
995  * Returns the given role's documentation.
996  */
roleDocumentation(Uml::RoleType::Enum role) const997 QString AssociationWidget::roleDocumentation(Uml::RoleType::Enum role) const
998 {
999     if (m_umlObject == 0 || m_umlObject->baseType() != UMLObject::ot_Association)
1000         return QString();
1001     UMLAssociation *umla = m_umlObject->asUMLAssociation();
1002     return umla->getRoleDoc(role);
1003 }
1004 
1005 /**
1006  * Change, create, or delete the FloatingTextWidget indicated by the given TextRole::Enum.
1007  *
1008  * @param tr    TextRole::Enum of the FloatingTextWidget to change or create.
1009  * @param text  Text string that controls the action:
1010  *              If empty and ft is NULL then setFloatingText() is a no-op.
1011  *              If empty and ft is non-NULL then the existing ft is deleted.
1012  *              If non-empty and ft is NULL then a new FloatingTextWidget is created
1013  *              and returned in ft with the text set.
1014  *              If non-empty and ft is non-NULL then the existing ft text is modified.
1015  * @param ft    Reference to the pointer to FloatingTextWidget to change or create.
1016  *              On creation/deletion, the pointer value will be changed.
1017  */
setFloatingText(Uml::TextRole::Enum role,const QString & text,FloatingTextWidget * & ft)1018 void AssociationWidget::setFloatingText(Uml::TextRole::Enum role,
1019                                         const QString &text,
1020                                         FloatingTextWidget* &ft)
1021 {
1022     if (! FloatingTextWidget::isTextValid(text)) {
1023         if (ft) {
1024             // Remove preexisting FloatingTextWidget
1025             m_scene->removeWidget(ft);  // physically deletes ft
1026             ft = 0;
1027         }
1028         return;
1029     }
1030 
1031     if (ft == 0) {
1032         ft = new FloatingTextWidget(m_scene, role, text);
1033         ft->setParentItem(this);
1034         ft->setLink(this);
1035         ft->activate();
1036         setTextPosition(role);
1037         m_scene->addFloatingTextWidget(ft);
1038     } else {
1039         bool newLabel = ft->text().isEmpty();
1040         ft->setText(text);
1041         if (newLabel)
1042             setTextPosition(role);
1043     }
1044 
1045     ft->show();
1046 }
1047 
1048 /**
1049  * Return the given role's multiplicity text.
1050  *
1051  * @return  Text of the given role's multiplicity widget.
1052  */
multiplicity(Uml::RoleType::Enum role) const1053 QString AssociationWidget::multiplicity(Uml::RoleType::Enum role) const
1054 {
1055     if (m_role[role].multiplicityWidget == 0)
1056         return QString();
1057     return m_role[role].multiplicityWidget->text();
1058 }
1059 
1060 /**
1061  * Sets the text in the FloatingTextWidget representing the multiplicity
1062  * at the given side of the association.
1063  */
setMultiplicity(const QString & text,Uml::RoleType::Enum role)1064 void AssociationWidget::setMultiplicity(const QString& text, Uml::RoleType::Enum role)
1065 {
1066     TextRole::Enum tr = (role == RoleType::A ? TextRole::MultiA : TextRole::MultiB);
1067 
1068     setFloatingText(tr, text, m_role[role].multiplicityWidget);
1069 
1070     if (m_umlObject && m_umlObject->baseType() == UMLObject::ot_Association)
1071         association()->setMultiplicity(text, role);
1072 }
1073 
1074 /**
1075  * Gets the visibility on the given role of the association.
1076  */
visibility(Uml::RoleType::Enum role) const1077 Visibility::Enum AssociationWidget::visibility(Uml::RoleType::Enum role) const
1078 {
1079     const UMLAssociation *assoc = association();
1080     if (assoc)
1081         return assoc->visibility(role);
1082     const UMLAttribute *attr = attribute();
1083     if (attr)
1084         return attr->visibility();
1085     return m_role[role].visibility;
1086 }
1087 
1088 /**
1089  * Sets the visibility on the given role of the association.
1090  */
setVisibility(Visibility::Enum value,Uml::RoleType::Enum role)1091 void AssociationWidget::setVisibility(Visibility::Enum value, Uml::RoleType::Enum role)
1092 {
1093     if (value != visibility(role) && m_umlObject) {
1094         // update our model object
1095         const UMLObject::ObjectType ot = m_umlObject->baseType();
1096         if (ot == UMLObject::ot_Association) {
1097             UMLAssociation *a = association();
1098             a->blockSignals(true);
1099             a->setVisibility(value, role);
1100             a->blockSignals(false);
1101         }
1102         else if (ot == UMLObject::ot_Attribute) {
1103             UMLAttribute *a = attribute();
1104             a->blockSignals(true);
1105             a->setVisibility(value);
1106             a->blockSignals(false);
1107         }
1108     }
1109     m_role[role].visibility = value;
1110     // update role pre-text attribute as appropriate
1111     if (m_role[role].roleWidget) {
1112         QString scopeString = Visibility::toString(value, true);
1113         m_role[role].roleWidget->setPreText(scopeString);
1114     }
1115 }
1116 
1117 /**
1118  * Gets the changeability on the given end of the Association.
1119  */
changeability(Uml::RoleType::Enum role) const1120 Uml::Changeability::Enum AssociationWidget::changeability(Uml::RoleType::Enum role) const
1121 {
1122     if (m_umlObject == 0 || m_umlObject->baseType() != UMLObject::ot_Association)
1123         return m_role[role].changeability;
1124     UMLAssociation *umla = m_umlObject->asUMLAssociation();
1125     return umla->changeability(role);
1126 }
1127 
1128 /**
1129  * Sets the changeability on the given end of the Association.
1130  */
setChangeability(Uml::Changeability::Enum value,Uml::RoleType::Enum role)1131 void AssociationWidget::setChangeability(Uml::Changeability::Enum value, Uml::RoleType::Enum role)
1132 {
1133     if (value == changeability(role))
1134         return;
1135     QString changeString = Uml::Changeability::toString(value);
1136     if (m_umlObject && m_umlObject->baseType() == UMLObject::ot_Association)  // update our model object
1137         association()->setChangeability(value, role);
1138     m_role[role].changeability = value;
1139     // update our string representation
1140     setChangeWidget(changeString, role);
1141 }
1142 
1143 /**
1144  * For internal purposes only.
1145  * Other classes/users should use setChangeability() instead.
1146  */
setChangeWidget(const QString & strChangeWidget,Uml::RoleType::Enum role)1147 void AssociationWidget::setChangeWidget(const QString &strChangeWidget, Uml::RoleType::Enum role)
1148 {
1149     bool newLabel = false;
1150     TextRole::Enum tr = (role == RoleType::A ? TextRole::ChangeA : TextRole::ChangeB);
1151 
1152     if (!m_role[role].changeabilityWidget) {
1153         // Don't construct the FloatingTextWidget if the string is empty.
1154         if (strChangeWidget.isEmpty())
1155             return;
1156 
1157         newLabel = true;
1158         m_role[role].changeabilityWidget = new FloatingTextWidget(m_scene, tr, strChangeWidget);
1159         m_role[role].changeabilityWidget->setParentItem(this);
1160         m_role[role].changeabilityWidget->setLink(this);
1161         m_scene->addFloatingTextWidget(m_role[role].changeabilityWidget);
1162         m_role[role].changeabilityWidget->setPreText(QLatin1String("{")); // all types have this
1163         m_role[role].changeabilityWidget->setPostText(QLatin1String("}")); // all types have this
1164     } else {
1165         if (m_role[role].changeabilityWidget->text().isEmpty()) {
1166             newLabel = true;
1167         }
1168         m_role[role].changeabilityWidget->setText(strChangeWidget);
1169     }
1170     m_role[role].changeabilityWidget->setActivated();
1171 
1172     if (newLabel) {
1173         setTextPosition(tr);
1174     }
1175 
1176     if (FloatingTextWidget::isTextValid(m_role[role].changeabilityWidget->text()))
1177         m_role[role].changeabilityWidget->show();
1178     else
1179         m_role[role].changeabilityWidget->hide();
1180 }
1181 
1182 /**
1183  * Returns true if the line path starts at the given widget.
1184  */
linePathStartsAt(const UMLWidget * widget)1185 bool AssociationWidget::linePathStartsAt(const UMLWidget* widget)
1186 {
1187 //:TODO:
1188 //    QPointF lpStart = m_associationLine->point(0);
1189 //    int startX = lpStart.x();
1190 //    int startY = lpStart.y();
1191 //    int wX = widget->getX();
1192 //    int wY = widget->getY();
1193 //    int wWidth = widget->width();
1194 //    int wHeight = widget->height();
1195 //    bool result = (startX >= wX && startX <= wX + wWidth &&
1196 //                   startY >= wY && startY <= wY + wHeight);
1197 //    return result;
1198     bool result = widget->contains(m_associationLine->point(0));
1199     DEBUG(DBG_SRC) << "widget=" << widget->name() << " / result=" << result;
1200     return result;
1201 }
1202 
1203 /**
1204  * This function calculates which role should be set for the m_nameWidget FloatingTextWidget.
1205  */
calculateNameType(Uml::TextRole::Enum defaultRole)1206 Uml::TextRole::Enum AssociationWidget::calculateNameType(Uml::TextRole::Enum defaultRole)
1207 {
1208     TextRole::Enum result = defaultRole;
1209     if (m_scene->type() == DiagramType::Collaboration) {
1210         if (m_role[RoleType::A].umlWidget == m_role[RoleType::B].umlWidget) {
1211             result = TextRole::Coll_Message;//for now same as other Coll_Message
1212         } else {
1213             result = TextRole::Coll_Message;
1214         }
1215     } else if (m_scene->type() == DiagramType::Sequence) {
1216         if (m_role[RoleType::A].umlWidget == m_role[RoleType::B].umlWidget) {
1217             result = TextRole::Seq_Message_Self;
1218         } else {
1219             result = TextRole::Seq_Message;
1220         }
1221     }
1222 
1223     return result;
1224 }
1225 
1226 /**
1227  * Gets the given role widget.
1228  *
1229  * @return  Pointer to the role's UMLWidget.
1230  */
widgetForRole(Uml::RoleType::Enum role) const1231 UMLWidget* AssociationWidget::widgetForRole(Uml::RoleType::Enum role) const
1232 {
1233     return m_role[role].umlWidget;
1234 }
1235 
1236 /**
1237  * Cleans up all the association's data in the related widgets.
1238  */
cleanup()1239 void AssociationWidget::cleanup()
1240 {
1241     //let any other associations know we are going so they can tidy their positions up
1242     if (m_role[RoleType::A].m_nTotalCount > 2)
1243         updateAssociations(m_role[RoleType::A].m_nTotalCount - 1, m_role[RoleType::A].m_WidgetRegion, RoleType::A);
1244     if (m_role[RoleType::B].m_nTotalCount > 2)
1245         updateAssociations(m_role[RoleType::B].m_nTotalCount - 1, m_role[RoleType::B].m_WidgetRegion, RoleType::B);
1246 
1247     m_role[RoleType::A].cleanup();
1248     m_role[RoleType::B].cleanup();
1249 
1250     if (m_nameWidget) {
1251         m_scene->removeWidget(m_nameWidget);
1252         m_nameWidget = 0;
1253     }
1254 
1255     if (m_umlObject && m_umlObject->baseType() == UMLObject::ot_Association) {
1256         /*
1257            We do not remove the UMLAssociation from the document.
1258            Why? - Well, for example we might be in the middle of
1259            a cut/paste. If the UMLAssociation is removed by the cut
1260            then upon pasteing we have a problem.
1261            This is not quite clean yet - there should be a way to
1262            explicitly delete a UMLAssociation.  The Right Thing would
1263            be to have a ListView representation for UMLAssociation.
1264         `
1265                 IF we are cut n pasting, why are we handling this association as a pointer?
1266                 We should be using the XMI representation for a cut and paste. This
1267                 allows us to be clean here, AND a choice of recreating the object
1268                 w/ same id IF its a "cut", or a new object if its a "copy" operation
1269                 (in which case we wouldnt be here, in cleanup()).
1270          */
1271         setUMLAssociation(0);
1272     }
1273 
1274     m_associationLine->cleanup();
1275     removeAssocClassLine();
1276 }
1277 
1278 /**
1279  * @brief Return state if the association line point in the near of the last context
1280  *        menu event position is addable or not.
1281  * A point is addable if the association is not an Exception and there is no point in the near.
1282  *
1283  * @return true if point is addable
1284  */
isPointAddable()1285 bool AssociationWidget::isPointAddable()
1286 {
1287     if (!isSelected() || associationType() == Uml::AssociationType::Exception)
1288         return false;
1289     int i = m_associationLine->closestPointIndex(m_eventScenePos);
1290     return i == -1;
1291 }
1292 
1293 /**
1294  * @brief Return state if the association line point in the near of the last context
1295  *        menu event position is removable or not.
1296  * A point is removable if the association is not an Exception and is not the start or end point.
1297  *
1298  * @return true if point is removable
1299  */
isPointRemovable()1300 bool AssociationWidget::isPointRemovable()
1301 {
1302     if (!isSelected() || associationType() == Uml::AssociationType::Exception || m_associationLine->count() <= 2)
1303         return false;
1304     int i = m_associationLine->closestPointIndex(m_eventScenePos);
1305     return i > 0 && i < m_associationLine->count() - 1;
1306 }
1307 
isAutoLayouted()1308 bool AssociationWidget::isAutoLayouted()
1309 {
1310     if (associationType() == Uml::AssociationType::Exception)
1311         return true;
1312     if (!isSelected() || m_associationLine->count() <= 2)
1313         return false;
1314     return  m_associationLine->isAutoLayouted();
1315 }
1316 
1317 /**
1318  * if layout of this widget can be changed
1319  * @return true if layout could be changed
1320  * @return false if layout could not be changed
1321  */
isLayoutChangeable()1322 bool AssociationWidget::isLayoutChangeable()
1323 {
1324     return associationType() != Uml::AssociationType::Exception;
1325 }
1326 
1327 /**
1328  * Set our internal umlAssociation.
1329  */
setUMLAssociation(UMLAssociation * assoc)1330 void AssociationWidget::setUMLAssociation (UMLAssociation * assoc)
1331 {
1332     if (m_umlObject && m_umlObject->baseType() == UMLObject::ot_Association) {
1333         UMLAssociation *umla = association();
1334 
1335         // safety check. Did some num-nuts try to set the existing
1336         // association again? If so, just bail here
1337         if (umla == assoc)
1338             return;
1339 
1340         //umla->disconnect(this);  //Qt does disconnect automatically upon destruction.
1341         umla->nrof_parent_widgets--;
1342 
1343         // ANSWER: This is the wrong treatment of cut and paste. Associations that
1344         // are being cut/n pasted should be serialized to XMI, then reconstituted
1345         // (IF a paste operation) rather than passing around object pointers. Its
1346         // just too hard otherwise to prevent problems in the code. Bottom line: we need to
1347         // delete orphaned associations or we well get code crashes and memory leaks.
1348         if (umla->nrof_parent_widgets <= 0) {
1349             //umla->deleteLater();
1350         }
1351 
1352         m_umlObject = 0;
1353     }
1354 
1355     if (assoc) {
1356         m_umlObject = assoc;
1357 
1358         // move counter to "0" from "-1" (which means, no assocwidgets)
1359         if (assoc->nrof_parent_widgets < 0)
1360             assoc->nrof_parent_widgets = 0;
1361 
1362         assoc->nrof_parent_widgets++;
1363         connect(assoc, SIGNAL(modified()), this, SLOT(syncToModel()));
1364     }
1365 
1366 }
1367 
1368 /**
1369  * Returns true if the Widget is either at the starting or ending side of the association.
1370  */
containsAsEndpoint(UMLWidget * widget)1371 bool AssociationWidget::containsAsEndpoint(UMLWidget* widget)
1372 {
1373     return (widget == m_role[RoleType::A].umlWidget || widget == m_role[RoleType::B].umlWidget);
1374 }
1375 
1376 /**
1377  * Returns true if this AssociationWidget represents a collaboration message.
1378  */
isCollaboration() const1379 bool AssociationWidget::isCollaboration() const
1380 {
1381     Uml::AssociationType::Enum at = associationType();
1382     return (at == AssociationType::Coll_Mesg_Sync
1383             || at == AssociationType::Coll_Mesg_Async
1384             || at == AssociationType::Coll_Mesg_Self);
1385 }
1386 
1387 /**
1388  * Returns true if this AssociationWidget represents a self message.
1389  */
isSelf() const1390 bool AssociationWidget::isSelf() const
1391 {
1392     return widgetForRole(Uml::RoleType::A) == widgetForRole(Uml::RoleType::B);
1393 }
1394 
1395 /**
1396  * Gets the association's type.
1397  *
1398  * @return  This AssociationWidget's AssociationType::Enum.
1399  */
associationType() const1400 Uml::AssociationType::Enum AssociationWidget::associationType() const
1401 {
1402     if (m_umlObject == 0 || m_umlObject->baseType() != UMLObject::ot_Association)
1403         return m_associationType;
1404     UMLAssociation *umla = m_umlObject->asUMLAssociation();
1405     return umla->getAssocType();
1406 }
1407 
1408 /**
1409  * Sets the association's type.
1410  *
1411  * @param type   The AssociationType::Enum to set.
1412  */
setAssociationType(Uml::AssociationType::Enum type)1413 void AssociationWidget::setAssociationType(Uml::AssociationType::Enum type)
1414 {
1415     if (m_umlObject && m_umlObject->baseType() == UMLObject::ot_Association) {
1416         association()->setAssociationType(type);
1417     }
1418     m_associationType = type;
1419     // If the association new type is not supposed to have Multiplicity
1420     // FloatingTexts and a Role FloatingTextWidget then set the texts
1421     // to empty.
1422     // NB We do not physically delete the floatingtext widgets here because
1423     // those widgets are also stored in the UMLView::m_WidgetList.
1424     if (!AssocRules::allowMultiplicity(type, widgetForRole(RoleType::A)->baseType())) {
1425         if (m_role[RoleType::A].multiplicityWidget) {
1426             m_role[RoleType::A].multiplicityWidget->setName(QString());
1427         }
1428         if (m_role[RoleType::B].multiplicityWidget) {
1429             m_role[RoleType::B].multiplicityWidget->setName(QString());
1430         }
1431     }
1432     if (!AssocRules::allowRole(type)) {
1433         if (m_role[RoleType::A].roleWidget) {
1434             m_role[RoleType::A].roleWidget->setName(QString());
1435         }
1436         if (m_role[RoleType::B].roleWidget) {
1437             m_role[RoleType::B].roleWidget->setName(QString());
1438         }
1439         setRoleDocumentation(QString(), RoleType::A);
1440         setRoleDocumentation(QString(), RoleType::B);
1441     }
1442     m_associationLine->reconstructSymbols();
1443     m_associationLine->updatePenStyle();
1444 }
1445 
1446 /**
1447  * Gets the ID of the given role widget.
1448  */
widgetIDForRole(Uml::RoleType::Enum role) const1449 Uml::ID::Type AssociationWidget::widgetIDForRole(Uml::RoleType::Enum role) const
1450 {
1451     if (m_role[role].umlWidget == 0) {
1452         if (m_umlObject && m_umlObject->baseType() == UMLObject::ot_Association) {
1453             UMLAssociation *umla = m_umlObject->asUMLAssociation();
1454             return umla->getObjectId(role);
1455         }
1456         uError() << "umlWidget is NULL";
1457         return Uml::ID::None;
1458     }
1459     if (m_role[role].umlWidget->isObjectWidget())
1460         return m_role[role].umlWidget->localID();
1461     Uml::ID::Type id = m_role[role].umlWidget->id();
1462     return id;
1463 }
1464 
1465 /**
1466  * Gets the local ID of the given role widget.
1467  */
widgetLocalIDForRole(Uml::RoleType::Enum role) const1468 Uml::ID::Type AssociationWidget::widgetLocalIDForRole(Uml::RoleType::Enum role) const
1469 {
1470     if (m_role[role].umlWidget == 0) {
1471         if (m_umlObject && m_umlObject->baseType() == UMLObject::ot_Association) {
1472             UMLAssociation *umla = m_umlObject->asUMLAssociation();
1473             return umla->getObjectId(role);
1474         }
1475         uError() << "umlWidget is NULL";
1476         return Uml::ID::None;
1477     }
1478     Uml::ID::Type id = m_role[role].umlWidget->localID();
1479     return id;
1480 }
1481 
1482 /**
1483  * Returns a QString Object representing this AssociationWidget.
1484  */
toString() const1485 QString AssociationWidget::toString() const
1486 {
1487     QString string;
1488     static const QChar colon(QLatin1Char(':'));
1489     static const QChar space(QLatin1Char(' '));
1490 
1491     if (widgetForRole(RoleType::A)) {
1492         string = widgetForRole(RoleType::A)->name();
1493     }
1494     string.append(colon);
1495 
1496     if (m_role[RoleType::A].roleWidget) {
1497         string += m_role[RoleType::A].roleWidget->text();
1498     }
1499     string.append(space);
1500     string.append(Uml::AssociationType::toStringI18n(associationType()));
1501     string.append(space);
1502 
1503     if (widgetForRole(RoleType::B)) {
1504         string += widgetForRole(RoleType::B)->name();
1505     }
1506 
1507     string.append(colon);
1508     if (m_role[RoleType::B].roleWidget) {
1509         string += m_role[RoleType::B].roleWidget->text();
1510     }
1511 
1512     return string;
1513 }
1514 
1515 /**
1516  * Adds a break point (if left mouse button).
1517  */
mouseDoubleClickEvent(QGraphicsSceneMouseEvent * event)1518 void AssociationWidget::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
1519 {
1520     if (event->button() == Qt::LeftButton) {
1521         uDebug() << "widget = " << name() << " / type = " << baseTypeStr();
1522         showPropertiesDialog();
1523         event->accept();
1524     }
1525 }
1526 
1527 /**
1528  * Overrides moveEvent.
1529  */
moveEvent(QGraphicsSceneMouseEvent * me)1530 void AssociationWidget::moveEvent(QGraphicsSceneMouseEvent *me)
1531 {
1532     // 2004-04-30: Achim Spangler
1533     // Simple Approach to block moveEvent during load of XMI
1534     /// @todo avoid trigger of this event during load
1535 
1536     if (umlDoc()->loading()) {
1537         // hmmh - change of position during load of XMI
1538         // -> there is something wrong
1539         // -> avoid movement during opening
1540         // -> print warn and stay at old position
1541         uWarning() << "called during load of XMI for ViewType: "
1542             << m_scene->type() << ", and BaseType: " << baseType();
1543         return;
1544     }
1545     /*to be here a line segment has moved.
1546       we need to see if the three text widgets needs to be moved.
1547       there are a few things to check first though:
1548 
1549       1) Do they exist
1550       2) does it need to move:
1551       2a) for the multi widgets only move if they changed region, otherwise they are close enough
1552       2b) for role name move if the segment it is on moves.
1553     */
1554     //first see if either the first or last segments moved, else no need to recalculate their point positions
1555 
1556     QPointF oldNamePoint = calculateTextPosition(TextRole::Name);
1557     QPointF oldMultiAPoint = calculateTextPosition(TextRole::MultiA);
1558     QPointF oldMultiBPoint = calculateTextPosition(TextRole::MultiB);
1559     QPointF oldChangeAPoint = calculateTextPosition(TextRole::ChangeA);
1560     QPointF oldChangeBPoint = calculateTextPosition(TextRole::ChangeB);
1561     QPointF oldRoleAPoint = calculateTextPosition(TextRole::RoleAName);
1562     QPointF oldRoleBPoint = calculateTextPosition(TextRole::RoleBName);
1563 
1564     int movingPoint = m_associationLine->closestPointIndex(me->scenePos());
1565     if (movingPoint != -1)
1566         m_associationLine->setPoint(movingPoint, me->scenePos());
1567     int pos = m_associationLine->count() - 1;//set to last point for widget b
1568 
1569     if ( movingPoint == 1 || (movingPoint == pos-1) ) {
1570         calculateEndingPoints();
1571     }
1572     if (m_role[RoleType::A].changeabilityWidget && (movingPoint == 1)) {
1573         setTextPositionRelatively(TextRole::ChangeA, oldChangeAPoint);
1574     }
1575     if (m_role[RoleType::B].changeabilityWidget && (movingPoint == 1)) {
1576         setTextPositionRelatively(TextRole::ChangeB, oldChangeBPoint);
1577     }
1578     if (m_role[RoleType::A].multiplicityWidget && (movingPoint == 1)) {
1579         setTextPositionRelatively(TextRole::MultiA, oldMultiAPoint);
1580     }
1581     if (m_role[RoleType::B].multiplicityWidget && (movingPoint == pos-1)) {
1582         setTextPositionRelatively(TextRole::MultiB, oldMultiBPoint);
1583     }
1584 
1585     if (m_nameWidget) {
1586         if (movingPoint == m_unNameLineSegment ||
1587                 movingPoint - 1 == m_unNameLineSegment) {
1588             setTextPositionRelatively(TextRole::Name, oldNamePoint);
1589         }
1590     }
1591 
1592     if (m_role[RoleType::A].roleWidget) {
1593         setTextPositionRelatively(TextRole::RoleAName, oldRoleAPoint);
1594     }
1595     if (m_role[RoleType::B].roleWidget) {
1596         setTextPositionRelatively(TextRole::RoleBName, oldRoleBPoint);
1597     }
1598 
1599     if (m_pAssocClassLine) {
1600         computeAssocClassLine();
1601     }
1602 }
1603 
1604 /**
1605  * Calculates and sets the first and last point in the Association's AssociationLine.
1606  * Each point is a middle point of its respecting UMLWidget's Bounding rectangle
1607  * or a corner of it.
1608  * This method picks which sides to use for the association.
1609  */
calculateEndingPoints()1610 void AssociationWidget::calculateEndingPoints()
1611 {
1612     /*
1613      * For each UMLWidget the diagram is divided in four regions by its diagonals
1614      * as indicated below
1615      *                              Region 2
1616      *                         \                /
1617      *                           \            /
1618      *                             +--------+
1619      *                             | \    / |
1620      *                Region 1     |   ><   |    Region 3
1621      *                             | /    \ |
1622      *                             +--------+
1623      *                           /            \
1624      *                         /                \
1625      *                              Region 4
1626      *
1627      * Each diagonal is defined by two corners of the bounding rectangle
1628      *
1629      * To calculate the first point in the AssociationLine we have to find out in which
1630      * Region (defined by WidgetA's diagonals) is WidgetB's center
1631      * (let's call it Region M.) After that the first point will be the middle
1632      * point of the rectangle's side contained in Region M.
1633      *
1634      * To calculate the last point in the AssociationLine we repeat the above but
1635      * in the opposite direction (from widgetB to WidgetA)
1636      */
1637 
1638     UMLWidget *pWidgetA = m_role[RoleType::A].umlWidget;
1639     UMLWidget *pWidgetB = m_role[RoleType::B].umlWidget;
1640     if (!pWidgetA || !pWidgetB) {
1641         uWarning() << "Returning - one of the role widgets is not set.";
1642         return;
1643     }
1644 
1645     int size = m_associationLine->count();
1646     if (size < 2) {
1647         QPointF pA = pWidgetA->getPos();
1648         QPointF pB = pWidgetB->getPos();
1649         QPolygonF polyA = pWidgetA->shape().toFillPolygon().translated(pA);
1650         QPolygonF polyB = pWidgetB->shape().toFillPolygon().translated(pB);
1651         QLineF nearestPoints = Widget_Utils::closestPoints(polyA, polyB);
1652         if (nearestPoints.isNull()) {
1653             uError() << "Widget_Utils::closestPoints failed, falling back to simple widget positions";
1654         } else {
1655             pA = nearestPoints.p1();
1656             pB = nearestPoints.p2();
1657         }
1658         m_associationLine->setEndPoints(pA, pB);
1659     }
1660 
1661     // See if an association to self.
1662     // See if it needs to be set up before we continue:
1663     // If self association/message and doesn't have the minimum 4 points
1664     // then create it. Make sure no points are out of bounds of viewing area.
1665     // This only happens on first time through that we are worried about.
1666     if (isSelf() && size < 4) {
1667         createPointsSelfAssociation();
1668         return;
1669     }
1670 
1671     if (associationType() == AssociationType::Exception && size < 4) {
1672         createPointsException();
1673         updatePointsException();
1674         return;
1675     }
1676 
1677     // If the line has more than one segment change the values to calculate
1678     // from widget to point 1.
1679     qreal xB = pWidgetB->getX() + pWidgetB->width() / 2;
1680     qreal yB = pWidgetB->getY() + pWidgetB->height() / 2;
1681     if (size > 2) {
1682         QPointF p = m_associationLine->point(1);
1683         xB = p.x();
1684         yB = p.y();
1685     }
1686     doUpdates(QPointF(xB, yB), RoleType::A);
1687 
1688     // Now do the same for widgetB.
1689     // If the line has more than one segment change the values to calculate
1690     // from widgetB to the last point away from it.
1691     qreal xA = pWidgetA->getX() + pWidgetA->width() / 2;
1692     qreal yA = pWidgetA->getY() + pWidgetA->height() / 2;
1693     if (size > 2 ) {
1694         QPointF p = m_associationLine->point(size - 2);
1695         xA = p.x();
1696         yA = p.y();
1697     }
1698     doUpdates(QPointF(xA, yA), RoleType::B);
1699 
1700     computeAssocClassLine();
1701 }
1702 
1703 /**
1704  * Used by @ref calculateEndingPoints.
1705  */
doUpdates(const QPointF & otherP,RoleType::Enum role)1706 void AssociationWidget::doUpdates(const QPointF &otherP, RoleType::Enum role)
1707 {
1708     // Find widget region.
1709     Uml::Region::Enum oldRegion = m_role[role].m_WidgetRegion;
1710     UMLWidget *pWidget = m_role[role].umlWidget;
1711     QRectF rc(pWidget->getX(), pWidget->getY(),
1712               pWidget->width(), pWidget->height());
1713     Uml::Region::Enum region = m_role[role].m_WidgetRegion;  // alias for brevity
1714     region = findPointRegion(rc, otherP);
1715     // Move some regions to the standard ones.
1716     switch( region ) {
1717     case Uml::Region::NorthWest:
1718         region = Uml::Region::North;
1719         break;
1720     case Uml::Region::NorthEast:
1721         region = Uml::Region::East;
1722         break;
1723     case Uml::Region::SouthEast:
1724         region = Uml::Region::South;
1725         break;
1726     case Uml::Region::SouthWest:
1727     case Uml::Region::Center:
1728         region = Uml::Region::West;
1729         break;
1730     default:
1731         break;
1732     }
1733     int regionCount = getRegionCount(region, role) + 2; //+2 = (1 for this one and one to halve it)
1734     int totalCount = m_role[role].m_nTotalCount;
1735     if (oldRegion != region) {
1736         updateRegionLineCount(regionCount - 1, regionCount, region, role);
1737         updateAssociations(totalCount - 1, oldRegion, role);
1738     } else if (totalCount != regionCount) {
1739         updateRegionLineCount(regionCount - 1, regionCount, region, role);
1740     } else {
1741         updateRegionLineCount(m_role[role].m_nIndex, totalCount, region, role);
1742     }
1743     updateAssociations(regionCount, region, role);
1744 }
1745 
1746 /**
1747  * Read property of bool m_activated.
1748  */
isActivated() const1749 bool AssociationWidget::isActivated() const
1750 {
1751     return m_activated;
1752 }
1753 
1754 /**
1755  * Set the m_activated flag of a widget but does not perform the Activate method.
1756  */
setActivated(bool active)1757 void AssociationWidget::setActivated(bool active)
1758 {
1759     m_activated = active;
1760 }
1761 
1762 /**
1763  * Synchronize this widget from the UMLAssociation.
1764  */
syncToModel()1765 void AssociationWidget::syncToModel()
1766 {
1767     UMLAssociation *uml = association();
1768 
1769     if (uml == 0) {
1770         UMLAttribute *attr = attribute();
1771         if (attr == 0)
1772             return;
1773         setVisibility(attr->visibility(), RoleType::B);
1774         setRoleName(attr->name(), RoleType::B);
1775         return;
1776     }
1777     // block signals until finished
1778     uml->blockSignals(true);
1779 
1780     setName(uml->name());
1781     setRoleName(uml->getRoleName(RoleType::A), RoleType::A);
1782     setRoleName(uml->getRoleName(RoleType::B), RoleType::B);
1783     setVisibility(uml->visibility(RoleType::A), RoleType::A);
1784     setVisibility(uml->visibility(RoleType::B), RoleType::B);
1785     setChangeability(uml->changeability(RoleType::A), RoleType::A);
1786     setChangeability(uml->changeability(RoleType::B), RoleType::B);
1787     setMultiplicity(uml->getMultiplicity(RoleType::A), RoleType::A);
1788     setMultiplicity(uml->getMultiplicity(RoleType::B), RoleType::B);
1789 
1790     uml->blockSignals(false);
1791 }
1792 
1793 /**
1794  * Merges/syncs the association widget data into UML object
1795  * representation.
1796  * This will synchronize UMLAssociation w/ this new Widget
1797  * CHECK: Can we get rid of this.
1798  */
mergeAssociationDataIntoUMLRepresentation()1799 void AssociationWidget::mergeAssociationDataIntoUMLRepresentation()
1800 {
1801     UMLAssociation *umlassoc = association();
1802     UMLAttribute *umlattr = attribute();
1803     if (umlassoc == 0 && umlattr == 0)
1804         return;
1805 
1806     // block emit modified signal, or we get a horrible loop
1807     m_umlObject->blockSignals(true);
1808 
1809     // would be desirable to do the following
1810     // so that we can be sure its back to initial state
1811     // in case we missed something here.
1812     //uml->init();
1813 
1814     // floating text widgets
1815     FloatingTextWidget *text = nameWidget();
1816     if (text)
1817         m_umlObject->setName(text->text());
1818 
1819     text = roleWidget(RoleType::A);
1820     if (text && umlassoc)
1821         umlassoc->setRoleName(text->text(), RoleType::A);
1822 
1823     text = roleWidget(RoleType::B);
1824     if (text) {
1825         if (umlassoc)
1826             umlassoc->setRoleName(text->text(), RoleType::B);
1827         else if (umlattr)
1828             umlattr->setName(text->text());
1829     }
1830 
1831     text = multiplicityWidget(RoleType::A);
1832     if (text && umlassoc)
1833         umlassoc->setMultiplicity(text->text(), RoleType::A);
1834 
1835     text = multiplicityWidget(RoleType::B);
1836     if (text && umlassoc)
1837         umlassoc->setMultiplicity(text->text(), RoleType::B);
1838 
1839     // unblock
1840     m_umlObject->blockSignals(false);
1841 }
1842 
1843 /**
1844  * Auxiliary method for widgetMoved():
1845  * Saves all ideally computed floatingtext positions before doing any
1846  * kind of change.  This is necessary because a single invocation of
1847  * calculateEndingPoints() modifies the AssociationLine ending points on ALL
1848  * AssociationWidgets.  This means that if we don't save the old ideal
1849  * positions then they are irretrievably lost as soon as
1850  * calculateEndingPoints() is invoked.
1851  */
saveIdealTextPositions()1852 void AssociationWidget::saveIdealTextPositions()
1853 {
1854     m_oldNamePoint    = calculateTextPosition(TextRole::Name);
1855     m_oldMultiAPoint  = calculateTextPosition(TextRole::MultiA);
1856     m_oldMultiBPoint  = calculateTextPosition(TextRole::MultiB);
1857     m_oldChangeAPoint = calculateTextPosition(TextRole::ChangeA);
1858     m_oldChangeBPoint = calculateTextPosition(TextRole::ChangeB);
1859     m_oldRoleAPoint   = calculateTextPosition(TextRole::RoleAName);
1860     m_oldRoleBPoint   = calculateTextPosition(TextRole::RoleBName);
1861 }
1862 
1863 /**
1864  * Adjusts the ending point of the association that connects to Widget.
1865  */
widgetMoved(UMLWidget * widget,qreal dx,qreal dy)1866 void AssociationWidget::widgetMoved(UMLWidget* widget, qreal dx, qreal dy)
1867 {
1868     Q_UNUSED(dx); Q_UNUSED(dy);
1869 
1870     // Simple Approach to block moveEvent during load of XMI
1871     /// @todo avoid trigger of this event during load
1872     if (umlDoc()->loading()) {
1873         // change of position during load of XMI
1874         // -> there is something wrong
1875         // -> avoid movement during opening
1876         // -> print warn and stay at old position
1877         DEBUG(DBG_SRC) << "called during load of XMI for ViewType: " << m_scene->type()
1878                        << ", and BaseType: " << baseTypeStr();
1879         return;
1880     }
1881 
1882     DEBUG(DBG_SRC) << "association type=" << Uml::AssociationType::toString(associationType());
1883     if (associationType() == AssociationType::Exception) {
1884         updatePointsException();
1885         setTextPosition(TextRole::Name);
1886     }
1887     else {
1888         calculateEndingPoints();
1889         computeAssocClassLine();
1890     }
1891 
1892     // Assoc to self - move all points:
1893     if (isSelf()) {
1894         updatePointsSelfAssociation();
1895 
1896         if (m_nameWidget && !m_nameWidget->isSelected()) {
1897             setTextPositionRelatively(TextRole::Name, m_oldNamePoint);
1898         }
1899 
1900     }//end if widgetA = widgetB
1901     else if (m_role[RoleType::A].umlWidget == widget) {
1902         if (m_nameWidget && m_unNameLineSegment == 0 && !m_nameWidget->isSelected() ) {
1903             //only calculate position and move text if the segment it is on is moving
1904             setTextPositionRelatively(TextRole::Name, m_oldNamePoint);
1905         }
1906         if (m_role[RoleType::B].umlWidget && m_role[RoleType::B].umlWidget->changesShape())
1907             m_role[RoleType::B].umlWidget->updateGeometry(false);
1908     }//end if widgetA moved
1909     else if (m_role[RoleType::B].umlWidget == widget) {
1910         const int size = m_associationLine->count();
1911         if (m_nameWidget && (m_unNameLineSegment == size-2) && !m_nameWidget->isSelected() ) {
1912             //only calculate position and move text if the segment it is on is moving
1913             setTextPositionRelatively(TextRole::Name, m_oldNamePoint);
1914         }
1915         if (m_role[RoleType::A].umlWidget && m_role[RoleType::A].umlWidget->changesShape())
1916             m_role[RoleType::A].umlWidget->updateGeometry(false);
1917     }//end if widgetB moved
1918 
1919     if (m_role[RoleType::A].roleWidget && !m_role[RoleType::A].roleWidget->isSelected()) {
1920         setTextPositionRelatively(TextRole::RoleAName, m_oldRoleAPoint);
1921     }
1922     if (m_role[RoleType::B].roleWidget && !m_role[RoleType::B].roleWidget->isSelected()) {
1923         setTextPositionRelatively(TextRole::RoleBName, m_oldRoleBPoint);
1924     }
1925     if (m_role[RoleType::A].multiplicityWidget && !m_role[RoleType::A].multiplicityWidget->isSelected()) {
1926         setTextPositionRelatively(TextRole::MultiA, m_oldMultiAPoint);
1927     }
1928     if (m_role[RoleType::B].multiplicityWidget && !m_role[RoleType::B].multiplicityWidget->isSelected()) {
1929         setTextPositionRelatively(TextRole::MultiB, m_oldMultiBPoint);
1930     }
1931     if (m_role[RoleType::A].changeabilityWidget && !m_role[RoleType::A].changeabilityWidget->isSelected()) {
1932         setTextPositionRelatively(TextRole::ChangeA, m_oldChangeAPoint);
1933     }
1934     if (m_role[RoleType::B].changeabilityWidget && !m_role[RoleType::B].changeabilityWidget->isSelected()) {
1935         setTextPositionRelatively(TextRole::ChangeB, m_oldChangeBPoint);
1936     }
1937 }
1938 
1939 /**
1940  * Creates the points of the self association.
1941  * Method called when a widget end points are calculated by calculateEndingPoints().
1942  */
createPointsSelfAssociation()1943 void AssociationWidget::createPointsSelfAssociation()
1944 {
1945     UMLWidget *pWidgetA = m_role[RoleType::A].umlWidget;
1946 
1947     const int DISTANCE = 50;
1948     qreal x = pWidgetA->x();
1949     qreal y = pWidgetA->y();
1950     qreal h = pWidgetA->height();
1951     qreal w = pWidgetA->width();
1952     // see if above widget ok to start
1953     if (y - DISTANCE > 0) {
1954         m_associationLine->setEndPoints(QPointF(x + w / 4, y) , QPointF(x + w * 3 / 4, y));
1955         m_associationLine->insertPoint(1, QPointF(x + w / 4, y - DISTANCE));
1956         m_associationLine->insertPoint(2, QPointF(x + w * 3 / 4, y - DISTANCE));
1957         m_role[RoleType::A].m_WidgetRegion = m_role[RoleType::B].m_WidgetRegion = Uml::Region::North;
1958     } else {
1959         m_associationLine->setEndPoints(QPointF(x + w / 4, y + h), QPointF(x + w * 3 / 4, y + h));
1960         m_associationLine->insertPoint(1, QPointF(x + w / 4, y + h + DISTANCE));
1961         m_associationLine->insertPoint(2, QPointF(x + w * 3 / 4, y + h + DISTANCE));
1962         m_role[RoleType::A].m_WidgetRegion = m_role[RoleType::B].m_WidgetRegion = Uml::Region::South;
1963     }
1964 }
1965 
1966 /**
1967  * Adjusts the points of the self association.
1968  * Method called when a widget was moved by widgetMoved(widget, x, y).
1969  */
updatePointsSelfAssociation()1970 void AssociationWidget::updatePointsSelfAssociation()
1971 {
1972     UMLWidget *pWidgetA = m_role[RoleType::A].umlWidget;
1973 
1974     const int DISTANCE = 50;
1975     qreal x = pWidgetA->x();
1976     qreal y = pWidgetA->y();
1977     qreal h = pWidgetA->height();
1978     qreal w = pWidgetA->width();
1979     // see if above widget ok to start
1980     if (y - DISTANCE > 0) {
1981         m_associationLine->setEndPoints(QPointF(x + w / 4, y) , QPointF(x + w * 3 / 4, y));
1982         m_associationLine->setPoint(1, QPointF(x + w / 4, y - DISTANCE));
1983         m_associationLine->setPoint(2, QPointF(x + w * 3 / 4, y - DISTANCE));
1984         m_role[RoleType::A].m_WidgetRegion = m_role[RoleType::B].m_WidgetRegion = Uml::Region::North;
1985     } else {
1986         m_associationLine->setEndPoints(QPointF(x + w / 4, y + h), QPointF(x + w * 3 / 4, y + h));
1987         m_associationLine->setPoint(1, QPointF(x + w / 4, y + h + DISTANCE));
1988         m_associationLine->setPoint(2, QPointF(x + w * 3 / 4, y + h + DISTANCE));
1989         m_role[RoleType::A].m_WidgetRegion = m_role[RoleType::B].m_WidgetRegion = Uml::Region::South;
1990     }
1991 }
1992 
1993 /**
1994  * Creates the points of the association exception.
1995  * Method called when a widget end points are calculated by calculateEndingPoints().
1996  */
createPointsException()1997 void AssociationWidget::createPointsException()
1998 {
1999     UMLWidget *pWidgetA = m_role[RoleType::A].umlWidget;
2000     UMLWidget *pWidgetB = m_role[RoleType::B].umlWidget;
2001 
2002     qreal xa = pWidgetA->x();
2003     qreal ya = pWidgetA->y();
2004     qreal ha = pWidgetA->height();
2005     qreal wa = pWidgetA->width();
2006 
2007     qreal xb = pWidgetB->x();
2008     qreal yb = pWidgetB->y();
2009     qreal hb = pWidgetB->height();
2010     //qreal wb = pWidgetB->width();
2011 
2012     m_associationLine->setEndPoints(QPointF(xa + wa , ya + ha/2) , QPointF(xb , yb + hb/2));
2013     m_associationLine->insertPoint(1, QPointF(xa + wa , ya + ha/2));
2014     m_associationLine->insertPoint(2, QPointF(xb , yb + hb/2));
2015 }
2016 
2017 /**
2018  * Adjusts the points of the association exception.
2019  * Method called when a widget was moved by widgetMoved(widget, x, y).
2020  */
updatePointsException()2021 void AssociationWidget::updatePointsException()
2022 {
2023     UMLWidget *pWidgetA = m_role[RoleType::A].umlWidget;
2024     UMLWidget *pWidgetB = m_role[RoleType::B].umlWidget;
2025 
2026     qreal xa = pWidgetA->x();
2027     qreal ya = pWidgetA->y();
2028     qreal ha = pWidgetA->height();
2029     qreal wa = pWidgetA->width();
2030 
2031     qreal xb = pWidgetB->x();
2032     qreal yb = pWidgetB->y();
2033     qreal hb = pWidgetB->height();
2034     qreal wb = pWidgetB->width();
2035     qreal xmil, ymil;
2036     qreal xdeb, ydeb;
2037     qreal xfin, yfin;
2038     qreal ESPACEX, ESPACEY;
2039     QPointF p1;
2040     QPointF p2;
2041     //calcul des coordonnées au milieu de la flèche eclair
2042     if (xb - xa - wa >= 45) {
2043         ESPACEX = 0;
2044         xdeb = xa + wa;
2045         xfin = xb;
2046     } else if (xa - xb - wb > 45) {
2047         ESPACEX = 0;
2048         xdeb = xa;
2049         xfin = xb + wb;
2050     } else {
2051         ESPACEX = 15;
2052         xdeb = xa + wa/2;
2053         xfin = xb + wb/2;
2054     }
2055 
2056     xmil = xdeb + (xfin - xdeb)/2;
2057 
2058     if (yb - ya - ha >= 45)  {
2059         ESPACEY = 0;
2060         ydeb = ya + ha;
2061         yfin = yb;
2062     } else if (ya - yb - hb > 45) {
2063         ESPACEY = 0;
2064         ydeb = ya;
2065         yfin = yb + hb;
2066     } else {
2067         ESPACEY = 15;
2068         ydeb = ya + ha/2;
2069         yfin = yb + hb/2;
2070     }
2071 
2072     ymil = ydeb + (yfin - ydeb)/2;
2073 
2074     p1.setX(xmil + (xfin - xmil)*1/2); p1.setY(ymil + (yfin - ymil)*1/3);
2075     p2.setX(xmil - (xmil - xdeb)*1/2); p2.setY(ymil - (ymil - ydeb)*1/3);
2076 
2077     if (fabs(p1.x() - p2.x()) <= 10)
2078         ESPACEX = 15;
2079     if (fabs(p1.y() - p2.y()) <= 10)
2080         ESPACEY = 15;
2081 
2082     m_associationLine->setEndPoints(QPointF(xdeb, ydeb), QPointF(xfin, yfin));
2083     m_associationLine->setPoint(1, QPointF(p1.x() + ESPACEX, p1.y() + ESPACEY));
2084     m_associationLine->setPoint(2, QPointF(p2.x() - ESPACEX, p2.y() - ESPACEY));
2085 
2086     m_role[RoleType::A].m_WidgetRegion = m_role[RoleType::B].m_WidgetRegion = Uml::Region::North;
2087 }
2088 
2089 /**
2090  * Finds out which region of rectangle 'rect' contains the point 'pos' and returns the region
2091  * number:
2092  * 1 = Region 1
2093  * 2 = Region 2
2094  * 3 = Region 3
2095  * 4 = Region 4
2096  * 5 = On diagonal 2 between Region 1 and 2
2097  * 6 = On diagonal 1 between Region 2 and 3
2098  * 7 = On diagonal 2 between Region 3 and 4
2099  * 8 = On diagonal 1 between Region 4 and 1
2100  * 9 = On diagonal 1 and On diagonal 2 (the center)
2101  */
findPointRegion(const QRectF & rect,const QPointF & pos)2102 Uml::Region::Enum AssociationWidget::findPointRegion(const QRectF& rect, const QPointF &pos)
2103 {
2104     qreal w = rect.width();
2105     qreal h = rect.height();
2106     qreal x = rect.x();
2107     qreal y = rect.y();
2108     qreal slope2 = w / h;
2109     qreal slope1 = slope2 *(-1.0);
2110     qreal b1 = x + w - (slope1 * y);
2111     qreal b2 = x - (slope2 * y);
2112 
2113     qreal eval1 = slope1 * pos.y() + b1;
2114     qreal eval2 = slope2 * pos.y() + b2;
2115 
2116     Uml::Region::Enum result = Uml::Region::Error;
2117     //if inside region 1
2118     if (eval1 > pos.x() && eval2 > pos.x()) {
2119         result = Uml::Region::West;
2120     }
2121     //if inside region 2
2122     else if (eval1 > pos.x() && eval2 < pos.x()) {
2123         result = Uml::Region::North;
2124     }
2125     //if inside region 3
2126     else if (eval1 < pos.x() && eval2 < pos.x()) {
2127         result = Uml::Region::East;
2128     }
2129     //if inside region 4
2130     else if (eval1 < pos.x() && eval2 > pos.x()) {
2131         result = Uml::Region::South;
2132     }
2133     //if inside region 5
2134     else if (eval1 == pos.x() && eval2 < pos.x()) {
2135         result = Uml::Region::NorthWest;
2136     }
2137     //if inside region 6
2138     else if (eval1 < pos.x() && eval2 == pos.x()) {
2139         result = Uml::Region::NorthEast;
2140     }
2141     //if inside region 7
2142     else if (eval1 == pos.x() && eval2 > pos.x()) {
2143         result = Uml::Region::SouthEast;
2144     }
2145     //if inside region 8
2146     else if (eval1 > pos.x() && eval2 == pos.x()) {
2147         result = Uml::Region::SouthWest;
2148     }
2149     //if inside region 9
2150     else if (eval1 == pos.x() && eval2 == pos.x()) {
2151         result = Uml::Region::Center;
2152     }
2153     return result;
2154 }
2155 
2156 /**
2157  * Returns a point with interchanged X and Y coordinates.
2158  */
swapXY(const QPointF & p)2159 QPointF AssociationWidget::swapXY(const QPointF &p)
2160 {
2161     QPointF swapped( p.y(), p.x() );
2162     return swapped;
2163 }
2164 
2165 #if 0  // not used at the moment
2166 /**
2167  * Calculates which point of segment P1P2 has a distance equal to
2168  * Distance from P1.
2169  * Let's say such point is PX, the distance from P1 to PX must be equal
2170  * to Distance and if PX is not a point of the segment P1P2 then the
2171  * function returns (-1, -1).
2172  */
2173 QPointF AssociationWidget::calculatePointAtDistance(const QPointF &P1, const QPointF &P2, float Distance)
2174 {
2175     /*
2176       the distance D between points (x1, y1) and (x3, y3) has the following formula:
2177           ---     ------------------------------
2178       D =    \   /         2         2
2179               \ /   (x3 - x1)  +  (y3 - y1)
2180 
2181       D, x1 and y1 are known and the point (x3, y3) is inside line (x1, y1)(x2, y2), so if the
2182       that line has the formula y = mx + b
2183       then y3 = m*x3 + b
2184 
2185        2             2             2
2186       D   = (x3 - x1)  +  (y3 - y1)
2187 
2188        2       2                 2      2                 2
2189       D    = x3    - 2*x3*x1 + x1   + y3   - 2*y3*y1  + y1
2190 
2191        2       2       2       2                  2
2192       D    - x1    - y1    = x3    - 2*x3*x1  + y3   - 2*y3*y1
2193 
2194        2       2       2       2                          2
2195       D    - x1    - y1    = x3    - 2*x3*x1  + (m*x3 + b)  - 2*(m*x3 + b)*y1
2196 
2197        2       2       2              2       2 2
2198       D    - x1    - y1   + 2*b*y1 - b   =  (m  + 1)*x3   + (-2*x1 + 2*m*b -2*m*y1)*x3
2199 
2200        2      2       2       2
2201       C  = - D    + x1    + y1   - 2*b*y1 + b
2202 
2203 
2204        2
2205       A  = (m    + 1)
2206 
2207       B  = (-2*x1 + 2*m*b -2*m*y1)
2208 
2209       and we have
2210        2
2211       A * x3 + B * x3 - C = 0
2212 
2213                          ---------------
2214              -B +  ---  /  2
2215                       \/  B   - 4*A*C
2216       sol_1  = --------------------------------
2217                        2*A
2218 
2219 
2220                          ---------------
2221              -B -  ---  /  2
2222                       \/  B   - 4*A*C
2223       sol_2  = --------------------------------
2224                        2*A
2225 
2226 
2227       then in the distance formula we have only one variable x3 and that is easy
2228       to calculate
2229     */
2230     int x1 = P1.y();
2231     int y1 = P1.x();
2232     int x2 = P2.y();
2233     int y2 = P2.x();
2234 
2235     if (x2 == x1) {
2236         return QPointF(x1, y1 + (int)Distance);
2237     }
2238     float slope = ((float)y2 - (float)y1) / ((float)x2 - (float)x1);
2239     float b = (y1 - slope*x1);
2240     float A = (slope * slope) + 1;
2241     float B = (2*slope*b) - (2*x1)  - (2*slope*y1);
2242     float C = (b*b) - (Distance*Distance) + (x1*x1) + (y1*y1) - (2*b*y1);
2243     float t = B*B - 4*A*C;
2244 
2245     if (t < 0) {
2246         return QPointF(-1, -1);
2247     }
2248     float sol_1 = ((-1* B) + sqrt(t)) / (2*A);
2249     float sol_2 = ((-1*B) - sqrt(t)) / (2*A);
2250 
2251     if (sol_1 < 0.0 && sol_2 < 0.0) {
2252         return QPointF(-1, -1);
2253     }
2254     QPointF sol1Point((int)(slope*sol_1 + b), (int)(sol_1));
2255     QPointF sol2Point((int)(slope*sol_2 + b), (int)(sol_2));
2256     if (sol_1 < 0 && sol_2 >=0) {
2257         if (x2 > x1) {
2258             if (x1 <= sol_2 && sol_2 <= x2)
2259                 return sol2Point;
2260         } else {
2261             if (x2 <= sol_2 && sol_2 <= x1)
2262                 return sol2Point;
2263         }
2264     } else if (sol_1 >= 0 && sol_2 < 0) {
2265         if (x2 > x1) {
2266             if (x1 <= sol_1 && sol_1 <= x2)
2267                 return sol1Point;
2268         } else {
2269             if (x2 <= sol_1 && sol_1 <= x1)
2270                 return sol1Point;
2271         }
2272     } else {
2273         if (x2 > x1) {
2274             if (x1 <= sol_1 && sol_1 <= x2)
2275                 return sol1Point;
2276             if (x1 <= sol_2 && sol_2 <= x2)
2277                 return sol2Point;
2278         } else {
2279             if (x2 <= sol_1 && sol_1 <= x1)
2280                 return sol1Point;
2281             if (x2 <= sol_2 && sol_2 <= x1)
2282                 return sol2Point;
2283         }
2284     }
2285     return QPointF(-1, -1);
2286 }
2287 
2288 /**
2289  * Calculates which point of a perpendicular line to segment P1P2 that contains P2
2290  *  has a distance equal to Distance from P2,
2291  * Lets say such point is P3,  the distance from P2 to P3 must be equal to Distance
2292  */
2293 QPointF AssociationWidget::calculatePointAtDistanceOnPerpendicular(const QPointF &P1, const QPointF &P2, float Distance)
2294 {
2295     /*
2296       the distance D between points (x2, y2) and (x3, y3) has the following formula:
2297 
2298           ---     ------------------------------
2299       D =    \   /         2             2
2300               \ / (x3 - x2)  +  (y3 - y2)
2301 
2302       D, x2 and y2 are known and line P2P3 is perpendicular to line (x1, y1)(x2, y2), so if the
2303       line P1P2 has the formula y = m*x + b,
2304       then      (x1 - x2)
2305           m =  -----------, because it is perpendicular to line P1P2
2306                 (y2 - y1)
2307 
2308       also y2 = m*x2 + b
2309       => b = y2 - m*x2
2310 
2311       then P3 = (x3, m*x3 + b)
2312 
2313        2            2            2
2314       D  = (x3 - x2)  + (y3 - y2)
2315 
2316        2     2               2     2               2
2317       D  = x3  - 2*x3*x2 + x2  + y3  - 2*y3*y2 + y2
2318 
2319        2     2     2     2               2
2320       D  - x2  - y2  = x3  - 2*x3*x2 + y3  - 2*y3*y2
2321 
2322 
2323 
2324        2     2     2     2                       2
2325       D  - x2  - y2  = x3  - 2*x3*x2 + (m*x3 + b)  - 2*(m*x3 + b)*y2
2326 
2327        2     2     2                   2        2       2
2328       D  - x2  - y2  + 2*b*y2 - b  = (m  + 1)*x3  + (-2*x2 + 2*m*b -2*m*y2)*x3
2329 
2330               2       2       2              2
2331       C  = - D    + x2    + y2   - 2*b*y2 + b
2332 
2333              2
2334       A  = (m  + 1)
2335 
2336       B  = (-2*x2 + 2*m*b -2*m*y2)
2337 
2338       and we have
2339        2
2340       A * x3 + B * x3 - C = 0
2341 
2342 
2343                            ---------------
2344                      ---  /  2
2345                 -B +    \/  B   - 4*A*C
2346       sol_1 = --------------------------------
2347                         2*A
2348 
2349 
2350                            ---------------
2351                      ---  /  2
2352                 -B -    \/  B   - 4*A*C
2353       sol_2 = --------------------------------
2354                         2*A
2355 
2356       then in the distance formula we have only one variable x3 and that is easy
2357       to calculate
2358     */
2359     if (P1.x() == P2.x()) {
2360         return QPointF((int)(P2.x() + Distance), P2.y());
2361     }
2362     const int x1 = P1.y();
2363     const int y1 = P1.x();
2364     const int x2 = P2.y();
2365     const int y2 = P2.x();
2366 
2367     float slope = ((float)x1 - (float)x2) / ((float)y2 - (float)y1);
2368     float b = (y2 - slope*x2);
2369     float A = (slope * slope) + 1;
2370     float B = (2*slope*b) - (2*x2) - (2*slope*y2);
2371     float C = (b*b) - (Distance*Distance) + (x2*x2) + (y2*y2) - (2*b*y2);
2372     float t = B*B - 4*A*C;
2373     if (t < 0) {
2374         return QPointF(-1, -1);
2375     }
2376     float sol_1 = ((-1* B) + sqrt(t)) / (2*A);
2377 
2378     float sol_2 = ((-1*B) - sqrt(t)) / (2*A);
2379 
2380     if (sol_1 < 0 && sol_2 < 0) {
2381         return QPointF(-1, -1);
2382     }
2383     QPointF sol1Point((int)(slope*sol_1 + b), (int)sol_1);
2384     QPointF sol2Point((int)(slope*sol_2 + b), (int)sol_2);
2385     if (sol_1 < 0 && sol_2 >=0) {
2386         return sol2Point;
2387     } else if (sol_1 >= 0 && sol_2 < 0) {
2388         return sol1Point;
2389     } else {    // Choose one solution, either will work fine
2390         if (slope >= 0) {
2391             if (sol_1 <= sol_2)
2392                 return sol2Point;
2393             else
2394                 return sol1Point;
2395         } else {
2396             if (sol_1 <= sol_2)
2397                 return sol1Point;
2398             else
2399                 return sol2Point;
2400         }
2401 
2402     }
2403     return QPointF(-1, -1);  // never reached, just keep compilers happy
2404 }
2405 
2406 /**
2407  * Calculates the intersection (PS) between line P1P2 and a perpendicular line containing
2408  * P3, the result is returned in ResultingPoint. and result value represents the distance
2409  * between ResultingPoint and P3; if this value is negative an error occurred.
2410  */
2411 float AssociationWidget::perpendicularProjection(const QPointF& P1, const QPointF& P2, const QPointF& P3,
2412         QPointF& ResultingPoint)
2413 {
2414     //line P1P2 is Line 1 = y=slope1*x + b1
2415 
2416     //line P3PS is Line 1 = y=slope2*x + b2
2417 
2418     float slope2 = 0;
2419     float slope1 = 0;
2420     float sx = 0, sy = 0;
2421     int y2 = P2.x();
2422     int y1 = P1.x();
2423     int x2 = P2.y();
2424     int x1 = P1.y();
2425     int y3 = P3.x();
2426     int x3 = P3.y();
2427     float distance = 0;
2428     float b1 = 0;
2429 
2430     float b2 = 0;
2431 
2432     if (x2 == x1) {
2433         sx = x2;
2434         sy = y3;
2435     } else if (y2 == y1) {
2436         sy = y2;
2437         sx = x3;
2438     } else {
2439         slope1 = (y2 - y1)/ (x2 - x1);
2440         slope2 = (x1 - x2)/ (y2 - y1);
2441         b1 = y2 - (slope1 * x2);
2442         b2 = y3 - (slope2 * x3);
2443         sx = (b2 - b1) / (slope1 - slope2);
2444         sy = slope1*sx + b1;
2445     }
2446     distance = (int)(sqrt(((x3 - sx)*(x3 - sx)) + ((y3 - sy)*(y3 - sy))));
2447 
2448     ResultingPoint.setX((int)sy);
2449     ResultingPoint.setY((int)sx);
2450 
2451     return distance;
2452 }
2453 #endif
2454 
2455 /**
2456  * Calculates the position of the text widget depending on the role
2457  * that widget is playing.
2458  * Returns the point at which to put the widget.
2459  */
calculateTextPosition(Uml::TextRole::Enum role)2460 QPointF AssociationWidget::calculateTextPosition(Uml::TextRole::Enum role)
2461 {
2462     const int SPACE = 2;
2463     QPointF p(-1, -1), q(-1, -1);
2464 
2465     // used to find out if association end point (p)
2466     // is at top or bottom edge of widget.
2467 
2468     if (role == TextRole::MultiA || role == TextRole::ChangeA || role == TextRole::RoleAName) {
2469         p = m_associationLine->point(0);
2470         q = m_associationLine->point(1);
2471     } else if (role == TextRole::MultiB || role == TextRole::ChangeB || role == TextRole::RoleBName) {
2472         const int lastSegment = m_associationLine->count() - 1;
2473         p = m_associationLine->point(lastSegment);
2474         q = m_associationLine->point(lastSegment - 1);
2475     } else if (role != TextRole::Name) {
2476         uError() << "called with unsupported TextRole::Enum " << role;
2477         return QPointF(-1, -1);
2478     }
2479 
2480     FloatingTextWidget *text = textWidgetByRole(role);
2481     int textW = 0, textH = 0;
2482     if (text) {
2483         textW = text->width();
2484         textH = text->height();
2485     }
2486 
2487     qreal x = 0.0, y = 0.0;
2488 
2489     if (role == TextRole::MultiA || role == TextRole::MultiB) {
2490         const bool isHorizontal = (p.y() == q.y());
2491         const int atBottom = p.y() + SPACE;
2492         const int atTop = p.y() - SPACE - textH;
2493         const int atLeft = p.x() - SPACE - textW;
2494         const int atRight = p.x() + SPACE;
2495         y = (p.y() > q.y()) == isHorizontal ? atBottom : atTop;
2496         x = (p.x() < q.x()) == isHorizontal ? atRight : atLeft;
2497 
2498     } else if (role == TextRole::ChangeA || role == TextRole::ChangeB) {
2499 
2500         if (p.y() > q.y())
2501             y = p.y() - SPACE - (textH * 2);
2502         else
2503             y = p.y() + SPACE + textH;
2504 
2505         if (p.x() < q.x())
2506             x = p.x() + SPACE;
2507         else
2508             x = p.x() - SPACE - textW;
2509 
2510     } else if (role == TextRole::RoleAName || role == TextRole::RoleBName) {
2511 
2512         if (p.y() > q.y())
2513             y = p.y() - SPACE - textH;
2514         else
2515             y = p.y() + SPACE;
2516 
2517         if (p.x() < q.x())
2518             x = p.x() + SPACE;
2519         else
2520             x = p.x() - SPACE - textW;
2521 
2522     } else if (role == TextRole::Name) {
2523 
2524         calculateNameTextSegment();
2525         if (m_unNameLineSegment == -1) {
2526             uWarning() << "TODO:negative line segment index";
2527             m_unNameLineSegment = 0;
2528         }
2529         x = ( m_associationLine->point(m_unNameLineSegment).x() +
2530                      m_associationLine->point(m_unNameLineSegment + 1).x() ) / 2;
2531         y = ( m_associationLine->point(m_unNameLineSegment).y() +
2532                      m_associationLine->point(m_unNameLineSegment + 1).y() ) / 2;
2533     }
2534 
2535     if (text) {
2536         constrainTextPos(x, y, textW, textH, role);
2537     }
2538     p = QPointF( x, y );
2539     return p;
2540 }
2541 
2542 /**
2543  * Return the mid point between p0 and p1
2544  */
midPoint(const QPointF & p0,const QPointF & p1)2545 QPointF AssociationWidget::midPoint(const QPointF& p0, const QPointF& p1)
2546 {
2547     QPointF midP;
2548     if (p0.x() < p1.x())
2549         midP.setX(p0.x() + (p1.x() - p0.x()) / 2);
2550     else
2551         midP.setX(p1.x() + (p0.x() - p1.x()) / 2);
2552     if (p0.y() < p1.y())
2553         midP.setY(p0.y() + (p1.y() - p0.y()) / 2);
2554     else
2555         midP.setY(p1.y() + (p0.y() - p1.y()) / 2);
2556     return midP;
2557 }
2558 
2559 /**
2560  * Constrains the FloatingTextWidget X and Y values supplied.
2561  * Implements the abstract operation from LinkWidget.
2562  *
2563  * @param textX       Candidate X value (may be modified by the constraint.)
2564  * @param textY       Candidate Y value (may be modified by the constraint.)
2565  * @param textWidth   Width of the text.
2566  * @param textHeight  Height of the text.
2567  * @param tr          Uml::Text_Role of the text.
2568  */
constrainTextPos(qreal & textX,qreal & textY,qreal textWidth,qreal textHeight,Uml::TextRole::Enum tr)2569 void AssociationWidget::constrainTextPos(qreal &textX, qreal &textY,
2570                                          qreal textWidth, qreal textHeight,
2571                                          Uml::TextRole::Enum tr)
2572 {
2573     const int textCenterX = textX + textWidth / 2;
2574     const int textCenterY = textY + textHeight / 2;
2575     const int lastSegment = m_associationLine->count() - 1;
2576     QPointF p0, p1;
2577     switch (tr) {
2578         case TextRole::RoleAName:
2579         case TextRole::MultiA:
2580         case TextRole::ChangeA:
2581             p0 = m_associationLine->point(0);
2582             p1 = m_associationLine->point(1);
2583             // If we are dealing with a single line then tie the
2584             // role label to the proper half of the line, i.e.
2585             // the role label must be closer to the "other"
2586             // role object.
2587             if (lastSegment == 1)
2588                 p1 = midPoint(p0, p1);
2589             break;
2590         case TextRole::RoleBName:
2591         case TextRole::MultiB:
2592         case TextRole::ChangeB:
2593             p0 = m_associationLine->point(lastSegment - 1);
2594             p1 = m_associationLine->point(lastSegment);
2595             if (lastSegment == 1)
2596                 p0 = midPoint(p0, p1);
2597             break;
2598         case TextRole::Name:
2599         case TextRole::Coll_Message:  // CHECK: collab.msg texts seem to be TextRole::Name
2600         case TextRole::State:         // CHECK: is this used?
2601             // Find the linepath segment to which the (textX, textY) is closest
2602             // and constrain to the corridor of that segment (see farther below)
2603             {
2604                 int minDistSquare = 100000;  // utopian initial value
2605                 int lpIndex = 0;
2606                 for (int i = 0; i < lastSegment; ++i) {
2607                     p0 = m_associationLine->point(i);
2608                     p1 = m_associationLine->point(i + 1);
2609                     QPointF midP = midPoint(p0, p1);
2610                     const int deltaX = textCenterX - midP.x();
2611                     const int deltaY = textCenterY - midP.y();
2612                     const int cSquare = deltaX * deltaX + deltaY * deltaY;
2613                     if (cSquare < minDistSquare) {
2614                         minDistSquare = cSquare;
2615                         lpIndex = i;
2616                     }
2617                 }
2618                 p0 = m_associationLine->point(lpIndex);
2619                 p1 = m_associationLine->point(lpIndex + 1);
2620             }
2621             break;
2622         default:
2623             uError() << "unexpected TextRole::Enum " << tr;
2624             return;
2625             break;
2626     }
2627     /* Constraint:
2628        The midpoint between p0 and p1 is taken to be the center of a circle
2629        with radius D/2 where D is the distance between p0 and p1.
2630        The text center needs to be within this circle else it is constrained
2631        to the nearest point on the circle.
2632      */
2633     p0 = swapXY(p0);    // go to the natural coordinate system
2634     p1 = swapXY(p1);    // with (0,0) in the lower left corner
2635     QPointF midP = midPoint(p0, p1);
2636     // If (textX,textY) is not inside the circle around midP then
2637     // constrain (textX,textY) to the nearest point on that circle.
2638     const int x0 = p0.x();
2639     const int y0 = p0.y();
2640     const int x1 = p1.x();
2641     const int y1 = p1.y();
2642     double r = sqrt((double)((x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0))) / 2;
2643     if (textWidth > r)
2644         r = textWidth;
2645     // swap textCenter{X,Y} to convert from Qt coord.system.
2646     const QPointF origTextCenter(textCenterY, textCenterX);
2647     const int relX = fabs(origTextCenter.x() - midP.x());
2648     const int relY = fabs(origTextCenter.y() - midP.y());
2649     const double negativeWhenInsideCircle = relX * relX + relY * relY - r * r;
2650     if (negativeWhenInsideCircle <= 0.0) {
2651         return;
2652     }
2653     /*
2654      The original constraint was to snap the text position to the
2655      midpoint but that creates unpleasant visual jitter:
2656     textX = midP.y() - textWidth / 2;   // go back to Qt coord.sys.
2657     textY = midP.x() - textHeight / 2;  // go back to Qt coord.sys.
2658 
2659      Rather, we project the text position onto the closest point
2660      on the circle:
2661 
2662      Circle equation:
2663        relX^2 + relY^2 - r^2 = 0, or in other words
2664        relY^2 = r^2 - relX^2, or
2665        relY = sqrt(r^2 - relX^2)
2666      Line equation:
2667        relY = a * relX + b
2668          We can omit "b" because relX and relY are already relative to
2669          the circle origin, therefore we can also write:
2670        a = relY / relX
2671      To obtain the point of intersection between the circle of radius r
2672      and the line connecting the circle origin with the point (relX, relY),
2673      we equate the relY:
2674        a * x = sqrt(r^2 - x^2), or in other words
2675        a^2 * x^2 = r^2 - x^2, or
2676        x^2 * (a^2 + 1) = r^2, or
2677        x^2 = r^2 / (a^2 + 1), or
2678        x = sqrt(r^2 / (a^2 + 1))
2679      and then
2680        y = a * x
2681      The resulting x and y are relative to the circle origin so we just add
2682      the circle origin (X, Y) to obtain the constrained (textX, textY).
2683      */
2684     // Handle the special case, relX = 0.
2685     if (relX == 0) {
2686         if (origTextCenter.y() > midP.y())
2687             textX = midP.y() + (int)r;   // go back to Qt coord.sys.
2688         else
2689             textX = midP.y() - (int)r;   // go back to Qt coord.sys.
2690         textX -= textWidth / 2;
2691         return;
2692     }
2693     const double a = (double)relY / (double)relX;
2694     const double x = sqrt(r*r / (a*a + 1));
2695     const double y = a * x;
2696     if (origTextCenter.x() > midP.x())
2697         textY = midP.x() + (int)x;   // go back to Qt coord.sys.
2698     else
2699         textY = midP.x() - (int)x;   // go back to Qt coord.sys.
2700     textY -= textHeight / 2;
2701     if (origTextCenter.y() > midP.y())
2702         textX = midP.y() + (int)y;   // go back to Qt coord.sys.
2703     else
2704         textX = midP.y() - (int)y;   // go back to Qt coord.sys.
2705     textX -= textWidth / 2;
2706 }
2707 
2708 /**
2709  * Puts the text widget with the given role at the given position.
2710  * This method calls @ref calculateTextPostion to get the needed position.
2711  * I.e. the line segment it is on has moved and it should move the same
2712  * amount as the line.
2713  */
setTextPosition(Uml::TextRole::Enum role)2714 void AssociationWidget::setTextPosition(Uml::TextRole::Enum role)
2715 {
2716     bool startMove = false;
2717     if (m_role[RoleType::A].getStartMove())
2718         startMove = true;
2719     else if (m_role[RoleType::B].getStartMove())
2720         startMove = true;
2721     else if (m_nameWidget && m_nameWidget->getStartMove())
2722         startMove = true;
2723     if (startMove) {
2724         return;
2725     }
2726     FloatingTextWidget *ft = textWidgetByRole(role);
2727     if (ft == 0)
2728         return;
2729     QPointF pos = calculateTextPosition(role);
2730     ft->setX(pos.x());
2731     ft->setY(pos.y());
2732 }
2733 
2734 /**
2735  * Moves the text widget with the given role by the difference between
2736  * the two points.
2737  */
setTextPositionRelatively(Uml::TextRole::Enum role,const QPointF & oldPosition)2738 void AssociationWidget::setTextPositionRelatively(Uml::TextRole::Enum role, const QPointF &oldPosition)
2739 {
2740     bool startMove = false;
2741     if (m_role[RoleType::A].getStartMove())
2742         startMove = true;
2743     else if (m_role[RoleType::B].getStartMove())
2744         startMove = true;
2745     else if (m_nameWidget && m_nameWidget->getStartMove())
2746         startMove = true;
2747 
2748     if (startMove) {
2749         return;
2750     }
2751     FloatingTextWidget *ft = textWidgetByRole(role);
2752     if (ft == 0)
2753         return;
2754     qreal ftX = ft->x();
2755     qreal ftY = ft->y();
2756 
2757     QPointF pos = calculateTextPosition(role);
2758     int relX = pos.x() - oldPosition.x();
2759     int relY = pos.y() - oldPosition.y();
2760     qreal ftNewX = ftX + relX;
2761     qreal ftNewY = ftY + relY;
2762 
2763     bool oldIgnoreSnapToGrid = ft->getIgnoreSnapToGrid();
2764     ft->setIgnoreSnapToGrid(true);
2765     ft->setX(ftNewX);
2766     ft->setY(ftNewY);
2767     ft->setIgnoreSnapToGrid(oldIgnoreSnapToGrid);
2768 }
2769 
2770 /**
2771  * Remove dashed connecting line for association class.
2772  */
removeAssocClassLine()2773 void AssociationWidget::removeAssocClassLine()
2774 {
2775     delete m_pAssocClassLineSel0;
2776     m_pAssocClassLineSel0 = 0;
2777     delete m_pAssocClassLineSel1;
2778     m_pAssocClassLineSel1 = 0;
2779     delete m_pAssocClassLine;
2780     m_pAssocClassLine = 0;
2781     if (m_associationClass) {
2782         m_associationClass->setClassAssociationWidget(0);
2783         m_associationClass = 0;
2784     }
2785 }
2786 
2787 /**
2788  * Creates the association class connecting line.
2789  */
createAssocClassLine()2790 void AssociationWidget::createAssocClassLine()
2791 {
2792     if (m_pAssocClassLine == 0) {
2793         m_pAssocClassLine = new QGraphicsLineItem(this);
2794     }
2795     QPen pen(lineColor(), lineWidth(), Qt::DashLine);
2796     m_pAssocClassLine->setPen(pen);
2797     // decoration points
2798     m_pAssocClassLineSel0 = Widget_Utils::decoratePoint(m_pAssocClassLine->line().p1(),
2799                                                         m_pAssocClassLine);
2800     m_pAssocClassLineSel1 = Widget_Utils::decoratePoint(m_pAssocClassLine->line().p2(),
2801                                                         m_pAssocClassLine);
2802     computeAssocClassLine();
2803     selectAssocClassLine(false);
2804 }
2805 
2806 /**
2807  * Creates the association class connecting line using the specified
2808  * ClassifierWidget.
2809  *
2810  * @param classifier The ClassifierWidget to use.
2811  * @param linePathSegmentIndex The index of the segment where the
2812  *        association class is created.
2813  */
createAssocClassLine(ClassifierWidget * classifier,int linePathSegmentIndex)2814 void AssociationWidget::createAssocClassLine(ClassifierWidget* classifier,
2815                                              int linePathSegmentIndex)
2816 {
2817     m_nLinePathSegmentIndex = linePathSegmentIndex;
2818 
2819     if (m_nLinePathSegmentIndex < 0) {
2820         return;
2821     }
2822 
2823     m_associationClass = classifier;
2824     m_associationClass->setClassAssociationWidget(this);
2825     m_associationClass->addAssoc(this);  // to get widgetMoved(...) for association classes
2826 
2827     createAssocClassLine();
2828 }
2829 
2830 /**
2831  * Compute the end points of m_pAssocClassLine in case this
2832  * association has an attached association class.
2833  * TODO: The decoration points make no sense for now, because they are not movable.
2834  */
computeAssocClassLine()2835 void AssociationWidget::computeAssocClassLine()
2836 {
2837     if (m_associationClass == 0 || m_pAssocClassLine == 0) {
2838         return;
2839     }
2840     if (m_nLinePathSegmentIndex < 0) {
2841         uError() << "m_nLinePathSegmentIndex is not set";
2842         return;
2843     }
2844 
2845     QPointF segStart = m_associationLine->point(m_nLinePathSegmentIndex);
2846     QPointF segEnd = m_associationLine->point(m_nLinePathSegmentIndex + 1);
2847     const qreal midSegX = segStart.x() + (segEnd.x() - segStart.x()) / 2.0;
2848     const qreal midSegY = segStart.y() + (segEnd.y() - segStart.y()) / 2.0;
2849     QPointF segmentMidPoint(midSegX, midSegY);
2850 
2851     QLineF possibleAssocLine = QLineF(segmentMidPoint,
2852                                       m_associationClass->mapRectToScene(m_associationClass->rect()).center());
2853     QPointF intersectionPoint;
2854     QLineF::IntersectType type = intersect(m_associationClass->mapRectToScene(m_associationClass->boundingRect()),
2855                                            possibleAssocLine,
2856                                            &intersectionPoint);
2857     // DEBUG(DBG_SRC) << "intersect type=" << type << " / point=" << intersectionPoint;
2858 
2859     if (type == QLineF::BoundedIntersection) {
2860         m_pAssocClassLine->setLine(midSegX, midSegY,
2861                                    intersectionPoint.x(), intersectionPoint.y());
2862 
2863         if (m_pAssocClassLineSel0 && m_pAssocClassLineSel1) {
2864             m_pAssocClassLineSel0->setPos(m_pAssocClassLine->line().p1());
2865             m_pAssocClassLineSel1->setPos(m_pAssocClassLine->line().p2());
2866         }
2867     }
2868 }
2869 
2870 /**
2871  * Renders the association class connecting line selected.
2872  */
selectAssocClassLine(bool sel)2873 void AssociationWidget::selectAssocClassLine(bool sel)
2874 {
2875     if (m_pAssocClassLineSel0 && m_pAssocClassLineSel1) {
2876         m_pAssocClassLineSel0->setVisible(sel);
2877         m_pAssocClassLineSel1->setVisible(sel);
2878     }
2879 }
2880 
2881 /**
2882  * Sets the association to be selected.
2883  */
mousePressEvent(QGraphicsSceneMouseEvent * me)2884 void AssociationWidget::mousePressEvent(QGraphicsSceneMouseEvent * me)
2885 {
2886     // clear other selected stuff on the screen of ShiftKey
2887     if (me->modifiers() != Qt::ShiftModifier) {
2888         m_scene->clearSelected();
2889     }
2890 
2891     if (me->button() == Qt::LeftButton && me->modifiers() == Qt::ControlModifier) {
2892         if (checkRemovePoint(me->scenePos()))
2893             return;
2894     }
2895 
2896     // make sure we should be here depending on the button
2897     if (me->button() != Qt::RightButton && me->button() != Qt::LeftButton) {
2898         return;
2899     }
2900     QPointF mep = me->scenePos();
2901     // see if `mep' is on the connecting line to the association class
2902     if (onAssocClassLine(mep)) {
2903         setSelected(true);
2904         selectAssocClassLine();
2905         return;
2906     }
2907     setSelected(!isSelected());
2908     associationLine()->mousePressEvent(me);
2909 }
2910 
2911 /**
2912  * Displays the right mouse buttom menu if right button is pressed.
2913  */
mouseReleaseEvent(QGraphicsSceneMouseEvent * me)2914 void AssociationWidget::mouseReleaseEvent(QGraphicsSceneMouseEvent * me)
2915 {
2916     associationLine()->mouseReleaseEvent(me);
2917 }
2918 
2919 /**
2920  * Handles the selection from the popup menu.
2921  */
slotMenuSelection(QAction * action)2922 void AssociationWidget::slotMenuSelection(QAction* action)
2923 {
2924     QString oldText, newText;
2925     bool ok = false;
2926     Uml::AssociationType::Enum atype = associationType();
2927     Uml::RoleType::Enum r = RoleType::B;
2928     ListPopupMenu::MenuType sel = ListPopupMenu::typeFromAction(action);
2929     DEBUG(DBG_SRC) << "menu selection = " << ListPopupMenu::toString(sel);
2930 
2931     // if it's a collaboration message we now just use the code in floatingtextwidget
2932     // this means there's some redundant code below but that's better than duplicated code
2933     if (isCollaboration() && sel != ListPopupMenu::mt_Delete) {
2934         m_nameWidget->slotMenuSelection(action);
2935         return;
2936     }
2937 
2938     switch(sel) {
2939     case ListPopupMenu::mt_Properties:
2940         if (atype == AssociationType::Seq_Message || atype == AssociationType::Seq_Message_Self) {
2941             // show op dlg for seq. diagram here
2942             // don't worry about here, I don't think it can get here as
2943             // line is widget on seq. diagram
2944             // here just in case - remove later after testing
2945             DEBUG(DBG_SRC) << "mt_Properties: assoctype is " << atype;
2946         } else {  //standard assoc dialog
2947             UMLApp::app()->docWindow()->updateDocumentation(false);
2948             showPropertiesDialog();
2949         }
2950         break;
2951 
2952     case ListPopupMenu::mt_Add_Point:
2953         checkAddPoint(m_eventScenePos);
2954         break;
2955 
2956     case ListPopupMenu::mt_Delete_Point:
2957         checkRemovePoint(m_eventScenePos);
2958         break;
2959 
2960     case ListPopupMenu::mt_Auto_Layout_Spline:
2961         checkAutoLayoutSpline();
2962         break;
2963 
2964     case ListPopupMenu::mt_Delete:
2965         if (!Dialog_Utils::askDeleteAssociation())
2966             break;
2967         if (m_pAssocClassLineSel0)
2968             removeAssocClassLine();
2969         else if (association())
2970             m_scene->removeAssocInViewAndDoc(this);
2971         else
2972             m_scene->removeWidgetCmd(this);
2973         break;
2974 
2975     case ListPopupMenu::mt_Rename_MultiA:
2976         r = RoleType::A;   // fall through
2977     case ListPopupMenu::mt_Rename_MultiB:
2978         if (m_role[r].multiplicityWidget)
2979             oldText = m_role[r].multiplicityWidget->text();
2980         else
2981             oldText = QString();
2982         newText = oldText;
2983         ok = Dialog_Utils::askName(i18n("Multiplicity"),
2984                                    i18n("Enter multiplicity:"),
2985                                    newText);
2986         if (ok && newText != oldText) {
2987             if (FloatingTextWidget::isTextValid(newText)) {
2988                 setMultiplicity(newText, r);
2989             } else {
2990                 m_scene->removeWidget(m_role[r].multiplicityWidget);
2991                 m_role[r].multiplicityWidget = 0;
2992             }
2993         }
2994         break;
2995 
2996     case ListPopupMenu::mt_Rename_Name:
2997         if (m_nameWidget)
2998             oldText = m_nameWidget->text();
2999         else
3000             oldText = QString();
3001         newText = oldText;
3002         ok = Dialog_Utils::askName(i18n("Association Name"),
3003                                    i18n("Enter association name:"),
3004                                    newText);
3005         if (ok && newText != oldText) {
3006             if (FloatingTextWidget::isTextValid(newText)) {
3007                 setName(newText);
3008             } else if (m_nameWidget) {
3009                 m_scene->removeWidget(m_nameWidget);
3010                 m_nameWidget = 0;
3011             }
3012         }
3013         break;
3014 
3015     case ListPopupMenu::mt_Rename_RoleAName:
3016         r = RoleType::A;   // fall through
3017     case ListPopupMenu::mt_Rename_RoleBName:
3018         if (m_role[r].roleWidget)
3019             oldText = m_role[r].roleWidget->text();
3020         else
3021             oldText = QString();
3022         newText = oldText;
3023         ok = Dialog_Utils::askName(i18n("Role Name"),
3024                                    i18n("Enter role name:"),
3025                                    newText);
3026         if (ok && newText != oldText) {
3027             if (FloatingTextWidget::isTextValid(newText)) {
3028                 setRoleName(newText, r);
3029             } else {
3030                 m_scene->removeWidget(m_role[r].roleWidget);
3031                 m_role[r].roleWidget = 0;
3032             }
3033         }
3034         break;
3035 
3036     case ListPopupMenu::mt_Change_Font:
3037         {
3038 #if QT_VERSION >= 0x050000
3039             bool ok = false;
3040             QFont fnt = QFontDialog::getFont(&ok, font(), m_scene->activeView());
3041             if (ok)
3042 #else
3043             QFont fnt = font();
3044             if (KFontDialog::getFont(fnt, KFontChooser::NoDisplayFlags, m_scene->activeView()))
3045 #endif
3046                 lwSetFont(fnt);
3047         }
3048         break;
3049 
3050     case ListPopupMenu::mt_Line_Color:
3051         {
3052 #if QT_VERSION >= 0x050000
3053             QColor newColor = QColorDialog::getColor(lineColor());
3054             if (newColor.isValid() && newColor != lineColor())
3055 #else
3056             QColor newColor;
3057             if (KColorDialog::getColor(newColor))
3058 #endif
3059             {
3060                 m_scene->selectionSetLineColor(newColor);
3061                 umlDoc()->setModified(true);
3062             }
3063         }
3064         break;
3065 
3066     case ListPopupMenu::mt_Cut:
3067         m_scene->setStartedCut();
3068         UMLApp::app()->slotEditCut();
3069         break;
3070 
3071     case ListPopupMenu::mt_Copy:
3072         UMLApp::app()->slotEditCopy();
3073         break;
3074 
3075     case ListPopupMenu::mt_Paste:
3076         UMLApp::app()->slotEditPaste();
3077         break;
3078 
3079     case ListPopupMenu::mt_Reset_Label_Positions:
3080         resetTextPositions();
3081         break;
3082 
3083     case ListPopupMenu::mt_LayoutDirect:
3084         m_associationLine->setLayout(Uml::LayoutType::Direct);
3085         break;
3086     case ListPopupMenu::mt_LayoutSpline:
3087         m_associationLine->setLayout(Uml::LayoutType::Spline);
3088         break;
3089     case ListPopupMenu::mt_LayoutOrthogonal:
3090         m_associationLine->setLayout(Uml::LayoutType::Orthogonal);
3091         break;
3092     case ListPopupMenu::mt_LayoutPolyline:
3093         m_associationLine->setLayout(Uml::LayoutType::Polyline);
3094         break;
3095 
3096     default:
3097         DEBUG(DBG_SRC) << "MenuType " << ListPopupMenu::toString(sel) << " not implemented";
3098         break;
3099     }//end switch
3100 }
3101 
3102 /**
3103  * Return the first font found being used by any child widget. (They
3104  * could be different fonts, so this is a slightly misleading method.)
3105  */
font() const3106 QFont AssociationWidget::font() const
3107 {
3108     //:TODO: find a general font for the association
3109     QFont font;
3110 
3111     if (m_role[RoleType::A].roleWidget)
3112         font = m_role[RoleType::A].roleWidget->font();
3113     else    if (m_role[RoleType::B].roleWidget)
3114         font = m_role[RoleType::B].roleWidget->font();
3115     else    if (m_role[RoleType::A].multiplicityWidget)
3116         font = m_role[RoleType::A].multiplicityWidget->font();
3117     else    if (m_role[RoleType::B].multiplicityWidget)
3118         font = m_role[RoleType::B].multiplicityWidget->font();
3119     else    if (m_role[RoleType::A].changeabilityWidget)
3120         font = m_role[RoleType::A].changeabilityWidget->font();
3121     else    if (m_role[RoleType::B].changeabilityWidget)
3122         font = m_role[RoleType::B].changeabilityWidget->font();
3123     else    if (m_nameWidget)
3124         font = m_nameWidget->font();
3125     else
3126         font = m_role[RoleType::A].umlWidget->font();
3127 
3128     return font;
3129 }
3130 
3131 /**
3132  * Set all 'owned' child widgets to this text color.
3133  */
setTextColor(const QColor & color)3134 void AssociationWidget::setTextColor(const QColor &color)
3135 {
3136     WidgetBase::setTextColor(color);
3137     if (m_nameWidget) {
3138         m_nameWidget->setTextColor(color);
3139     }
3140     if (m_role[RoleType::A].roleWidget) {
3141         m_role[RoleType::A].roleWidget->setTextColor(color);
3142     }
3143     if (m_role[RoleType::B].roleWidget) {
3144         m_role[RoleType::B].roleWidget->setTextColor(color);
3145     }
3146     if (m_role[RoleType::A].multiplicityWidget) {
3147         m_role[RoleType::A].multiplicityWidget->setTextColor(color);
3148     }
3149     if (m_role[RoleType::B].multiplicityWidget) {
3150         m_role[RoleType::B].multiplicityWidget->setTextColor(color);
3151     }
3152     if (m_role[RoleType::A].changeabilityWidget)
3153         m_role[RoleType::A].changeabilityWidget->setTextColor(color);
3154     if (m_role[RoleType::B].changeabilityWidget)
3155         m_role[RoleType::B].changeabilityWidget->setTextColor(color);
3156 }
3157 
setLineColor(const QColor & color)3158 void AssociationWidget::setLineColor(const QColor &color)
3159 {
3160     WidgetBase::setLineColor(color);
3161     QPen pen = m_associationLine->pen();
3162     pen.setColor(color);
3163     m_associationLine->setPen(pen);
3164 }
3165 
setLineWidth(uint width)3166 void AssociationWidget::setLineWidth(uint width)
3167 {
3168     WidgetBase::setLineWidth(width);
3169     QPen pen = m_associationLine->pen();
3170     pen.setWidth(width);
3171     m_associationLine->setPen(pen);
3172 }
3173 
checkAddPoint(const QPointF & scenePos)3174 bool AssociationWidget::checkAddPoint(const QPointF &scenePos)
3175 {
3176     if (associationType() == AssociationType::Exception) {
3177         return false;
3178     }
3179 
3180     // if there is no point around the mouse pointer, we insert a new one
3181     if (m_associationLine->closestPointIndex(scenePos) < 0) {
3182         int i = m_associationLine->closestSegmentIndex(scenePos);
3183         if (i < 0) {
3184             DEBUG(DBG_SRC) << "no closest segment found!";
3185             return false;
3186         }
3187         // switch type to see additional points by default
3188         if (m_associationLine->count() == 2)
3189             m_associationLine->setLayout(Uml::LayoutType::Polyline);
3190         m_associationLine->insertPoint(i + 1, scenePos);
3191         if (m_nLinePathSegmentIndex == i) {
3192             QPointF segStart = m_associationLine->point(i);
3193             QPointF segEnd = m_associationLine->point(i + 2);
3194             const int midSegX = segStart.x() + (segEnd.x() - segStart.x()) / 2;
3195             const int midSegY = segStart.y() + (segEnd.y() - segStart.y()) / 2;
3196             /*
3197             DEBUG(DBG_SRC) << "segStart=" << segStart << ", segEnd=" << segEnd
3198                            << ", midSeg=(" << midSegX << "," << midSegY
3199                            << "), mp=" << mp;
3200              */
3201             if (midSegX > scenePos.x() || midSegY < scenePos.y()) {
3202                 m_nLinePathSegmentIndex++;
3203                 DEBUG(DBG_SRC) << "setting m_nLinePathSegmentIndex to "
3204                                << m_nLinePathSegmentIndex;
3205                 computeAssocClassLine();
3206             }
3207             m_associationLine->update();
3208             calculateNameTextSegment();
3209             umlDoc()->setModified(true);
3210             setSelected(true);
3211         }
3212         return true;
3213     }
3214     else {
3215         DEBUG(DBG_SRC) << "found point already close enough!";
3216         return false;
3217     }
3218 }
3219 
3220 /**
3221  * Remove point close to the given point and redraw the association.
3222  * @param scenePos   point which should be removed
3223  * @return   success status of the remove action
3224  */
checkRemovePoint(const QPointF & scenePos)3225 bool AssociationWidget::checkRemovePoint(const QPointF &scenePos)
3226 {
3227     int i = m_associationLine->closestPointIndex(scenePos);
3228     if (i == -1)
3229         return false;
3230 
3231     m_associationLine->setSelected(false);
3232 
3233     // there was a point so we remove the point
3234     m_associationLine->removePoint(i);
3235 
3236     // switch type back to simple line
3237     if (m_associationLine->count() == 2)
3238         m_associationLine->setLayout(Uml::LayoutType::Direct);
3239 
3240     // Maybe reattach association class connecting line
3241     // to different association linepath segment.
3242     const int numberOfLines = m_associationLine->count() - 1;
3243     if (m_nLinePathSegmentIndex >= numberOfLines) {
3244         m_nLinePathSegmentIndex = numberOfLines - 1;
3245     }
3246     calculateEndingPoints();
3247 
3248     // select the line path
3249     m_associationLine->setSelected(true);
3250 
3251     m_associationLine->update();
3252 
3253     calculateNameTextSegment();
3254     umlDoc()->setModified(true);
3255     return true;
3256 }
3257 
checkAutoLayoutSpline()3258 bool AssociationWidget::checkAutoLayoutSpline() {
3259     m_associationLine->enableAutoLayout();
3260     m_associationLine->update();
3261     return true;
3262 }
3263 
3264 /**
3265  * Moves the break point being dragged.
3266  */
mouseMoveEvent(QGraphicsSceneMouseEvent * me)3267 void AssociationWidget::mouseMoveEvent(QGraphicsSceneMouseEvent* me)
3268 {
3269     if (me->buttons() != Qt::LeftButton) {
3270         return;
3271     }
3272 
3273     setSelected(true);
3274 
3275     associationLine()->mouseMoveEvent(me);
3276     moveEvent(me);
3277     m_scene->resizeSceneToItems();
3278 }
3279 
3280 /**
3281  * Returns the Region the widget to line intersection is for the given
3282  * widget in this Association.  If the given widget is not in the
3283  * Association then Region::Error is returned.
3284  * Used by @ref calculateEndingPoints to work these positions out for
3285  * another Association - since the number of Associations on the same
3286  * region for the same widget will mean the lines will need to be
3287  * spread out across the region.
3288  */
3289 //Uml::Region::Enum AssociationWidget::getWidgetRegion(AssociationWidget * widget) const
3290 //{
3291 //    if (widget->widgetForRole(RoleType::A) == m_role[RoleType::A].umlWidget)
3292 //        return m_role[RoleType::A].m_WidgetRegion;
3293 //    if (widget->widgetForRole(RoleType::B) == m_role[RoleType::B].umlWidget)
3294 //        return m_role[RoleType::B].m_WidgetRegion;
3295 //    return Uml::Region::Error;
3296 //}
3297 
3298 /**
3299  * Returns the number of lines there are on the given region for
3300  * either widget A or B of the association.
3301  */
getRegionCount(Uml::Region::Enum region,Uml::RoleType::Enum role)3302 int AssociationWidget::getRegionCount(Uml::Region::Enum region, Uml::RoleType::Enum role)
3303 {
3304     if ((region == Uml::Region::Error) | (umlScene() == 0)) {
3305         return 0;
3306     }
3307     int widgetCount = 0;
3308     AssociationWidgetList list = m_scene->associationList();
3309     foreach (AssociationWidget* assocwidget, list) {
3310         //don't count this association
3311         if (assocwidget == this)
3312             continue;
3313         const AssociationWidgetRole& otherA = assocwidget->m_role[RoleType::A];
3314         const AssociationWidgetRole& otherB = assocwidget->m_role[RoleType::B];
3315         const UMLWidget *a = otherA.umlWidget;
3316         const UMLWidget *b = otherB.umlWidget;
3317         /*
3318         //don't count associations to self if both of their end points are on the same region
3319         //they are different and placement won't interfere with them
3320         if (a == b && otherA.m_WidgetRegion == otherB.m_WidgetRegion)
3321                 continue;
3322          */
3323         if (m_role[role].umlWidget == a && region == otherA.m_WidgetRegion)
3324             widgetCount++;
3325         else if (m_role[role].umlWidget == b && region == otherB.m_WidgetRegion)
3326             widgetCount++;
3327     }//end foreach
3328     return widgetCount;
3329 }
3330 
3331 /**
3332  * Find the border point of the given rect when a line is drawn from the
3333  * given point to the rect.
3334  * @param rect   rect of a classifier
3335  * @param line   a line to the rect
3336  * @param intersectionPoint   the intercept point on the border of the rect
3337  * @return   the type of the intersection @ref QLineF::IntersectType
3338  */
intersect(const QRectF & rect,const QLineF & line,QPointF * intersectionPoint)3339 QLineF::IntersectType AssociationWidget::intersect(const QRectF &rect, const QLineF &line,
3340                                                    QPointF* intersectionPoint)
3341 {
3342     QList<QLineF> lines;
3343     lines << QLineF(rect.topLeft(), rect.topRight());
3344     lines << QLineF(rect.topRight(), rect.bottomRight());
3345     lines << QLineF(rect.bottomRight(), rect.bottomLeft());
3346     lines << QLineF(rect.bottomLeft(), rect.topLeft());
3347     foreach (const QLineF& rectLine, lines) {
3348         QLineF::IntersectType type = rectLine.intersect(line, intersectionPoint);
3349         if (type == QLineF::BoundedIntersection) {
3350             return type;
3351         }
3352     }
3353     return QLineF::NoIntersection;
3354 }
3355 
3356 /**
3357  * Given a rectangle and a point, findInterceptOnEdge computes the
3358  * connecting line between the middle point of the rectangle and
3359  * the point, and returns the intercept of this line with the
3360  * the edge of the rectangle identified by `region'.
3361  * When the region is North or South, the X value is returned (Y is
3362  * constant.)
3363  * When the region is East or West, the Y value is returned (X is
3364  * constant.)
3365  * @todo This is buggy. Try replacing by intersect()
3366  */
findInterceptOnEdge(const QRectF & rect,Uml::Region::Enum region,const QPointF & point)3367 qreal AssociationWidget::findInterceptOnEdge(const QRectF &rect,
3368                                              Uml::Region::Enum region,
3369                                              const QPointF &point)
3370 {
3371     // The Qt coordinate system has (0, 0) in the top left corner.
3372     // In order to go to the regular XY coordinate system with (0, 0)
3373     // in the bottom left corner, we swap the X and Y axis.
3374     // That's why the following assignments look twisted.
3375     const qreal rectHalfWidth = rect.height() / 2.0;
3376     const qreal rectHalfHeight = rect.width() / 2.0;
3377     const qreal rectMidX = rect.y() + rectHalfWidth;
3378     const qreal rectMidY = rect.x() + rectHalfHeight;
3379     const qreal dX = rectMidX - point.y();
3380     const qreal dY = rectMidY - point.x();
3381     switch (region) {
3382     case Uml::Region::West:
3383         region = Uml::Region::South;
3384         break;
3385     case Uml::Region::North:
3386         region = Uml::Region::West;
3387         break;
3388     case Uml::Region::East:
3389         region = Uml::Region::North;
3390         break;
3391     case Uml::Region::South:
3392         region = Uml::Region::East;
3393         break;
3394     default:
3395         break;
3396     }
3397     // Now we have regular coordinates with the point (0, 0) in the
3398     // bottom left corner.
3399     if (region == Uml::Region::North || region == Uml::Region::South) {
3400         if (dX == 0)
3401             return rectMidY;
3402         // should be rectMidX, but we go back to Qt coord.sys.
3403         if (dY == 0) {
3404             uError() << "usage error: " << "North/South (dY == 0)";
3405             return -1.0;
3406         }
3407         const qreal m = dY / dX;
3408         qreal relativeX;
3409         if (region == Uml::Region::North)
3410             relativeX = rectHalfHeight / m;
3411         else
3412             relativeX = -rectHalfHeight / m;
3413         return (rectMidY + relativeX);
3414         // should be rectMidX, but we go back to Qt coord.sys.
3415     } else {
3416         if (dY == 0)
3417             return rectMidX;
3418         // should be rectMidY, but we go back to Qt coord.sys.
3419         if (dX == 0) {
3420             uError() << "usage error: " << "East/West (dX == 0)";
3421             return -1.0;
3422         }
3423         const qreal m = dY / dX;
3424         qreal relativeY = m * rectHalfWidth;
3425         if (region == Uml::Region::West)
3426             relativeY = -relativeY;
3427         return (rectMidX + relativeY);
3428         // should be rectMidY, but we go back to Qt coord.sys.
3429     }
3430 }
3431 
3432 /**
3433  * Auxiliary method for updateAssociations():
3434  * Put position into m_positions and assoc into m_ordered at the
3435  * correct index.
3436  * m_positions and m_ordered move in parallel and are sorted by
3437  * ascending position.
3438  */
insertIntoLists(qreal position,const AssociationWidget * assoc)3439 void AssociationWidget::insertIntoLists(qreal position, const AssociationWidget* assoc)
3440 {
3441     bool did_insertion = false;
3442     for (int index = 0; index < m_positions_len; ++index) {
3443         if (position < m_positions[index]) {
3444             for (int moveback = m_positions_len; moveback > index; moveback--)
3445                 m_positions[moveback] = m_positions[moveback - 1];
3446             m_positions[index] = position;
3447             m_ordered.insert(index, const_cast<AssociationWidget*>(assoc));
3448             did_insertion = true;
3449             break;
3450         }
3451     }
3452     if (! did_insertion) {
3453         m_positions[m_positions_len] = position;
3454         m_ordered.append(const_cast<AssociationWidget*>(assoc));
3455     }
3456     m_positions_len++;
3457 }
3458 
3459 /**
3460  * Tells all the other view associations the new count for the
3461  * given widget on a certain region. And also what index they should be.
3462  */
updateAssociations(int totalCount,Uml::Region::Enum region,Uml::RoleType::Enum role)3463 void AssociationWidget::updateAssociations(int totalCount,
3464                                            Uml::Region::Enum region,
3465                                            Uml::RoleType::Enum role)
3466 {
3467     if ((region == Uml::Region::Error) | (umlScene() == 0)) {
3468         return;
3469     }
3470     AssociationWidgetList list = m_scene->associationList();
3471 
3472     UMLWidget *ownWidget = m_role[role].umlWidget;
3473     m_positions_len = 0;
3474     m_ordered.clear();
3475     // we order the AssociationWidget list by region and x/y value
3476     foreach (AssociationWidget* assocwidget, list) {
3477         AssociationWidgetRole *roleA = &assocwidget->m_role[RoleType::A];
3478         AssociationWidgetRole *roleB = &assocwidget->m_role[RoleType::B];
3479         UMLWidget *wA = roleA->umlWidget;
3480         UMLWidget *wB = roleB->umlWidget;
3481         // Skip self associations.
3482         if (wA == wB)
3483             continue;
3484         // Now we must find out with which end the assocwidget connects
3485         // to the input widget (ownWidget).
3486         bool inWidgetARegion = (ownWidget == wA &&
3487                                  region == roleA->m_WidgetRegion);
3488         bool inWidgetBRegion = (ownWidget == wB &&
3489                                  region == roleB->m_WidgetRegion);
3490         if (!inWidgetARegion && !inWidgetBRegion)
3491             continue;
3492         // Determine intercept position on the edge indicated by `region'.
3493         UMLWidget * otherWidget = (inWidgetARegion ? wB : wA);
3494         AssociationLine *linepath = assocwidget->associationLine();
3495         QPointF refpoint;
3496         if (assocwidget->linePathStartsAt(otherWidget))
3497             refpoint = linepath->point(linepath->count() - 2);
3498         else
3499             refpoint = linepath->point(1);
3500         // The point is authoritative if we're called for the second time
3501         // (i.e. role==B) or it is a waypoint on the line path.
3502         bool pointIsAuthoritative = (role == RoleType::B || linepath->count() > 2);
3503         if (! pointIsAuthoritative) {
3504             // If the point is not authoritative then we use the other
3505             // widget's center.
3506             refpoint.setX(otherWidget->scenePos().x() + otherWidget->width() / 2);
3507             refpoint.setY(otherWidget->scenePos().y() + otherWidget->height() / 2);
3508         }
3509         qreal intercept = findInterceptOnEdge(ownWidget->rect(), region, refpoint);
3510         if (intercept < 0) {
3511             DEBUG(DBG_SRC) << "error from findInterceptOnEdge for"
3512                            << " assocType=" << assocwidget->associationType()
3513                            << " ownWidget=" << ownWidget->name()
3514                            << " otherWidget=" << otherWidget->name();
3515             continue;
3516         }
3517         insertIntoLists(intercept, assocwidget);
3518     } // while ((assocwidget = assoc_it.current()))
3519 
3520     // we now have an ordered list and we only have to call updateRegionLineCount
3521     int index = 1;
3522     foreach (AssociationWidget* assocwidget, m_ordered ) {
3523         if (ownWidget == assocwidget->widgetForRole(RoleType::A)) {
3524             assocwidget->updateRegionLineCount(index++, totalCount, region, RoleType::A);
3525         } else if (ownWidget == assocwidget->widgetForRole(RoleType::B)) {
3526             assocwidget->updateRegionLineCount(index++, totalCount, region, RoleType::B);
3527         }
3528     } // for (assocwidget = ordered.first(); ...)
3529 }
3530 
3531 /**
3532  * Called to tell the association that another association has added
3533  * a line to the region of one of its widgets. The widget is identified
3534  * by its role (A or B).
3535  *
3536  * Called by @ref updateAssociations which is called by
3537  * @ref calculateEndingPoints when required.
3538  */
updateRegionLineCount(int index,int totalCount,Uml::Region::Enum region,Uml::RoleType::Enum role)3539 void AssociationWidget::updateRegionLineCount(int index, int totalCount,
3540                                               Uml::Region::Enum region,
3541                                               Uml::RoleType::Enum role)
3542 {
3543     if ((region == Uml::Region::Error) | (umlScene() == 0)) {
3544         return;
3545     }
3546     // If the association is to self and the line ends are on the same region then
3547     // use a different calculation.
3548     if (isSelf() &&
3549             m_role[RoleType::A].m_WidgetRegion == m_role[RoleType::B].m_WidgetRegion) {
3550         UMLWidget * pWidget = m_role[RoleType::A].umlWidget;
3551         qreal x = pWidget->scenePos().x();
3552         qreal y = pWidget->scenePos().y();
3553         qreal wh = pWidget->height();
3554         qreal ww = pWidget->width();
3555         int size = m_associationLine->count();
3556         // See if above widget ok to place assoc.
3557         switch( m_role[RoleType::A].m_WidgetRegion ) {
3558         case Uml::Region::North:
3559             m_associationLine->setPoint( 0, QPointF( x + ( ww / 4 ), y ) );
3560             m_associationLine->setPoint( size - 1, QPointF(x + ( ww * 3 / 4 ), y ) );
3561             break;
3562 
3563         case Uml::Region::South:
3564             m_associationLine->setPoint( 0, QPointF( x + ( ww / 4 ), y + wh ) );
3565             m_associationLine->setPoint( size - 1, QPointF( x + ( ww * 3 / 4 ), y + wh ) );
3566             break;
3567 
3568         case Uml::Region::East:
3569             m_associationLine->setPoint( 0, QPointF( x + ww, y + ( wh / 4 ) ) );
3570             m_associationLine->setPoint( size - 1, QPointF( x + ww, y + ( wh * 3 / 4 ) ) );
3571             break;
3572 
3573         case Uml::Region::West:
3574             m_associationLine->setPoint( 0, QPointF( x, y + ( wh / 4 ) ) );
3575             m_associationLine->setPoint( size - 1, QPointF( x, y + ( wh * 3 / 4 ) ) );
3576             break;
3577         default:
3578             break;
3579         }//end switch
3580 
3581         return;
3582     }
3583 
3584     AssociationWidgetRole& robj = m_role[role];
3585     UMLWidget * pWidget = robj.umlWidget;
3586 
3587     robj.m_nIndex = index;
3588     robj.m_nTotalCount = totalCount;
3589     qreal x = pWidget->scenePos().x();
3590     qreal y = pWidget->scenePos().y();
3591     qreal ww = pWidget->width();
3592     qreal wh = pWidget->height();
3593     const bool angular = Settings::optionState().generalState.angularlines;
3594     qreal ch = 0;
3595     qreal cw = 0;
3596     if (angular) {
3597         uint nind = (role == RoleType::A ? 1 : m_associationLine->count() - 2);
3598         QPointF neighbour = m_associationLine->point(nind);
3599         if (neighbour.x() < x)
3600             cw = 0;
3601         else if (neighbour.x() > x + ww)
3602             cw = 0 + ww;
3603         else
3604             cw = neighbour.x() - x;
3605         if (neighbour.y() < y)
3606             ch = 0;
3607         else if (neighbour.y() > y + wh)
3608             ch = 0 + wh;
3609         else
3610             ch = neighbour.y() - y;
3611     } else {
3612         ch = wh * index / totalCount;
3613         cw = ww * index / totalCount;
3614     }
3615 
3616     qreal newX = x + cw;
3617     qreal newY = y + ch;
3618 
3619     QPointF pt;
3620     if (angular) {
3621         pt = QPointF(newX, newY);
3622     } else {
3623         UMLWidget *pWidgetA = m_role[RoleType::A].umlWidget;
3624         UMLWidget *pWidgetB = m_role[RoleType::B].umlWidget;
3625         QList<QPolygonF> polyListA = pWidgetA->shape().toSubpathPolygons();
3626         QPolygonF polyA = polyListA.at(0);
3627         if (polyListA.size() > 1) {
3628             for (int i = 1; i < polyListA.size(); i++) {
3629                  polyA = polyA.united(polyListA.at(i));
3630             }
3631         }
3632         polyA = polyA.translated(pWidgetA->getPos());
3633         QList<QPolygonF> polyListB = pWidgetB->shape().toSubpathPolygons();
3634         QPolygonF polyB = polyListB.at(0);
3635         if (polyListB.size() > 1) {
3636             for (int i = 1; i < polyListB.size(); i++) {
3637                  polyB = polyB.united(polyListB.at(i));
3638             }
3639         }
3640         polyB = polyB.translated(pWidgetB->getPos());
3641         QLineF nearestPoints = Widget_Utils::closestPoints(polyA, polyB);
3642         if (nearestPoints.isNull()) {
3643             uError() << "Widget_Utils::closestPoints failed, falling back to simple widget positions";
3644             switch(region) {
3645                 case Uml::Region::West:
3646                     pt.setX(x);
3647                     pt.setY(newY);
3648                     break;
3649                 case Uml::Region::North:
3650                     pt.setX(newX);
3651                     pt.setY(y);
3652                     break;
3653                 case Uml::Region::East:
3654                     pt.setX(x + ww);
3655                     pt.setY(newY);
3656                     break;
3657                 case Uml::Region::South:
3658                     pt.setX(newX);
3659                     pt.setY(y + wh);
3660                     break;
3661                 case Uml::Region::Center:
3662                     pt.setX(x + ww / 2);
3663                     pt.setY(y + wh / 2);
3664                     break;
3665                 default:
3666                     break;
3667             }
3668         } else {
3669             if (role == RoleType::A)
3670                 pt = nearestPoints.p1();
3671             else
3672                 pt = nearestPoints.p2();
3673         }
3674     }
3675     if (role == RoleType::A) {
3676         m_associationLine->setPoint(0, pt);
3677     }
3678     else {
3679         m_associationLine->setPoint(m_associationLine->count() - 1, pt);
3680     }
3681 }
3682 
3683 /**
3684  * Sets the state of whether the widget is selected.
3685  *
3686  * @param _select   The state of whether the widget is selected.
3687  */
setSelected(bool _select)3688 void AssociationWidget::setSelected(bool _select /* = true */)
3689 {
3690     WidgetBase::setSelected(_select);
3691     if ( m_nameWidget)
3692         m_nameWidget->setSelected( _select );
3693 
3694     m_role[RoleType::A].setSelected(_select);
3695     m_role[RoleType::B].setSelected(_select);
3696 
3697     // Update the docwindow for this association.
3698     // This is done last because each of the above setSelected calls
3699     // overwrites the docwindow, but we want the main association doc
3700     // to win.
3701     if (_select) {
3702         UMLApp::app()->docWindow()->showDocumentation(this, false);
3703     } else
3704         UMLApp::app()->docWindow()->updateDocumentation(true);
3705 
3706     m_associationLine->setSelected(_select);
3707     if (! _select) {
3708         // For now, if _select is true we don't make the assoc class line
3709         // selected. But that's certainly open for discussion.
3710         // At any rate, we need to deselect the assoc class line
3711         // if _select is false.
3712         selectAssocClassLine(false);
3713     }
3714     UMLApp::app()->document()->writeToStatusBar(_select ? i18n("Press Ctrl with left mouse click to delete a point") : QString());
3715 }
3716 
3717 /**
3718  * Reimplement method from WidgetBase in order to check owned floating texts.
3719  *
3720  * @param p Point to be checked.
3721  *
3722  * @return pointer to widget at the provided point
3723  * @return 0 is no widget has been found
3724  */
onWidget(const QPointF & p)3725 UMLWidget* AssociationWidget::onWidget(const QPointF &p)
3726 {
3727     if (m_nameWidget && m_nameWidget->onWidget(p))
3728         return m_nameWidget;
3729 
3730     UMLWidget *w = m_role[RoleType::A].onWidget(p);
3731     if (w)
3732         return w;
3733 
3734     w = m_role[RoleType::B].onWidget(p);
3735     if (w)
3736         return w;
3737 
3738     return nullptr;
3739 }
3740 
3741 /**
3742  * Returns true if the given point is on the connecting line to
3743  * the association class. Returns false if there is no association
3744  * class attached, or if the given point is not on the connecting
3745  * line.
3746  */
onAssocClassLine(const QPointF & point)3747 bool AssociationWidget::onAssocClassLine(const QPointF &point)
3748 {
3749     bool onLine = false;
3750     if (m_pAssocClassLine) {
3751 //:TODO:
3752 //        const QPointF mapped = m_pAssocClassLine->mapFromParent(point);
3753 //        bool onLine = m_pAssocClassLine->contains(mapped);
3754 //        return onLine;
3755         UMLSceneItemList list = m_scene->collisions(point);
3756         UMLSceneItemList::iterator end(list.end());
3757         for (UMLSceneItemList::iterator item_it(list.begin()); item_it != end; ++item_it) {
3758             if (*item_it == m_pAssocClassLine) {
3759                 onLine = true;
3760                 break;
3761             }
3762         }
3763     }
3764     DEBUG(DBG_SRC) << onLine;
3765     return onLine;
3766 }
3767 
3768 /**
3769  * Returns true if the given point is on the association line.
3770  * A circle (rectangle) around the point is used to obtain more tolerance.
3771  * @param point   the point to check
3772  * @return   flag whether point is on association line
3773  */
onAssociation(const QPointF & point)3774 bool AssociationWidget::onAssociation(const QPointF& point)
3775 {
3776     // check the path
3777     const qreal diameter(4.0);
3778     QPainterPath path = m_associationLine->shape();
3779     if (path.contains(point)) {
3780         DEBUG(DBG_SRC) << "on path";
3781         return true;
3782     }
3783     // check also the points
3784     if (m_associationLine->layout() == Uml::LayoutType::Spline) {
3785         if (m_associationLine->closestPointIndex(point, diameter) > -1) {
3786             DEBUG(DBG_SRC) << "on spline point";
3787             return true;
3788         }
3789     }
3790     return onAssocClassLine(point);
3791 }
3792 
3793 /**
3794  * Set all association points to x coordinate.
3795  */
setXEntireAssoc(qreal x)3796 void AssociationWidget::setXEntireAssoc(qreal x)
3797 {
3798     for (int i = 0; i < m_associationLine->count(); ++i) {
3799         QPointF p = m_associationLine->point(i);
3800         p.setX(x);
3801         m_associationLine->setPoint(i, p);
3802     }
3803 }
3804 
3805 /**
3806  * Set all association points to y coordinate.
3807  */
setYEntireAssoc(qreal y)3808 void AssociationWidget::setYEntireAssoc(qreal y)
3809 {
3810     for (int i = 0; i < m_associationLine->count(); ++i) {
3811         QPointF p = m_associationLine->point(i);
3812         p.setY(y);
3813         m_associationLine->setPoint(i, p);
3814     }
3815 }
3816 
3817 /**
3818  * Moves all the mid points (all except start /end) by the given amount.
3819  */
moveMidPointsBy(qreal x,qreal y)3820 void AssociationWidget::moveMidPointsBy(qreal x, qreal y)
3821 {
3822     int pos = m_associationLine->count() - 1;
3823     for (int i = 1; i < (int)pos; ++i) {
3824         QPointF p = m_associationLine->point( i );
3825         qreal newX = p.x() + x;
3826         qreal newY = p.y() + y;
3827         p.setX( newX );
3828         p.setY( newY );
3829         m_associationLine->setPoint( i, p );
3830     }
3831 }
3832 
3833 /**
3834  * Moves the entire association by the given offset.
3835  */
moveEntireAssoc(qreal x,qreal y)3836 void AssociationWidget::moveEntireAssoc(qreal x, qreal y)
3837 {
3838     //TODO: ADD SUPPORT FOR ASSOC. ON SEQ. DIAGRAMS WHEN NOTES BACK IN.
3839     moveMidPointsBy(x, y);
3840     // multi select
3841     if (umlScene()->selectedCount() > 1) {
3842         QPointF d(x, y);
3843         QPointF s = m_associationLine->startPoint() + d;
3844         QPointF e = m_associationLine->endPoint() + d;
3845         m_associationLine->setEndPoints(s, e);
3846     }
3847     calculateEndingPoints();
3848     calculateNameTextSegment();
3849     resetTextPositions();
3850 }
3851 
3852 /**
3853  * Returns the bounding rectangle of all segments of the association.
3854  */
boundingRect() const3855 QRectF AssociationWidget::boundingRect() const
3856 {
3857     return m_associationLine->boundingRect();
3858 }
3859 
3860 /**
3861  * Returns the shape of all segments of the association.
3862  */
shape() const3863 QPainterPath AssociationWidget::shape() const
3864 {
3865     return m_associationLine->shape();
3866 }
3867 
3868 /**
3869  * Connected to UMLClassifier::attributeRemoved() or UMLEntity::constraintRemoved()
3870  * in case this AssociationWidget is linked to a classifier list item
3871  * (an attribute or a foreign key constraint)
3872  *
3873  * @param obj   The UMLClassifierListItem removed.
3874  */
slotClassifierListItemRemoved(UMLClassifierListItem * obj)3875 void AssociationWidget::slotClassifierListItemRemoved(UMLClassifierListItem* obj)
3876 {
3877     if (obj != m_umlObject) {
3878         DEBUG(DBG_SRC) << "obj=" << obj << ": m_umlObject=" << m_umlObject;
3879         return;
3880     }
3881     m_umlObject = 0;
3882     m_scene->removeWidgetCmd(this);
3883 }
3884 
3885 /**
3886  * Connected to UMLObject::modified() in case this
3887  * AssociationWidget is linked to a classifer's attribute type.
3888  */
slotAttributeChanged()3889 void AssociationWidget::slotAttributeChanged()
3890 {
3891     UMLAttribute *attr = attribute();
3892     if (attr == 0) {
3893         uError() << "attribute() returns NULL";
3894         return;
3895     }
3896     setVisibility(attr->visibility(), RoleType::B);
3897     setRoleName(attr->name(), RoleType::B);
3898 }
3899 
clipSize()3900 void AssociationWidget::clipSize()
3901 {
3902     if (m_nameWidget)
3903         m_nameWidget->clipSize();
3904 
3905     m_role[RoleType::A].clipSize();
3906     m_role[RoleType::B].clipSize();
3907 
3908     if (m_associationClass)
3909         m_associationClass->clipSize();
3910 }
3911 
3912 /**
3913  * Event handler for context menu events, called from the line segments.
3914  */
contextMenuEvent(QGraphicsSceneContextMenuEvent * event)3915 void AssociationWidget::contextMenuEvent(QGraphicsSceneContextMenuEvent *event)
3916 {
3917     event->accept();
3918 
3919     UMLScene *scene = umlScene();
3920     QWidget *parent = 0;
3921     if (scene) {
3922         parent = scene->activeView();
3923     }
3924 
3925     if (!isSelected() && scene && !scene->selectedItems().isEmpty()) {
3926         Qt::KeyboardModifiers forSelection = (Qt::ControlModifier | Qt::ShiftModifier);
3927         if ((event->modifiers() & forSelection) == 0) {
3928             scene->clearSelection();
3929         }
3930     }
3931     setSelected(true);
3932     m_eventScenePos = event->scenePos();
3933 
3934     const Uml::AssociationType::Enum type = onAssocClassLine(event->scenePos()) ? Uml::AssociationType::Anchor : associationType();
3935     AssociationWidgetPopupMenu popup(parent, type, this);
3936     QAction *triggered = popup.exec(event->screenPos());
3937     slotMenuSelection(triggered);
3938 }
3939 
3940 /**
3941  * Reimplemented event handler for hover enter events.
3942  */
hoverEnterEvent(QGraphicsSceneHoverEvent * event)3943 void AssociationWidget::hoverEnterEvent(QGraphicsSceneHoverEvent *event)
3944 {
3945     associationLine()->hoverEnterEvent(event);
3946 }
3947 
3948 /**
3949  * Reimplemented event handler for hover leave events.
3950  */
hoverLeaveEvent(QGraphicsSceneHoverEvent * event)3951 void AssociationWidget::hoverLeaveEvent(QGraphicsSceneHoverEvent *event)
3952 {
3953     associationLine()->hoverLeaveEvent(event);
3954 }
3955 
3956 /**
3957  * Reimplemented event handler for hover move events.
3958  */
hoverMoveEvent(QGraphicsSceneHoverEvent * event)3959 void AssociationWidget::hoverMoveEvent(QGraphicsSceneHoverEvent *event)
3960 {
3961     associationLine()->hoverMoveEvent(event);
3962 }
3963 
3964 /**
3965  * Saves this widget to the "assocwidget" XMI element.
3966  */
saveToXMI1(QXmlStreamWriter & writer)3967 void AssociationWidget::saveToXMI1(QXmlStreamWriter& writer)
3968 {
3969     writer.writeStartElement(QLatin1String("assocwidget"));
3970 
3971     WidgetBase::saveToXMI1(writer);
3972     LinkWidget::saveToXMI1(writer);
3973     if (m_umlObject) {
3974         writer.writeAttribute(QLatin1String("xmi.id"), Uml::ID::toString(m_umlObject->id()));
3975     }
3976     writer.writeAttribute(QLatin1String("type"), QString::number(associationType()));
3977     if (!association()) {
3978         writer.writeAttribute(QLatin1String("visibilityA"), QString::number(visibility(RoleType::A)));
3979         writer.writeAttribute(QLatin1String("visibilityB"), QString::number(visibility(RoleType::B)));
3980         writer.writeAttribute(QLatin1String("changeabilityA"), QString::number(changeability(RoleType::A)));
3981         writer.writeAttribute(QLatin1String("changeabilityB"), QString::number(changeability(RoleType::B)));
3982         if (m_umlObject == 0) {
3983             writer.writeAttribute(QLatin1String("roleAdoc"), roleDocumentation(RoleType::A));
3984             writer.writeAttribute(QLatin1String("roleBdoc"), roleDocumentation(RoleType::B));
3985             writer.writeAttribute(QLatin1String("documentation"), documentation());
3986         }
3987     }
3988     writer.writeAttribute(QLatin1String("widgetaid"), Uml::ID::toString(widgetIDForRole(RoleType::A)));
3989     writer.writeAttribute(QLatin1String("widgetbid"), Uml::ID::toString(widgetIDForRole(RoleType::B)));
3990 
3991     if (m_associationClass) {
3992         QString acid = Uml::ID::toString(m_associationClass->id());
3993         writer.writeAttribute(QLatin1String("assocclass"), acid);
3994         writer.writeAttribute(QLatin1String("aclsegindex"), QString::number(m_nLinePathSegmentIndex));
3995     }
3996 
3997     // save attributes of m_role[A]
3998     const AssociationWidgetRole& roleA = m_role[RoleType::A];
3999     writer.writeAttribute(QLatin1String("indexa"), QString::number(roleA.m_nIndex));
4000     writer.writeAttribute(QLatin1String("totalcounta"), QString::number(roleA.m_nTotalCount));
4001     // save attributes of m_role[B]
4002     const AssociationWidgetRole& roleB = m_role[RoleType::B];
4003     writer.writeAttribute(QLatin1String("indexb"), QString::number(roleB.m_nIndex));
4004     writer.writeAttribute(QLatin1String("totalcountb"), QString::number(roleB.m_nTotalCount));
4005 
4006     // Save subelements of m_role[A] and m_role[B].
4007     m_role[RoleType::A].saveToXMI1(writer);
4008     // This is separated from attributes because attributes may not follow
4009     // elements, i.e. all attributes must be written before the first subelement.
4010     m_role[RoleType::B].saveToXMI1(writer);
4011 
4012     if (m_nameWidget) {
4013         m_nameWidget->saveToXMI1(writer);
4014     }
4015 
4016     m_associationLine->saveToXMI1(writer);
4017 
4018     writer.writeEndElement();  // assocwidget
4019 }
4020 
4021 /**
4022  * Uses the supplied widgetList for resolving
4023  * the role A and role B widgets. (The other loadFromXMI1() queries
4024  * the UMLView for these widgets.)
4025  * Required for clipboard operations.
4026  */
loadFromXMI1(QDomElement & qElement,const UMLWidgetList & widgets,const MessageWidgetList * messages)4027 bool AssociationWidget::loadFromXMI1(QDomElement& qElement,
4028                                     const UMLWidgetList& widgets,
4029                                     const MessageWidgetList* messages)
4030 {
4031     if (!WidgetBase::loadFromXMI1(qElement)) {
4032         return false;
4033     }
4034 
4035     if (!LinkWidget::loadFromXMI1(qElement)) {
4036         return false;
4037     }
4038 
4039     // load child widgets first
4040     QString widgetaid = qElement.attribute(QLatin1String("widgetaid"), QLatin1String("-1"));
4041     QString widgetbid = qElement.attribute(QLatin1String("widgetbid"), QLatin1String("-1"));
4042     Uml::ID::Type aId = Uml::ID::fromString(widgetaid);
4043     Uml::ID::Type bId = Uml::ID::fromString(widgetbid);
4044     UMLWidget *pWidgetA = Widget_Utils::findWidget(aId, widgets, messages);
4045     if (!pWidgetA) {
4046         uError() << "cannot find widget for roleA id " << Uml::ID::toString(aId);
4047         return false;
4048     }
4049     UMLWidget *pWidgetB = Widget_Utils::findWidget(bId, widgets, messages);
4050     if (!pWidgetB) {
4051         uError() << "cannot find widget for roleB id " << Uml::ID::toString(bId);
4052         return false;
4053     }
4054     setWidgetForRole(pWidgetA, RoleType::A);
4055     setWidgetForRole(pWidgetB, RoleType::B);
4056 
4057     QString type = qElement.attribute(QLatin1String("type"), QLatin1String("-1"));
4058     Uml::AssociationType::Enum aType = Uml::AssociationType::fromInt(type.toInt());
4059 
4060     bool oldStyleLoad = false;
4061     if (m_nId == Uml::ID::None) {
4062         // xmi.id not present, ergo either a pure widget association,
4063         // or old (pre-1.2) style:
4064         // Everything is loaded from the AssociationWidget.
4065         // UMLAssociation may or may not be saved - if it is, it's a dummy.
4066         // Create the UMLAssociation if both roles are UML objects;
4067         // else load the info locally.
4068 
4069         if (Uml::AssociationType::hasUMLRepresentation(aType)) {
4070             // lack of an association in our widget AND presence of
4071             // both uml objects for each role clearly identifies this
4072             // as reading in an old-school file. Note it as such, and
4073             // create, and add, the UMLAssociation for this widget.
4074             // Remove this special code when backwards compatibility
4075             // with older files isn't important anymore. -b.t.
4076             UMLObject* umlRoleA = pWidgetA->umlObject();
4077             UMLObject* umlRoleB = pWidgetB->umlObject();
4078             if (!m_umlObject && umlRoleA && umlRoleB) {
4079                 oldStyleLoad = true; // flag for further special config below
4080                 if (aType == AssociationType::Aggregation || aType == AssociationType::Composition) {
4081                     uWarning()<<" Old Style save file? swapping roles on association widget"<<this;
4082                     // We have to swap the A and B widgets to compensate
4083                     // for the long standing bug in AssociationLine of drawing
4084                     // the diamond at the wrong end which was fixed
4085                     // just before the 1.2 release.
4086                     // The logic here is that the user has understood
4087                     // that the diamond belongs at the SOURCE end of the
4088                     // the association (i.e. at the container, not at the
4089                     // contained), and has compensated for this anomaly
4090                     // by drawing the aggregations/compositions from
4091                     // target to source.
4092                     UMLWidget *tmpWidget = pWidgetA;
4093                     pWidgetA = pWidgetB;
4094                     pWidgetB = tmpWidget;
4095                     setWidgetForRole(pWidgetA, RoleType::A);
4096                     setWidgetForRole(pWidgetB, RoleType::B);
4097                     umlRoleA = pWidgetA->umlObject();
4098                     umlRoleB = pWidgetB->umlObject();
4099                 }
4100 
4101                 setUMLAssociation(umlDoc()->createUMLAssociation(umlRoleA, umlRoleB, aType));
4102             }
4103         }
4104 
4105         setDocumentation(qElement.attribute(QLatin1String("documentation")));
4106         setRoleDocumentation(qElement.attribute(QLatin1String("roleAdoc")), RoleType::A);
4107         setRoleDocumentation(qElement.attribute(QLatin1String("roleBdoc")), RoleType::B);
4108 
4109         // visibility defaults to Public if it cant set it here..
4110         QString visibilityA = qElement.attribute(QLatin1String("visibilityA"), QLatin1String("0"));
4111         int vis = visibilityA.toInt();
4112         if (vis >= 200) {  // bkwd compat.
4113             vis -= 200;
4114         }
4115         setVisibility((Uml::Visibility::Enum)vis, RoleType::A);
4116 
4117         QString visibilityB = qElement.attribute(QLatin1String("visibilityB"), QLatin1String("0"));
4118         vis = visibilityB.toInt();
4119         if (vis >= 200) { // bkwd compat.
4120             vis -= 200;
4121         }
4122         setVisibility((Uml::Visibility::Enum)vis, RoleType::B);
4123 
4124         // Changeability defaults to "Changeable" if it cant set it here..
4125         QString changeabilityA = qElement.attribute(QLatin1String("changeabilityA"), QLatin1String("0"));
4126         if (changeabilityA.toInt() > 0)
4127             setChangeability(Uml::Changeability::fromInt(changeabilityA.toInt()), RoleType::A);
4128 
4129         QString changeabilityB = qElement.attribute(QLatin1String("changeabilityB"), QLatin1String("0"));
4130         if (changeabilityB.toInt() > 0)
4131             setChangeability(Uml::Changeability::fromInt(changeabilityB.toInt()), RoleType::B);
4132 
4133     } else {
4134 
4135         // we should disconnect any prior association (can this happen??)
4136         if (m_umlObject && m_umlObject->baseType() == UMLObject::ot_Association) {
4137             UMLAssociation *umla = association();
4138             umla->disconnect(this);
4139             umla->nrof_parent_widgets--;
4140         }
4141 
4142         // New style: The xmi.id is a reference to the UMLAssociation.
4143         // If the UMLObject is not found right now, we try again later
4144         // during the type resolution pass - see activate().
4145         UMLObject *myObj = umlDoc()->findObjectById(m_nId);
4146         if (myObj) {
4147             const UMLObject::ObjectType ot = myObj->baseType();
4148             if (ot != UMLObject::ot_Association) {
4149                 setUMLObject(myObj);
4150             } else {
4151                 UMLAssociation * myAssoc = myObj->asUMLAssociation();
4152                 setUMLAssociation(myAssoc);
4153                 if (type == QLatin1String("-1"))
4154                     aType = myAssoc->getAssocType();
4155             }
4156         }
4157     }
4158 
4159     setAssociationType(aType);
4160 
4161     m_role[RoleType::A].loadFromXMI1(qElement, QLatin1String("a"));
4162     m_role[RoleType::B].loadFromXMI1(qElement, QLatin1String("b"));
4163 
4164     QString assocclassid = qElement.attribute(QLatin1String("assocclass"));
4165     if (! assocclassid.isEmpty()) {
4166         Uml::ID::Type acid = Uml::ID::fromString(assocclassid);
4167         UMLWidget *w = Widget_Utils::findWidget(acid, widgets);
4168         if (w) {
4169             ClassifierWidget* aclWidget = static_cast<ClassifierWidget*>(w);
4170             QString aclSegIndex = qElement.attribute(QLatin1String("aclsegindex"), QLatin1String("0"));
4171             createAssocClassLine(aclWidget, aclSegIndex.toInt());
4172         } else {
4173             uError() << "cannot find assocclass " << assocclassid;
4174         }
4175     }
4176 
4177     //now load child elements
4178     QDomNode node = qElement.firstChild();
4179     QDomElement element = node.toElement();
4180     while (!element.isNull()) {
4181         QString tag = element.tagName();
4182         if (tag == QLatin1String("linepath")) {
4183             if (!m_associationLine->loadFromXMI1(element)) {
4184                 return false;
4185             }
4186         } else if (tag == QLatin1String("floatingtext") ||
4187                    tag == QLatin1String("UML:FloatingTextWidget")) {  // for bkwd compatibility
4188             QString r = element.attribute(QLatin1String("role"), QLatin1String("-1"));
4189             if (r == QLatin1String("-1"))
4190                 return false;
4191             Uml::TextRole::Enum role = Uml::TextRole::fromInt(r.toInt());
4192             FloatingTextWidget *ft = new FloatingTextWidget(m_scene, role, QString(), Uml::ID::Reserved);
4193             if (! ft->loadFromXMI1(element)) {
4194                 // Most likely cause: The FloatingTextWidget is empty.
4195                 delete ft;
4196                 node = element.nextSibling();
4197                 element = node.toElement();
4198                 continue;
4199             }
4200             // always need this
4201             ft->setParentItem(this);
4202             ft->setLink(this);
4203             ft->setSequenceNumber(m_SequenceNumber);
4204             ft->setFontCmd(ft->font());
4205 
4206             switch(role) {
4207             case Uml::TextRole::MultiA:
4208                 m_role[RoleType::A].multiplicityWidget = ft;
4209                 if (oldStyleLoad)
4210                     setMultiplicity(m_role[RoleType::A].multiplicityWidget->text(), RoleType::A);
4211                 break;
4212 
4213             case Uml::TextRole::MultiB:
4214                 m_role[RoleType::B].multiplicityWidget = ft;
4215                 if (oldStyleLoad)
4216                     setMultiplicity(m_role[RoleType::B].multiplicityWidget->text(), RoleType::B);
4217                 break;
4218 
4219             case Uml::TextRole::ChangeA:
4220                 m_role[RoleType::A].changeabilityWidget = ft;
4221                 break;
4222 
4223             case Uml::TextRole::ChangeB:
4224                 m_role[RoleType::B].changeabilityWidget = ft;
4225                 break;
4226 
4227             case Uml::TextRole::Name:
4228                 m_nameWidget = ft;
4229                 if (oldStyleLoad)
4230                     setName(m_nameWidget->text());
4231                 break;
4232 
4233             case Uml::TextRole::Coll_Message:
4234             case Uml::TextRole::Coll_Message_Self:
4235                 m_nameWidget = ft;
4236                 ft->setLink(this);
4237                 ft->setActivated();
4238                 if (FloatingTextWidget::isTextValid(ft->text()))
4239                     ft->show();
4240                 else
4241                     ft->hide();
4242                 break;
4243 
4244             case Uml::TextRole::RoleAName:
4245                 m_role[RoleType::A].roleWidget = ft;
4246                 setRoleName(ft->text(), RoleType::A);
4247                 break;
4248             case Uml::TextRole::RoleBName:
4249                 m_role[RoleType::B].roleWidget = ft;
4250                 setRoleName(ft->text(), RoleType::B);
4251                 break;
4252             default:
4253                 DEBUG(DBG_SRC) << "unexpected FloatingTextWidget (TextRole::Enum " << role << ")";
4254                 delete ft;
4255                 break;
4256             }
4257         }
4258         node = element.nextSibling();
4259         element = node.toElement();
4260     }
4261 
4262     return true;
4263 }
4264 
4265 /**
4266  * Queries the UMLView for resolving the role A and role B widgets.
4267  * ....
4268  */
loadFromXMI1(QDomElement & qElement)4269 bool AssociationWidget::loadFromXMI1(QDomElement& qElement)
4270 {
4271     UMLScene *scene = umlScene();
4272     if (scene) {
4273         const UMLWidgetList& widgetList = scene->widgetList();
4274         const MessageWidgetList& messageList = scene->messageList();
4275         return loadFromXMI1(qElement, widgetList, &messageList);
4276     }
4277     else {
4278         DEBUG(DBG_SRC) << "This isn't on UMLScene yet, so can neither fetch"
4279             "messages nor widgets on umlscene";
4280         return false;
4281     }
4282 }
4283