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