umbrello API Documentation

associationwidget.cpp

00001 /***************************************************************************
00002  *                                                                         *
00003  *   This program is free software; you can redistribute it and/or modify  *
00004  *   it under the terms of the GNU General Public License as published by  *
00005  *   the Free Software Foundation; either version 2 of the License, or     *
00006  *   (at your option) any later version.                                   *
00007  *                                                                         *
00008  *   copyright (C) 2002-2007                                               *
00009  *   Umbrello UML Modeller Authors <uml-devel@uml.sf.net>                  *
00010  ***************************************************************************/
00011 
00012 // own header
00013 #include "associationwidget.h"
00014 // system includes
00015 #include <cstdlib>
00016 #include <cmath>
00017 // qt/kde includes
00018 #include <qcanvas.h>
00019 #include <qvalidator.h>
00020 #include <kdebug.h>
00021 #include <klocale.h>
00022 #include <kinputdialog.h>
00023 #include <kcolordialog.h>
00024 #include <kapplication.h>
00025 // app includes
00026 #include "activitywidget.h"
00027 #include "uml.h"
00028 #include "umlview.h"
00029 #include "umldoc.h"
00030 #include "umlwidget.h"
00031 #include "messagewidget.h"
00032 #include "umlrole.h"
00033 #include "listpopupmenu.h"
00034 #include "classifierwidget.h"
00035 #include "classifier.h"
00036 #include "entity.h"
00037 #include "attribute.h"
00038 #include "operation.h"
00039 #include "association.h"
00040 #include "assocrules.h"
00041 #include "floatingtextwidget.h"
00042 #include "objectwidget.h"
00043 #include "model_utils.h"
00044 #include "widget_utils.h"
00045 #include "dialogs/assocpropdlg.h"
00046 #include "optionstate.h"
00047 
00048 using namespace Uml;
00049 
00050 // this constructor really only for loading from XMI, otherwise it
00051 // is bad..and shouldn't be allowed as it creates an incomplete
00052 // associationwidget.
00053 AssociationWidget::AssociationWidget(UMLView *view)
00054         : WidgetBase(view)
00055 {
00056     init(view);
00057 }
00058 
00059 // the preferred constructor
00060 AssociationWidget::AssociationWidget(UMLView *view, UMLWidget* pWidgetA,
00061                                      Association_Type assocType, UMLWidget* pWidgetB,
00062                                      UMLAssociation *umlassoc /* = NULL */)
00063         : WidgetBase(view)
00064 {
00065     init(view);
00066     if (umlassoc)
00067         setUMLAssociation(umlassoc);
00068     else
00069         // set up UMLAssociation obj if assoc is represented and both roles are UML objects
00070         if (UMLAssociation::assocTypeHasUMLRepresentation(assocType)) {
00071             UMLObject* umlRoleA = pWidgetA->getUMLObject();
00072             UMLObject* umlRoleB = pWidgetB->getUMLObject();
00073             if (umlRoleA != NULL && umlRoleB != NULL) {
00074                 bool swap;
00075 
00076                 // THis isnt correct. We could very easily have more than one
00077                 // of the same type of association between the same two objects.
00078                 // Just create the association. This search should have been
00079                 // done BEFORE creation of the widget, if it mattered to the code.
00080                 // But lets leave check in here for the time being so that debugging
00081                 // output is shown, in case there is a collision with code elsewhere.
00082                 UMLAssociation * myAssoc = m_umldoc->findAssociation( assocType, umlRoleA, umlRoleB, &swap );
00083                 if (myAssoc != NULL) {
00084                     if (assocType == at_Generalization) {
00085                         kDebug() << " Ignoring second construction of same generalization"
00086                         << endl;
00087                     } else {
00088                         kDebug() << " constructing a similar or exact same assoc " <<
00089                         "as an already existing assoc (swap=" << swap << ")" << endl;
00090                         // now, just create a new association anyways
00091                         myAssoc = NULL;
00092                     }
00093                 }
00094                 if (myAssoc == NULL)
00095                     myAssoc = new UMLAssociation( assocType, umlRoleA, umlRoleB );
00096                 setUMLAssociation(myAssoc);
00097             }
00098         }
00099 
00100     setWidget(pWidgetA, A);
00101     setWidget(pWidgetB, B);
00102 
00103     setAssocType(assocType);
00104 
00105     calculateEndingPoints();
00106 
00107     //The AssociationWidget is set to Activated because it already has its side widgets
00108     setActivated(true);
00109 
00110     // sync UML meta-data to settings here
00111     mergeAssociationDataIntoUMLRepresentation();
00112 
00113     // Collaboration messages need a name label because it's that
00114     // which lets operator== distinguish them, which in turn
00115     // permits us to have more than one message between two objects.
00116     if (isCollaboration()) {
00117         // Create a temporary name to bring on setName()
00118         int collabID = m_pView->generateCollaborationId();
00119         setName('m' + QString::number(collabID));
00120     }
00121 }
00122 
00123 AssociationWidget::~AssociationWidget() {
00124 }
00125 
00126 AssociationWidget& AssociationWidget::operator=(AssociationWidget & Other) {
00127     m_LinePath = Other.m_LinePath;
00128 
00129     m_pView = Other.m_pView;
00130 
00131     if (Other.m_pName) {
00132         m_pName = new FloatingTextWidget(m_pView);
00133         *m_pName = *(Other.m_pName);
00134     } else {
00135         m_pName = NULL;
00136     }
00137 
00138     for (unsigned r = (unsigned)A; r <= (unsigned)B; r++) {
00139         WidgetRole& lhs = m_role[r];
00140         const WidgetRole& rhs = Other.m_role[r];
00141         lhs.m_nIndex = rhs.m_nIndex;
00142         lhs.m_nTotalCount = rhs.m_nTotalCount;
00143 
00144         if (rhs.m_pMulti) {
00145             lhs.m_pMulti = new FloatingTextWidget(m_pView);
00146             *(lhs.m_pMulti) = *(rhs.m_pMulti);
00147         } else {
00148             lhs.m_pMulti = NULL;
00149         }
00150 
00151         if (rhs.m_pRole) {
00152             lhs.m_pRole = new FloatingTextWidget(m_pView);
00153             *(lhs.m_pRole) = *(rhs.m_pRole);
00154         } else {
00155             lhs.m_pRole = NULL;
00156         }
00157 
00158         if (rhs.m_pChangeWidget) {
00159             lhs.m_pChangeWidget = new FloatingTextWidget(m_pView);
00160             *(lhs.m_pChangeWidget) = *(rhs.m_pChangeWidget);
00161         } else {
00162             lhs.m_pChangeWidget = NULL;
00163         }
00164 
00165         lhs.m_pWidget = rhs.m_pWidget;
00166         lhs.m_OldCorner = rhs.m_OldCorner;
00167         lhs.m_WidgetRegion = rhs.m_WidgetRegion;
00168     }
00169 
00170     m_bActivated = Other.m_bActivated;
00171     m_unNameLineSegment = Other.m_unNameLineSegment;
00172     m_pMenu = Other.m_pMenu;
00173     setUMLAssociation(Other.getAssociation());
00174     m_bSelected = Other.m_bSelected;
00175     m_nMovingPoint = Other.m_nMovingPoint;
00176 
00177     return *this;
00178 }
00179 
00180 bool AssociationWidget::operator==(AssociationWidget & Other) {
00181     if( this == &Other )
00182         return true;
00183 
00184     if( !m_pObject || !Other.m_pObject ) {
00185         if( !Other.m_pObject && m_pObject )
00186             return false;
00187         if( Other.m_pObject && !m_pObject )
00188             return false;
00189     } else if( m_pObject != Other.m_pObject )
00190         return false;
00191 
00192     if (getAssocType() != Other.getAssocType())
00193         return false;
00194 
00195     if (getWidgetID(A) != Other.getWidgetID(A))
00196         return false;
00197 
00198     if (getWidgetID(B) != Other.getWidgetID(B))
00199         return false;
00200 
00201     if (getWidget(A)->getBaseType() == Uml::wt_Object &&
00202             Other.getWidget(A)->getBaseType() == Uml::wt_Object) {
00203         ObjectWidget *ownA = static_cast<ObjectWidget*>(getWidget(A));
00204         ObjectWidget *otherA = static_cast<ObjectWidget*>(Other.getWidget(A));
00205         if (ownA->getLocalID() != otherA->getLocalID())
00206             return false;
00207     }
00208 
00209     if (getWidget(B)->getBaseType() == Uml::wt_Object &&
00210             Other.getWidget(B)->getBaseType() == Uml::wt_Object) {
00211         ObjectWidget *ownB = static_cast<ObjectWidget*>(getWidget(B));
00212         ObjectWidget *otherB = static_cast<ObjectWidget*>(Other.getWidget(B));
00213         if (ownB->getLocalID() != otherB->getLocalID())
00214             return false;
00215     }
00216 
00217     // Two objects in a collaboration can have multiple messages between each other.
00218     // Here we depend on the messages having names, and the names must be different.
00219     // That is the reason why collaboration messages have strange initial names like
00220     // "m29997" or similar.
00221     return (getName() == Other.getName());
00222 }
00223 
00224 bool AssociationWidget::operator!=(AssociationWidget & Other) {
00225     return !(*this == Other);
00226 }
00227 
00228 UMLAssociation * AssociationWidget::getAssociation () const {
00229     if (m_pObject == NULL || m_pObject->getBaseType() != ot_Association)
00230         return NULL;
00231     return static_cast<UMLAssociation*>(m_pObject);
00232 }
00233 
00234 UMLAttribute * AssociationWidget::getAttribute () const {
00235     if (m_pObject == NULL)
00236         return NULL;
00237     Uml::Object_Type ot = m_pObject->getBaseType();
00238     if (ot != ot_Attribute && ot != ot_EntityAttribute)
00239         return NULL;
00240     return static_cast<UMLAttribute*>(m_pObject);
00241 }
00242 
00243 FloatingTextWidget* AssociationWidget::getMultiWidget(Role_Type role) {
00244     return m_role[role].m_pMulti;
00245 }
00246 
00247 QString AssociationWidget::getMulti(Role_Type role) const
00248 {
00249     if (m_role[role].m_pMulti == NULL)
00250         return "";
00251     return m_role[role].m_pMulti->getText();
00252 }
00253 
00254 FloatingTextWidget* AssociationWidget::getNameWidget()
00255 {
00256     return m_pName;
00257 }
00258 
00259 QString AssociationWidget::getName() const {
00260     if (m_pName == NULL)
00261         return "";
00262     return m_pName->getText();
00263 }
00264 
00265 FloatingTextWidget* AssociationWidget::getRoleWidget(Role_Type role) {
00266     return m_role[role].m_pRole;
00267 }
00268 
00269 FloatingTextWidget* AssociationWidget::getChangeWidget(Role_Type role) {
00270     return m_role[role].m_pChangeWidget;
00271 }
00272 
00273 FloatingTextWidget* AssociationWidget::getTextWidgetByRole(Uml::Text_Role tr) {
00274     switch (tr) {
00275         case tr_MultiA:
00276             return m_role[A].m_pMulti;
00277         case tr_MultiB:
00278             return m_role[B].m_pMulti;
00279         case tr_Name:
00280         case tr_Coll_Message:
00281             return m_pName;
00282         case tr_RoleAName:
00283             return m_role[A].m_pRole;
00284         case tr_RoleBName:
00285             return m_role[B].m_pRole;
00286         case tr_ChangeA:
00287             return m_role[A].m_pChangeWidget;
00288         case tr_ChangeB:
00289             return m_role[B].m_pChangeWidget;
00290         default:
00291             break;
00292     }
00293     return NULL;
00294 }
00295 
00296 QString AssociationWidget::getRoleName(Role_Type role) const {
00297     if (m_role[role].m_pRole == NULL)
00298         return "";
00299     return m_role[role].m_pRole->getText();
00300 }
00301 
00302 QString AssociationWidget::getRoleDoc(Role_Type role) const {
00303     if (m_pObject == NULL || m_pObject->getBaseType() != ot_Association)
00304         return "";
00305     UMLAssociation *umla = static_cast<UMLAssociation*>(m_pObject);
00306     return umla->getRoleDoc(role);
00307 }
00308 
00309 void AssociationWidget::setName(const QString &strName) {
00310     // set attribute of UMLAssociation associated with this associationwidget
00311     UMLAssociation *umla = getAssociation();
00312     if (umla)
00313         umla->setName(strName);
00314 
00315     bool newLabel = false;
00316     if(!m_pName) {
00317         // Don't construct the FloatingTextWidget if the string is empty.
00318         if (! FloatingTextWidget::isTextValid(strName))
00319             return;
00320 
00321         newLabel = true;
00322         m_pName = new FloatingTextWidget(m_pView, CalculateNameType(tr_Name), strName);
00323         m_pName->setLink(this);
00324     } else {
00325         m_pName->setText(strName);
00326         if (! FloatingTextWidget::isTextValid(strName)) {
00327             //m_pName->hide();
00328             m_pView->removeWidget(m_pName);
00329             m_pName = NULL;
00330             return;
00331         }
00332     }
00333 
00334     setTextPosition( tr_Name );
00335     if (newLabel) {
00336         m_pName->setActivated();
00337         m_pView->addWidget(m_pName);
00338     }
00339 
00340     m_pName->show();
00341 }
00342 
00343 void AssociationWidget::setFloatingText(Uml::Text_Role tr,
00344                                         const QString &text,
00345                                         FloatingTextWidget* &ft) {
00346     if (! FloatingTextWidget::isTextValid(text)) {
00347         if (ft) {
00348             // Remove preexisting FloatingTextWidget
00349             m_pView->removeWidget(ft);  // physically deletes ft
00350             ft = NULL;
00351         }
00352         return;
00353     }
00354 
00355     if (ft == NULL) {
00356         ft = new FloatingTextWidget(m_pView, tr, text);
00357         ft->setLink(this);
00358         ft->activate();
00359         setTextPosition(tr);
00360         m_pView->addWidget(ft);
00361     } else {
00362         bool newLabel = ft->getText().isEmpty();
00363         ft->setText(text);
00364         if (newLabel)
00365             setTextPosition(tr);
00366     }
00367 
00368     ft->show();
00369 }
00370 
00371 void AssociationWidget::setMulti(const QString &strMulti, Role_Type role) {
00372     Text_Role tr = (role == A ? tr_MultiA : tr_MultiB);
00373 
00374     setFloatingText(tr, strMulti, m_role[role].m_pMulti);
00375 
00376     if (m_pObject && m_pObject->getBaseType() == ot_Association)
00377         getAssociation()->setMulti(strMulti, role);
00378 }
00379 
00380 void AssociationWidget::setRoleName (const QString &strRole, Role_Type role) {
00381     Association_Type type = getAssocType();
00382     //if the association is not supposed to have a Role FloatingTextWidget
00383     if (!AssocRules::allowRole(type))  {
00384         return;
00385     }
00386 
00387     Text_Role tr = (role == A ? tr_RoleAName : tr_RoleBName);
00388     setFloatingText(tr, strRole, m_role[role].m_pRole);
00389     if (m_role[role].m_pRole) {
00390         Uml::Visibility vis = getVisibility(role);
00391         if (FloatingTextWidget::isTextValid(m_role[role].m_pRole->getText())) {
00392             m_role[role].m_pRole->setPreText(vis.toString(true));
00393             //m_role[role].m_pRole->show();
00394         } else {
00395             m_role[role].m_pRole->setPreText("");
00396             //m_role[role].m_pRole->hide();
00397         }
00398     }
00399 
00400     // set attribute of UMLAssociation associated with this associationwidget
00401     if (m_pObject && m_pObject->getBaseType() == ot_Association)
00402         getAssociation()->setRoleName(strRole, role);
00403 }
00404 
00405 void AssociationWidget::setRoleDoc (const QString &doc, Role_Type role) {
00406     if (m_pObject && m_pObject->getBaseType() == ot_Association)
00407         getAssociation()->setRoleDoc(doc, role);
00408     else
00409         m_role[role].m_RoleDoc = doc;
00410 }
00411 
00412 void AssociationWidget::setMessageText(FloatingTextWidget *ft) {
00413     QString message;
00414     if (isCollaboration()) {
00415         if (m_pObject != NULL) {
00416             message = getMulti(A) + ": " + getOperationText(m_pView);
00417         } else {
00418             message = getMulti(A) + ": " + getName();
00419         }
00420     } else {
00421         message = getName();
00422     }
00423     ft->setText(message);
00424 }
00425 
00426 Uml::Visibility AssociationWidget::getVisibility(Role_Type role) const {
00427     const UMLAssociation *assoc = getAssociation();
00428     if (assoc)
00429         return assoc->getVisibility(role);
00430     const UMLAttribute *attr = getAttribute();
00431     if (attr)
00432         return attr->getVisibility();
00433     return m_role[role].m_Visibility;
00434 }
00435 
00436 void AssociationWidget::setVisibility(Uml::Visibility value, Role_Type role)
00437 {
00438     if (value == getVisibility(role))
00439         return;
00440     if (m_pObject) {
00441         // update our model object
00442         const Uml::Object_Type ot = m_pObject->getBaseType();
00443         if (ot == ot_Association)
00444             getAssociation()->setVisibility(value, role);
00445         else if (ot == ot_Attribute)
00446             getAttribute()->setVisibility(value);
00447     }
00448     m_role[role].m_Visibility = value;
00449     // update role pre-text attribute as appropriate
00450     if (m_role[role].m_pRole) {
00451         QString scopeString = value.toString(true);
00452         m_role[role].m_pRole->setPreText(scopeString);
00453     }
00454 }
00455 
00456 Changeability_Type AssociationWidget::getChangeability(Role_Type role) const
00457 {
00458     if (m_pObject == NULL || m_pObject->getBaseType() != ot_Association)
00459         return m_role[role].m_Changeability;
00460     UMLAssociation *umla = static_cast<UMLAssociation*>(m_pObject);
00461     return umla->getChangeability(role);
00462 }
00463 
00464 void AssociationWidget::setChangeability (Changeability_Type value, Role_Type role)
00465 {
00466     if (value == getChangeability(role))
00467         return;
00468     QString changeString = UMLAssociation::ChangeabilityToString(value);
00469     if (m_pObject && m_pObject->getBaseType() == ot_Association)  // update our model object
00470         getAssociation()->setChangeability(value, role);
00471     m_role[role].m_Changeability = value;
00472     // update our string representation
00473     setChangeWidget(changeString, role);
00474 }
00475 
00476 void AssociationWidget::setChangeWidget(const QString &strChangeWidget, Role_Type role) {
00477     bool newLabel = false;
00478     Text_Role tr = (role == A ? tr_ChangeA : tr_ChangeB);
00479 
00480     if(!m_role[role].m_pChangeWidget) {
00481         // Don't construct the FloatingTextWidget if the string is empty.
00482         if (strChangeWidget.isEmpty())
00483             return;
00484 
00485         newLabel = true;
00486         m_role[role].m_pChangeWidget = new FloatingTextWidget(m_pView, tr, strChangeWidget);
00487         m_role[role].m_pChangeWidget->setLink(this);
00488         m_pView->addWidget(m_role[role].m_pChangeWidget);
00489         m_role[role].m_pChangeWidget->setPreText("{"); // all types have this
00490         m_role[role].m_pChangeWidget->setPostText("}"); // all types have this
00491     } else {
00492         if (m_role[role].m_pChangeWidget->getText().isEmpty()) {
00493             newLabel = true;
00494         }
00495         m_role[role].m_pChangeWidget->setText(strChangeWidget);
00496     }
00497     m_role[role].m_pChangeWidget->setActivated();
00498 
00499     if (newLabel) {
00500         setTextPosition( tr );
00501     }
00502 
00503     if(FloatingTextWidget::isTextValid(m_role[role].m_pChangeWidget->getText()))
00504         m_role[role].m_pChangeWidget -> show();
00505     else
00506         m_role[role].m_pChangeWidget -> hide();
00507 }
00508 
00509 bool AssociationWidget::linePathStartsAt(const UMLWidget* widget) {
00510     QPoint lpStart = m_LinePath.getPoint(0);
00511     int startX = lpStart.x();
00512     int startY = lpStart.y();
00513     int wX = widget->getX();
00514     int wY = widget->getY();
00515     int wWidth = widget->getWidth();
00516     int wHeight = widget->getHeight();
00517     bool result = (startX >= wX && startX <= wX + wWidth &&
00518                    startY >= wY && startY <= wY + wHeight);
00519     return result;
00520 }
00521 
00522 void AssociationWidget::setText(FloatingTextWidget *ft, const QString &text) {
00523     Uml::Text_Role role = ft->getRole();
00524     switch (role) {
00525     case tr_Name:
00526         setName(text);
00527         break;
00528     case tr_RoleAName:
00529         setRoleName(text, A);
00530         break;
00531     case tr_RoleBName:
00532         setRoleName(text, B);
00533         break;
00534     case tr_MultiA:
00535         setMulti(text, A);
00536         break;
00537     case tr_MultiB:
00538         setMulti(text, B);
00539         break;
00540     default:
00541         break;
00542     }
00543 }
00544 
00545 bool AssociationWidget::activate() {
00546     if (m_pObject == NULL &&
00547         UMLAssociation::assocTypeHasUMLRepresentation(m_AssocType)) {
00548         UMLObject *myObj = m_umldoc->findObjectById(m_nId);
00549         if (myObj == NULL) {
00550             kError() << "AssociationWidget::activate: cannot find UMLObject "
00551                 << ID2STR(m_nId) << endl;
00552             return false;
00553         } else {
00554             const Uml::Object_Type ot = myObj->getBaseType();
00555             if (ot == ot_Association) {
00556                 UMLAssociation * myAssoc = static_cast<UMLAssociation*>(myObj);
00557                 setUMLAssociation(myAssoc);
00558                 m_LinePath.setAssocType( myAssoc->getAssocType() );
00559             } else {
00560                 setUMLObject(myObj);
00561                 setAssocType(m_AssocType);
00562             }
00563         }
00564     }
00565 
00566     if (m_bActivated)
00567         return true;
00568 
00569     Association_Type type = getAssocType();
00570 
00571     if (m_role[A].m_pWidget == NULL)
00572         setWidget(m_pView->findWidget(getWidgetID(A)), A);
00573     if (m_role[B].m_pWidget == NULL)
00574         setWidget(m_pView->findWidget(getWidgetID(B)), B);
00575 
00576     if(!m_role[A].m_pWidget || !m_role[B].m_pWidget) {
00577         kDebug() << "Can't make association" << endl;
00578         return false;
00579     }
00580 
00581     calculateEndingPoints();
00582     m_LinePath.activate();
00583 
00584     if (AssocRules::allowRole(type)) {
00585         for (unsigned r = A; r <= B; r++) {
00586             WidgetRole& robj = m_role[r];
00587             if (robj.m_pRole == NULL)
00588                 continue;
00589             robj.m_pRole->setLink(this);
00590             Text_Role tr = (r == A ? tr_RoleAName : tr_RoleBName);
00591             robj.m_pRole->setRole(tr);
00592             Uml::Visibility vis = getVisibility((Role_Type)r);
00593             robj.m_pRole->setPreText(vis.toString(true));
00594 
00595             if (FloatingTextWidget::isTextValid(robj.m_pRole->getText()))
00596                 robj.m_pRole -> show();
00597             else
00598                 robj.m_pRole -> hide();
00599             if (m_pView->getType() == dt_Collaboration)
00600                 robj.m_pRole->setUMLObject(robj.m_pWidget->getUMLObject());
00601             robj.m_pRole->activate();
00602         }
00603     }
00604 
00605     if( m_pName != NULL ) {
00606         m_pName->setLink(this);
00607         m_pName->setRole( CalculateNameType(tr_Name) );
00608 
00609         if ( FloatingTextWidget::isTextValid(m_pName->getText()) ) {
00610             m_pName-> show();
00611         } else {
00612             m_pName-> hide();
00613         }
00614         m_pName->activate();
00615         calculateNameTextSegment();
00616     }
00617 
00618     for (unsigned r = A; r <= B; r++) {
00619         WidgetRole& robj = m_role[r];
00620 
00621         FloatingTextWidget* pMulti = robj.m_pMulti;
00622         if (pMulti != NULL &&
00623                 AssocRules::allowMultiplicity(type, robj.m_pWidget->getBaseType())) {
00624             pMulti->setLink(this);
00625             Text_Role tr = (r == A ? tr_MultiA : tr_MultiB);
00626             pMulti->setRole(tr);
00627             if (FloatingTextWidget::isTextValid(pMulti->getText()))
00628                 pMulti -> show();
00629             else
00630                 pMulti -> hide();
00631             pMulti->activate();
00632         }
00633 
00634         FloatingTextWidget* pChangeWidget = robj.m_pChangeWidget;
00635         if (pChangeWidget != NULL ) {
00636             pChangeWidget->setLink(this);
00637             Text_Role tr = (r == A ? tr_ChangeA : tr_ChangeB);
00638             pChangeWidget->setRole(tr);
00639             if (FloatingTextWidget::isTextValid(pChangeWidget->getText()))
00640                 pChangeWidget -> show();
00641             else
00642                 pChangeWidget -> hide ();
00643             pChangeWidget->activate();
00644         }
00645     }
00646 
00647     // Prepare the association class line if needed.
00648     if (m_pAssocClassWidget && !m_pAssocClassLine) {
00649         createAssocClassLine();
00650     }
00651 
00652     m_bActivated = true;
00653     return true;
00654 }
00655 
00657 Uml::Text_Role AssociationWidget::CalculateNameType(Text_Role defaultRole) {
00658 
00659     Text_Role result = defaultRole;
00660     if( m_pView -> getType() == dt_Collaboration ) {
00661         if(m_role[A].m_pWidget == m_role[B].m_pWidget) {
00662             result = tr_Coll_Message;//for now same as other Coll_Message
00663         } else {
00664             result = tr_Coll_Message;
00665         }
00666     } else if( m_pView -> getType() == dt_Sequence ) {
00667         if(m_role[A].m_pWidget == m_role[B].m_pWidget) {
00668             result = tr_Seq_Message_Self;
00669         } else {
00670             result = tr_Seq_Message;
00671         }
00672     }
00673 
00674     return result;
00675 }
00676 
00677 UMLWidget* AssociationWidget::getWidget(Role_Type role) {
00678     return m_role[role].m_pWidget;
00679 }
00680 
00681 bool AssociationWidget::setWidgets( UMLWidget* widgetA,
00682                                     Association_Type assocType,
00683                                     UMLWidget* widgetB) {
00684     //if the association already has a WidgetB or WidgetA associated, then
00685     //it cannot be changed to other widget, that would require a  deletion
00686     //of the association and the creation of a new one
00687     if ((m_role[A].m_pWidget && (m_role[A].m_pWidget != widgetA)) ||
00688             (m_role[B].m_pWidget && (m_role[B].m_pWidget != widgetB))) {
00689         return false;
00690     }
00691     setWidget(widgetA, A);
00692     setAssocType(assocType);
00693     setWidget(widgetB, B);
00694 
00695     calculateEndingPoints();
00696     return true;
00697 }
00698 
00701 bool AssociationWidget::checkAssoc(UMLWidget * widgetA, UMLWidget *widgetB) {
00702     return (widgetA == m_role[A].m_pWidget && widgetB == m_role[B].m_pWidget);
00703 }
00704 
00706 void AssociationWidget::cleanup() {
00707 
00708     //let any other associations know we are going so they can tidy their positions up
00709     if(m_role[A].m_nTotalCount > 2)
00710         updateAssociations(m_role[A].m_nTotalCount - 1, m_role[A].m_WidgetRegion, A);
00711     if(m_role[B].m_nTotalCount > 2)
00712         updateAssociations(m_role[B].m_nTotalCount - 1, m_role[B].m_WidgetRegion, B);
00713 
00714     for (unsigned r = A; r <= B; r++) {
00715         WidgetRole& robj = m_role[r];
00716 
00717         if(robj.m_pWidget) {
00718             robj.m_pWidget->removeAssoc(this);
00719             robj.m_pWidget = 0;
00720         }
00721         if(robj.m_pRole) {
00722             m_pView->removeWidget(robj.m_pRole);
00723             robj.m_pRole = 0;
00724         }
00725         if(robj.m_pMulti) {
00726             m_pView->removeWidget(robj.m_pMulti);
00727             robj.m_pMulti = 0;
00728         }
00729         if(robj.m_pChangeWidget) {
00730             m_pView->removeWidget(robj.m_pChangeWidget);
00731             robj.m_pChangeWidget = 0;
00732         }
00733     }
00734 
00735     if(m_pName) {
00736         m_pView->removeWidget(m_pName);
00737         m_pName = 0;
00738     }
00739 
00740     if (m_pObject && m_pObject->getBaseType() == ot_Association) {
00741         /*
00742            We do not remove the UMLAssociation from the document.
00743            Why? - Well, for example we might be in the middle of
00744            a cut/paste. If the UMLAssociation is removed by the cut
00745            then upon pasteing we have a problem.
00746            This is not quite clean yet - there should be a way to
00747            explicitly delete a UMLAssociation.  The Right Thing would
00748            be to have a ListView representation for UMLAssociation.
00749         `
00750                 IF we are cut n pasting, why are we handling this association as a pointer?
00751                 We should be using the XMI representation for a cut and paste. This
00752                 allows us to be clean here, AND a choice of recreating the object
00753                 w/ same id IF its a "cut", or a new object if its a "copy" operation
00754                 (in which case we wouldnt be here, in cleanup()).
00755          */
00756         setUMLAssociation(0);
00757     }
00758 
00759     m_LinePath.cleanup();
00760     removeAssocClassLine();
00761 }
00762 
00763 void AssociationWidget::setUMLAssociation (UMLAssociation * assoc)
00764 {
00765     if (m_pObject && m_pObject->getBaseType() == ot_Association) {
00766         UMLAssociation *umla = getAssociation();
00767 
00768         // safety check. Did some num-nuts try to set the existing
00769         // association again? If so, just bail here
00770         if (assoc && umla == assoc)
00771             return;
00772 
00773         //umla->disconnect(this);  //Qt does disconnect automatically upon destruction.
00774         umla->nrof_parent_widgets--;
00775 
00776         // we are the last "owner" of this association, so delete it
00777         // from the parent UMLDoc, and as a stand-alone
00778         //DISCUSS: Should we really do this?
00779         //    It implies that an association's existence is ONLY
00780         //    governed by its existence on at least one diagram.
00781         //    OTOH, it might be argued that an association should
00782         //    further exist even when it's (temporarily) not present
00783         //    on any diagram. This is exactly what cut and paste
00784         //    relies on (at least the way it's implemented now)
00785         // ANSWER: yes, we *should* do this.
00786         //   This only implies that IF an association once 'belonged'
00787         //   to one or more parent associationwidgets, then it must 'die' when the
00788         //   last widget does. UMLAssociations which never had a parent
00789         //   in the first place wont be affected by this code, and can happily
00790         //   live on without a parent.
00791         //DISCUSS: Sorry Brian, but this breaks cut/paste.
00792         //    In particular, cut/paste means that the UMLAssociation _does_
00793         //    have the assocwidget parent - the only means of doing a cut/paste
00794         //    on the diagram is via the widgets. I.e. in practice there is no
00795         //    such thing as an "orphan" UMLAssociation.
00796         //    BTW, IMHO the concept of a widget being the parent of a UML object
00797         //    is fundamentally flawed. Widgets are pure presentation - they can
00798         //    come and go at a whim. If at all, the widgets could be considered
00799         //    children of the corresponding UML object.
00800         //
00801         // ANSWER: This is the wrong treatment of cut and paste. Associations that
00802         // are being cut/n pasted should be serialized to XMI, then reconstituted
00803         // (IF a paste operation) rather than passing around object pointers. Its
00804         // just too hard otherwise to prevent problems in the code. Bottom line: we need to
00805         // delete orphaned associations or we well get code crashes and memory leaks.
00806         if (umla->nrof_parent_widgets == 0) {
00807             //umla->deleteLater();
00808         }
00809 
00810         m_pObject = NULL;
00811     }
00812 
00813     if(assoc) {
00814         m_pObject = assoc;
00815 
00816         // move counter to "0" from "-1" (which means, no assocwidgets)
00817         if(assoc->nrof_parent_widgets < 0)
00818             assoc->nrof_parent_widgets = 0;
00819 
00820         assoc->nrof_parent_widgets++;
00821         connect(assoc, SIGNAL(modified()), this, SLOT(syncToModel()));
00822     }
00823 
00824 }
00825 
00826 
00828 bool AssociationWidget::contains(UMLWidget* widget) {
00829     return (widget == m_role[A].m_pWidget || widget == m_role[B].m_pWidget);
00830 }
00831 
00832 bool AssociationWidget::isCollaboration() {
00833     Uml::Association_Type at = getAssocType();
00834     return (at == at_Coll_Message || at == at_Coll_Message_Self);
00835 }
00836 
00837 Association_Type AssociationWidget::getAssocType() const {
00838     if (m_pObject == NULL || m_pObject->getBaseType() != ot_Association)
00839         return m_AssocType;
00840     UMLAssociation *umla = static_cast<UMLAssociation*>(m_pObject);
00841     return umla->getAssocType();
00842 }
00843 
00845 void AssociationWidget::setAssocType(Association_Type type) {
00846     if (m_pObject && m_pObject->getBaseType() == ot_Association)
00847         getAssociation()->setAssocType(type);
00848     m_AssocType = type;
00849     m_LinePath.setAssocType(type);
00850     // If the association new type is not supposed to have Multiplicity
00851     // FloatingTexts and a Role FloatingTextWidget then set the texts
00852     // to empty.
00853     // NB We do not physically delete the floatingtext widgets here because
00854     // those widgets are also stored in the UMLView::m_WidgetList.
00855     if( !AssocRules::allowMultiplicity(type, getWidget(A)->getBaseType()) ) {
00856         if (m_role[A].m_pMulti) {
00857             m_role[A].m_pMulti->setName("");
00858         }
00859         if (m_role[B].m_pMulti) {
00860             m_role[B].m_pMulti->setName("");
00861         }
00862     }
00863     if( !AssocRules::allowRole( type ) ) {
00864         if (m_role[A].m_pRole) {
00865             m_role[A].m_pRole->setName("");
00866         }
00867         if (m_role[B].m_pRole) {
00868             m_role[B].m_pRole->setName("");
00869         }
00870         setRoleDoc("", A);
00871         setRoleDoc("", B);
00872     }
00873 }
00874 
00875 Uml::IDType AssociationWidget::getWidgetID(Role_Type role) const {
00876     if (m_role[role].m_pWidget == NULL) {
00877         if (m_pObject && m_pObject->getBaseType() == ot_Association) {
00878             UMLAssociation *umla = static_cast<UMLAssociation*>(m_pObject);
00879             return umla->getObjectId(role);
00880         }
00881         kError() << "AssociationWidget::getWidgetID(): m_pWidget is NULL" << endl;
00882         return Uml::id_None;
00883     }
00884     if (m_role[role].m_pWidget->getBaseType() == Uml::wt_Object)
00885         return static_cast<ObjectWidget*>(m_role[role].m_pWidget)->getLocalID();
00886     Uml::IDType id = m_role[role].m_pWidget->getID();
00887     return id;
00888 }
00889 
00891 QString AssociationWidget::toString() {
00892     QString string = "";
00893 
00894     if(m_role[A].m_pWidget) {
00895         string = m_role[A].m_pWidget -> getName();
00896     }
00897     string.append(":");
00898 
00899     if(m_role[A].m_pRole) {
00900         string += m_role[A].m_pRole -> getText();
00901     }
00902     string.append(":");
00903     string.append( UMLAssociation::typeAsString(getAssocType()) );
00904     string.append(":");
00905     if(m_role[B].m_pWidget) {
00906         string += m_role[B].m_pWidget -> getName();
00907     }
00908 
00909     string.append(":");
00910     if(m_role[B].m_pRole) {
00911         string += m_role[B].m_pRole -> getText();
00912     }
00913 
00914     return string;
00915 }
00916 
00917 void AssociationWidget::mouseDoubleClickEvent(QMouseEvent * me) {
00918     if (me->button() != Qt::RightButton && me->button() != Qt::LeftButton)
00919         return;
00920     int i = m_LinePath.onLinePath(me->pos());
00921     if (i == -1) {
00922         m_LinePath.setSelected(false);
00923         return;
00924     }
00925     if (me->button() != Qt::LeftButton)
00926         return;
00927     const QPoint mp(me->pos());
00928     /* if there is no point around the mouse pointer, we insert a new one */
00929     if (! m_LinePath.isPoint(i, mp, POINT_DELTA)) {
00930         m_LinePath.insertPoint(i + 1, mp);
00931         if (m_nLinePathSegmentIndex == i) {
00932             QPoint segStart = m_LinePath.getPoint(i);
00933             QPoint segEnd = m_LinePath.getPoint(i + 2);
00934             const int midSegX = segStart.x() + (segEnd.x() - segStart.x()) / 2;
00935             const int midSegY = segStart.y() + (segEnd.y() - segStart.y()) / 2;
00936             /*
00937             kDebug() << "AssociationWidget::mouseDoubleClickEvent: "
00938                   << "segStart=(" << segStart.x() << "," << segStart.y()
00939                   << "), segEnd=(" << segEnd.x() << "," << segEnd.y()
00940                   << "), midSeg=(" << midSegX << "," << midSegY
00941                   << "), mp=(" << mp.x() << "," << mp.y() << ")"
00942                   << endl;
00943              */
00944             if (midSegX > mp.x() || midSegY < mp.y()) {
00945                 m_nLinePathSegmentIndex++;
00946                 kDebug() << "AssociationWidget::mouseDoubleClickEvent: "
00947                 << "setting m_nLinePathSegmentIndex to "
00948                 << m_nLinePathSegmentIndex << endl;
00949                 computeAssocClassLine();
00950             }
00951         }
00952     } else {
00953         /* deselect the line path */
00954         m_LinePath.setSelected( false );
00955 
00956         /* there was a point so we remove the point */
00957         if (m_LinePath.removePoint(i, mp, POINT_DELTA)) {
00958             /* Maybe reattach association class connecting line
00959                to different association linepath segment.  */
00960             const int numberOfLines = m_LinePath.count() - 1;
00961             if (m_nLinePathSegmentIndex >= numberOfLines) {
00962                 m_nLinePathSegmentIndex = numberOfLines - 1;
00963                 computeAssocClassLine();
00964             }
00965         }
00966 
00967         /* select the line path */
00968         m_LinePath.setSelected( true );
00969     }
00970 
00971     m_LinePath.update();
00972 
00973     calculateNameTextSegment();
00974     m_umldoc->setModified(true);
00975 }
00976 
00977 void AssociationWidget::moveEvent(QMoveEvent* me) {
00978     // 2004-04-30: Achim Spangler
00979     // Simple Approach to block moveEvent during load of
00980     // XMI
00982     if ( m_umldoc->loading() ) {
00983         // hmmh - change of position during load of XMI
00984         // -> there is something wrong
00985         // -> avoid movement during opening
00986         // -> print warn and stay at old position
00987         kWarning() << "AssociationWidget::moveEvent() called during load of XMI for ViewType: " << m_pView -> getType()
00988         << ", and BaseType: " << getBaseType()
00989         << endl;
00990         return;
00991     }
00992     /*to be here a line segment has moved.
00993       we need to see if the three text widgets needs to be moved.
00994       there are a few things to check first though:
00995 
00996       1) Do they exist
00997       2) does it need to move:
00998       2a) for the multi widgets only move if they changed region, otherwise they are close enough
00999       2b) for role name move if the segment it is on moves.
01000     */
01001     //first see if either the first or last segments moved, else no need to recalculate their point positions
01002 
01003     QPoint oldNamePoint = calculateTextPosition(tr_Name);
01004     QPoint oldMultiAPoint = calculateTextPosition(tr_MultiA);
01005     QPoint oldMultiBPoint = calculateTextPosition(tr_MultiB);
01006     QPoint oldChangeAPoint = calculateTextPosition(tr_ChangeA);
01007     QPoint oldChangeBPoint = calculateTextPosition(tr_ChangeB);
01008     QPoint oldRoleAPoint = calculateTextPosition(tr_RoleAName);
01009     QPoint oldRoleBPoint = calculateTextPosition(tr_RoleBName);
01010 
01011     m_LinePath.setPoint( m_nMovingPoint, me->pos() );
01012     int pos = m_LinePath.count() - 1;//set to last point for widget b
01013 
01014     if ( m_nMovingPoint == 1 || (m_nMovingPoint == pos-1) ) {
01015         calculateEndingPoints();
01016     }
01017     if (m_role[A].m_pChangeWidget && (m_nMovingPoint == 1)) {
01018         setTextPositionRelatively(tr_ChangeA, oldChangeAPoint);
01019     }
01020     if (m_role[B].m_pChangeWidget && (m_nMovingPoint == 1)) {
01021         setTextPositionRelatively(tr_ChangeB, oldChangeBPoint);
01022     }
01023     if (m_role[A].m_pMulti && (m_nMovingPoint == 1)) {
01024         setTextPositionRelatively(tr_MultiA, oldMultiAPoint);
01025     }
01026     if (m_role[B].m_pMulti && (m_nMovingPoint == pos-1)) {
01027         setTextPositionRelatively(tr_MultiB, oldMultiBPoint);
01028     }
01029 
01030     if (m_pName) {
01031         if(m_nMovingPoint == (int)m_unNameLineSegment ||
01032                 m_nMovingPoint - 1 == (int)m_unNameLineSegment) {
01033             setTextPositionRelatively(tr_Name, oldNamePoint);
01034         }
01035     }
01036 
01037     if (m_role[A].m_pRole) {
01038         setTextPositionRelatively(tr_RoleAName, oldRoleAPoint);
01039     }
01040     if (m_role[B].m_pRole) {
01041         setTextPositionRelatively(tr_RoleBName, oldRoleBPoint);
01042     }
01043 }
01044 
01045 
01050 void AssociationWidget::calculateEndingPoints() {
01051     /*
01052      * For each UMLWidget the diagram is divided in four regions by its diagonals
01053      * as indicated below
01054      *                              Region 2
01055      *                         \                /
01056      *                           \            /
01057      *                             +--------+
01058      *                             | \    / |
01059      *                Region 1     |   ><   |    Region 3
01060      *                             | /    \ |
01061      *                             +--------+
01062      *                           /            \
01063      *                         /                \
01064      *                              Region 4
01065      *
01066      * Each diagonal is defined by two corners of the bounding rectangle
01067      *
01068      * To calculate the first point in the LinePath we have to find out in which
01069      * Region (defined by WidgetA's diagonals) is WidgetB's center
01070      * (let's call it Region M.) After that the first point will be the middle
01071      * point of the rectangle's side contained in Region M.
01072      *
01073      * To calculate the last point in the LinePath we repeat the above but
01074      * in the opposite direction (from widgetB to WidgetA)
01075      */
01076 
01077     UMLWidget *pWidgetA = m_role[A].m_pWidget;
01078     UMLWidget *pWidgetB = m_role[B].m_pWidget;
01079     if (!pWidgetA || !pWidgetB)
01080         return;
01081     m_role[A].m_OldCorner.setX( pWidgetA->getX() );
01082     m_role[A].m_OldCorner.setY( pWidgetA->getY() );
01083     m_role[B].m_OldCorner.setX( pWidgetB->getX() );
01084     m_role[B].m_OldCorner.setY( pWidgetB->getY() );
01085     uint size = m_LinePath.count();
01086     if(size < 2)
01087         m_LinePath.setStartEndPoints( m_role[A].m_OldCorner, m_role[B].m_OldCorner );
01088 
01089     // See if an association to self.
01090     // See if it needs to be set up before we continue:
01091     // If self association/message and doesn't have the minimum 4 points
01092     // then create it.  Make sure no points are out of bounds of viewing area.
01093     // This only happens on first time through that we are worried about.
01094     if (pWidgetA == pWidgetB && size < 4) {
01095         const int DISTANCE = 50;
01096         int x = pWidgetA -> getX();
01097         int y = pWidgetA -> getY();
01098         int h = pWidgetA -> getHeight();
01099         int w = pWidgetA -> getWidth();
01100         //see if above widget ok to start
01101         if( y - DISTANCE > 0 ) {
01102             m_LinePath.setStartEndPoints( QPoint( x + w / 4, y ) , QPoint( x + w * 3 / 4, y ) );
01103             m_LinePath.insertPoint( 1, QPoint( x + w / 4, y - DISTANCE ) );
01104             m_LinePath.insertPoint( 2 ,QPoint( x + w * 3 / 4, y - DISTANCE ) );
01105             m_role[A].m_WidgetRegion = m_role[B].m_WidgetRegion = North;
01106         } else {
01107             m_LinePath.setStartEndPoints( QPoint( x + w / 4, y + h ), QPoint( x + w * 3 / 4, y + h ) );
01108             m_LinePath.insertPoint( 1, QPoint( x + w / 4, y + h + DISTANCE ) );
01109             m_LinePath.insertPoint( 2, QPoint( x + w * 3 / 4, y + h + DISTANCE ) );
01110             m_role[A].m_WidgetRegion = m_role[B].m_WidgetRegion = South;
01111         }
01112         return;
01113     }//end a == b
01114 
01115     // If the line has more than one segment change the values to calculate
01116     // from widget to point 1.
01117     int xB = pWidgetB->getX() + pWidgetB->getWidth() / 2;
01118     int yB = pWidgetB->getY() + pWidgetB->getHeight() / 2;
01119     if( size > 2 ) {
01120         QPoint p = m_LinePath.getPoint( 1 );
01121         xB = p.x();
01122         yB = p.y();
01123     }
01124     doUpdates(xB, yB, A);
01125 
01126     // Now do the same for widgetB.
01127     // If the line has more than one segment change the values to calculate
01128     // from widgetB to the last point away from it.
01129     int xA = pWidgetA->getX() + pWidgetA->getWidth() / 2;
01130     int yA = pWidgetA->getY() + pWidgetA->getHeight() / 2;
01131     if (size > 2 ) {
01132         QPoint p = m_LinePath.getPoint( size - 2 );
01133         xA = p.x();
01134         yA = p.y();
01135     }
01136     doUpdates( xA, yA, B );
01137 
01138     computeAssocClassLine();
01139 }
01140 
01141 void AssociationWidget::doUpdates(int otherX, int otherY, Role_Type role) {
01142     // Find widget region.
01143     Region oldRegion = m_role[role].m_WidgetRegion;
01144     UMLWidget *pWidget = m_role[role].m_pWidget;
01145     QRect rc(pWidget->getX(), pWidget->getY(),
01146              pWidget->getWidth(), pWidget->getHeight());
01147     Region& region = m_role[role].m_WidgetRegion;  // alias for brevity
01148     region = findPointRegion( rc, otherX, otherY);
01149     // Move some regions to the standard ones.
01150     switch( region ) {
01151     case NorthWest:
01152         region = North;
01153         break;
01154     case NorthEast:
01155         region = East;
01156         break;
01157     case SouthEast:
01158         region = South;
01159         break;
01160     case SouthWest:
01161     case Center:
01162         region = West;
01163         break;
01164     default:
01165         break;
01166     }
01167     int regionCount = getRegionCount(region, role) + 2;//+2 = (1 for this one and one to halve it)
01168     int totalCount = m_role[role].m_nTotalCount;
01169     if( oldRegion != region ) {
01170         updateRegionLineCount( regionCount - 1, regionCount, region, role );
01171         updateAssociations( totalCount - 1, oldRegion, role );
01172     } else if( totalCount != regionCount ) {
01173         updateRegionLineCount( regionCount - 1, regionCount, region, role );
01174     } else {
01175         updateRegionLineCount( m_role[role].m_nIndex, totalCount, region, role );
01176     }
01177     updateAssociations( regionCount, region, role );
01178 }
01179 
01181 bool AssociationWidget::isActivated() {
01182     return m_bActivated;
01183 }
01184 
01186 void AssociationWidget::setActivated(bool active /*=true*/) {
01187     m_bActivated = active;
01188 }
01189 
01190 void AssociationWidget::syncToModel()
01191 {
01192     UMLAssociation *uml = getAssociation();
01193 
01194     if (uml == NULL) {
01195         UMLAttribute *attr = getAttribute();
01196         if (attr == NULL)
01197             return;
01198         setVisibility(attr->getVisibility(), B);
01199         setRoleName(attr->getName(), B);
01200         return;
01201     }
01202     // block signals until finished
01203     uml->blockSignals(true);
01204 
01205     setName(uml->getName());
01206     setRoleName(uml->getRoleName(A), A);
01207     setRoleName(uml->getRoleName(B), B);
01208     setVisibility(uml->getVisibility(A), A);
01209     setVisibility(uml->getVisibility(B), B);
01210     setChangeability(uml->getChangeability(A), A);
01211     setChangeability(uml->getChangeability(B), B);
01212     setMulti(uml->getMulti(A), A);
01213     setMulti(uml->getMulti(B), B);
01214 
01215     uml->blockSignals(false);
01216 }
01217 
01218 // this will synchronize UMLAssociation w/ this new Widget
01219 void AssociationWidget::mergeAssociationDataIntoUMLRepresentation()
01220 {
01221     UMLAssociation *umlassoc = getAssociation();
01222     UMLAttribute *umlattr = getAttribute();
01223     if (umlassoc == NULL && umlattr == NULL)
01224         return;
01225 
01226     // block emit modified signal, or we get a horrible loop
01227     m_pObject->blockSignals(true);
01228 
01229     // would be desirable to do the following
01230     // so that we can be sure its back to initial state
01231     // in case we missed something here.
01232     //uml->init();
01233 
01234     // floating text widgets
01235     FloatingTextWidget *text = getNameWidget();
01236     if (text)
01237         m_pObject->setName(text->getText());
01238 
01239     text = getRoleWidget(A);
01240     if (text && umlassoc)
01241         umlassoc->setRoleName(text->getText(), A);
01242 
01243     text = getRoleWidget(B);
01244     if (text) {
01245         if (umlassoc)
01246             umlassoc->setRoleName(text->getText(), B);
01247         else if (umlattr)
01248             umlattr->setName(text->getText());
01249     }
01250 
01251     text = getMultiWidget(A);
01252     if (text && umlassoc)
01253         umlassoc->setMulti(text->getText(), A);
01254 
01255     text = getMultiWidget(B);
01256     if (text && umlassoc)
01257         umlassoc->setMulti(text->getText(), B);
01258 
01259     // unblock
01260     m_pObject->blockSignals(false);
01261 }
01262 
01263 void AssociationWidget::saveIdealTextPositions() {
01264     m_oldNamePoint = calculateTextPosition(tr_Name);
01265     m_oldMultiAPoint = calculateTextPosition(tr_MultiA);
01266     m_oldMultiBPoint = calculateTextPosition(tr_MultiB);
01267     m_oldChangeAPoint = calculateTextPosition(tr_ChangeA);
01268     m_oldChangeBPoint = calculateTextPosition(tr_ChangeB);
01269     m_oldRoleAPoint = calculateTextPosition(tr_RoleAName);
01270     m_oldRoleBPoint = calculateTextPosition(tr_RoleBName);
01271 }
01272 
01274 void AssociationWidget::widgetMoved(UMLWidget* widget, int x, int y ) {
01275     // 2004-04-30: Achim Spangler
01276     // Simple Approach to block moveEvent during load of
01277     // XMI
01279     if ( m_umldoc->loading() ) {
01280         // hmmh - change of position during load of XMI
01281         // -> there is something wrong
01282         // -> avoid movement during opening
01283         // -> print warn and stay at old position
01284         kDebug() << "AssociationWidget::widgetMoved() called during load of XMI for ViewType: " << m_pView -> getType()
01285         << ", and BaseType: " << getBaseType()
01286         << endl;
01287         return;
01288     }
01289 
01290     int dx = m_role[A].m_OldCorner.x() - x;
01291     int dy = m_role[A].m_OldCorner.y() - y;
01292     uint size = m_LinePath.count();
01293     uint pos = size - 1;
01294     calculateEndingPoints();
01295 
01296     // Assoc to self - move all points:
01297     if( m_role[A].m_pWidget == m_role[B].m_pWidget ) {
01298         for( int i=1 ; i < (int)pos ; i++ ) {
01299             QPoint p = m_LinePath.getPoint( i );
01300             int newX = p.x() - dx;
01301             int newY = p.y() - dy;
01302             // safety. We DON'T want to go off the screen
01303             if(newX < 0)
01304                 newX = 0;
01305             // safety. We DON'T want to go off the screen
01306             if(newY < 0)
01307                 newY = 0;
01308             newX = m_pView -> snappedX( newX );
01309             newY = m_pView -> snappedY( newY );
01310             p.setX( newX );
01311             p.setY( newY );
01312             m_LinePath.setPoint( i, p );
01313         }
01314 
01315         if ( m_pName && !m_pName->getSelected() ) {
01316             setTextPositionRelatively(tr_Name, m_oldNamePoint);
01317         }
01318 
01319     }//end if widgetA = widgetB
01320     else if (m_role[A].m_pWidget==widget) {
01321         if (m_pName && m_unNameLineSegment == 0 && !m_pName->getSelected() ) {
01322             //only calculate position and move text if the segment it is on is moving
01323             setTextPositionRelatively(tr_Name, m_oldNamePoint);
01324         }
01325     }//end if widgetA moved
01326     else if (m_role[B].m_pWidget==widget) {
01327         if (m_pName && (m_unNameLineSegment == pos-1) && !m_pName->getSelected() ) {
01328             //only calculate position and move text if the segment it is on is moving
01329             setTextPositionRelatively(tr_Name, m_oldNamePoint);
01330         }
01331     }//end if widgetB moved
01332 
01333     if ( m_role[A].m_pRole && !m_role[A].m_pRole->getSelected() ) {
01334         setTextPositionRelatively(tr_RoleAName, m_oldRoleAPoint);
01335     }
01336     if ( m_role[B].m_pRole && !m_role[B].m_pRole->getSelected() ) {
01337         setTextPositionRelatively(tr_RoleBName, m_oldRoleBPoint);
01338     }
01339     if ( m_role[A].m_pMulti && !m_role[A].m_pMulti->getSelected() ) {
01340         setTextPositionRelatively(tr_MultiA, m_oldMultiAPoint);
01341     }
01342     if ( m_role[B].m_pMulti && !m_role[B].m_pMulti->getSelected() ) {
01343         setTextPositionRelatively(tr_MultiB, m_oldMultiBPoint);
01344     }
01345     if ( m_role[A].m_pChangeWidget && !m_role[A].m_pChangeWidget->getSelected() ) {
01346         setTextPositionRelatively(tr_ChangeA, m_oldChangeAPoint);
01347     }
01348     if ( m_role[B].m_pChangeWidget && !m_role[B].m_pChangeWidget->getSelected() ) {
01349         setTextPositionRelatively(tr_ChangeB, m_oldChangeBPoint);
01350     }
01351 }//end method widgetMoved
01352 
01365 AssociationWidget::Region AssociationWidget::findPointRegion(QRect Rect, int PosX, int PosY) {
01366     float w = (float)Rect.width();
01367     float h = (float)Rect.height();
01368     float x = (float)Rect.x();
01369     float y = (float)Rect.y();
01370     float Slope2 = w / h;
01371     float Slope1 = Slope2*(float)(-1);
01372     float b1 = x + w - ( Slope1* y );
01373     float b2 = x - ( Slope2* y );
01374 
01375     float eval1 = Slope1 * (float)PosY + b1;
01376     float eval2 = Slope2  *(float)PosY + b2;
01377 
01378     Region result = Error;
01379     //if inside region 1
01380     if(eval1 > PosX && eval2 > PosX) {
01381         result = West;
01382     }
01383     //if inside region 2
01384     else if (eval1 > PosX && eval2 < PosX) {
01385         result = North;
01386     }
01387     //if inside region 3
01388     else if (eval1 < PosX && eval2 < PosX) {
01389         result = East;
01390     }
01391     //if inside region 4
01392     else if (eval1 < PosX && eval2 > PosX) {
01393         result = South;
01394     }
01395     //if inside region 5
01396     else if (eval1 == PosX && eval2 < PosX) {
01397         result = NorthWest;
01398     }
01399     //if inside region 6
01400     else if (eval1 < PosX && eval2 == PosX) {
01401         result = NorthEast;
01402     }
01403     //if inside region 7
01404     else if (eval1 == PosX && eval2 > PosX) {
01405         result = SouthEast;
01406     }
01407     //if inside region 8
01408     else if (eval1 > PosX && eval2 == PosX) {
01409         result = SouthWest;
01410     }
01411     //if inside region 9
01412     else if (eval1 == PosX && eval2 == PosX) {
01413         result = Center;
01414     }
01415     return result;
01416 }
01417 
01418 QPoint AssociationWidget::swapXY(const QPoint &p) {
01419     QPoint swapped( p.y(), p.x() );
01420     return swapped;
01421 }
01422 
01423 /* Returns the total length of the association's LinePath:
01424    result = segment_1_length + segment_2_length + ..... + segment_n_length
01425  */
01426 float AssociationWidget::totalLength() {
01427     uint size = m_LinePath.count();
01428     float total_length = 0;
01429 
01430     for(uint i = 0; i < size - 1; i++) {
01431         QPoint pi = m_LinePath.getPoint( i );
01432         QPoint pj = m_LinePath.getPoint( i+1 );
01433         int xi = pi.y();
01434         int xj = pj.y();
01435         int yi = pi.x();
01436         int yj = pj.x();
01437         total_length +=  sqrt( double(((xj - xi)*(xj - xi)) + ((yj - yi)*(yj - yi))) );
01438     }
01439 
01440     return total_length;
01441 }
01442 
01443 
01448 QPoint AssociationWidget::calculatePointAtDistance(const QPoint &P1, const QPoint &P2, float Distance) {
01449     /*
01450       the distance D between points (x1, y1) and (x3, y3) has the following formula:
01451           ---     ------------------------------
01452       D =    \   /         2         2
01453               \ /   (x3 - x1)  +  (y3 - y1)
01454 
01455       D, x1 and y1 are known and the point (x3, y3) is inside line (x1,y1)(x2,y2), so if the
01456       that line has the formula y = mx + b
01457       then y3 = m*x3 + b
01458 
01459        2             2             2
01460       D   = (x3 - x1)  +  (y3 - y1)
01461 
01462        2       2                 2      2                 2
01463       D    = x3    - 2*x3*x1 + x1   + y3   - 2*y3*y1  + y1
01464 
01465        2       2       2       2                  2
01466       D    - x1    - y1    = x3    - 2*x3*x1  + y3   - 2*y3*y1
01467 
01468        2       2       2       2                          2
01469       D    - x1    - y1    = x3    - 2*x3*x1  + (m*x3 + b)  - 2*(m*x3 + b)*y1
01470 
01471        2       2       2              2       2 2
01472       D    - x1    - y1   + 2*b*y1 - b   =  (m  + 1)*x3   + (-2*x1 + 2*m*b -2*m*y1)*x3
01473 
01474        2      2       2       2
01475       C  = - D    + x1    + y1   - 2*b*y1 + b
01476 
01477 
01478        2
01479       A  = (m    + 1)
01480 
01481       B  = (-2*x1 + 2*m*b -2*m*y1)
01482 
01483       and we have
01484        2
01485       A * x3 + B * x3 - C = 0
01486 
01487                          ---------------
01488              -B +  ---  /  2
01489                       \/  B   - 4*A*C
01490       sol_1  = --------------------------------
01491                        2*A
01492 
01493 
01494                          ---------------
01495              -B -  ---  /  2
01496                       \/  B   - 4*A*C
01497       sol_2  = --------------------------------
01498                        2*A
01499 
01500 
01501       then in the distance formula we have only one variable x3 and that is easy
01502       to calculate
01503     */
01504     int x1 = P1.y();
01505     int y1 = P1.x();
01506     int x2 = P2.y();
01507     int y2 = P2.x();
01508 
01509     if(x2 == x1) {
01510         return QPoint(x1, y1 + (int)Distance);
01511     }
01512     float slope = ((float)y2 - (float)y1) / ((float)x2 - (float)x1);
01513     float b = (y1 - slope*x1);
01514     float A = (slope * slope) + 1;
01515     float B = (2*slope*b) - (2*x1)  - (2*slope*y1);
01516     float C = (b*b) - (Distance*Distance) + (x1*x1) + (y1*y1) - (2*b*y1);
01517     float t = B*B - 4*A*C;
01518 
01519     if(t < 0) {
01520         return QPoint(-1, -1);
01521     }
01522     float sol_1 = ((-1* B) + sqrt(t) ) / (2*A);
01523     float sol_2 = ((-1*B) - sqrt(t) ) / (2*A);
01524 
01525     if(sol_1 < 0.0 && sol_2 < 0.0) {
01526         return QPoint(-1, -1);
01527     }
01528     QPoint sol1Point((int)(slope*sol_1 + b), (int)(sol_1));
01529     QPoint sol2Point((int)(slope*sol_2 + b), (int)(sol_2));
01530     if(sol_1 < 0 && sol_2 >=0) {
01531         if(x2 > x1) {
01532             if(x1 <= sol_2 && sol_2 <= x2)
01533                 return sol2Point;
01534         } else {
01535             if(x2 <= sol_2 && sol_2 <= x1)
01536                 return sol2Point;
01537         }
01538     } else if(sol_1 >= 0 && sol_2 < 0) {
01539         if(x2 > x1) {
01540             if(x1 <= sol_1 && sol_1 <= x2)
01541                 return sol1Point;
01542         } else {
01543             if(x2 <= sol_1 && sol_1 <= x1)
01544                 return sol1Point;
01545         }
01546     } else {
01547         if(x2 > x1) {
01548             if(x1 <= sol_1 && sol_1 <= x2)
01549                 return sol1Point;
01550             if(x1 <= sol_2 && sol_2 <= x2)
01551                 return sol2Point;
01552         } else {
01553             if(x2 <= sol_1 && sol_1 <= x1)
01554                 return sol1Point;
01555             if(x2 <= sol_2 && sol_2 <= x1)
01556                 return sol2Point;
01557         }
01558     }
01559     return QPoint(-1, -1);
01560 }
01561 
01566 QPoint AssociationWidget::calculatePointAtDistanceOnPerpendicular(const QPoint &P1, const QPoint &P2, float Distance) {
01567     /*
01568       the distance D between points (x2, y2) and (x3, y3) has the following formula:
01569 
01570           ---     ------------------------------
01571       D =    \   /         2             2
01572               \ / (x3 - x2)  +  (y3 - y2)
01573 
01574       D, x2 and y2 are known and line P2P3 is perpendicular to line (x1,y1)(x2,y2), so if the
01575       line P1P2 has the formula y = m*x + b,
01576       then      (x1 - x2)
01577           m =  -----------    , because it is perpendicular to line P1P2
01578                 (y2 - y1)
01579 
01580       also y2 = m*x2 + b
01581       => b = y2 - m*x2
01582 
01583       then P3 = (x3, m*x3 + b)
01584 
01585        2            2            2
01586       D  = (x3 - x2)  + (y3 - y2)
01587 
01588        2     2               2     2               2
01589       D  = x3  - 2*x3*x2 + x2  + y3  - 2*y3*y2 + y2
01590 
01591        2     2     2     2               2
01592       D  - x2  - y2  = x3  - 2*x3*x2 + y3  - 2*y3*y2
01593 
01594 
01595 
01596        2     2     2     2                       2
01597       D  - x2  - y2  = x3  - 2*x3*x2 + (m*x3 + b)  - 2*(m*x3 + b)*y2
01598 
01599        2     2     2                   2        2       2
01600       D  - x2  - y2  + 2*b*y2 - b  = (m  + 1)*x3  + (-2*x2 + 2*m*b -2*m*y2)*x3
01601 
01602               2       2       2              2
01603       C  = - D    + x2    + y2   - 2*b*y2 + b
01604 
01605              2
01606       A  = (m  + 1)
01607 
01608       B  = (-2*x2 + 2*m*b -2*m*y2)
01609 
01610       and we have
01611        2
01612       A * x3 + B * x3 - C = 0
01613 
01614 
01615                            ---------------
01616                      ---  /  2
01617                 -B +    \/  B   - 4*A*C
01618       sol_1 = --------------------------------
01619                         2*A
01620 
01621 
01622                            ---------------
01623                      ---  /  2
01624                 -B -    \/  B   - 4*A*C
01625       sol_2 = --------------------------------
01626                         2*A
01627 
01628       then in the distance formula we have only one variable x3 and that is easy
01629       to calculate
01630     */
01631     if (P1.x() == P2.x()) {
01632         return QPoint((int)(P2.x() + Distance), P2.y());
01633     }
01634     const int x1 = P1.y();
01635     const int y1 = P1.x();
01636     const int x2 = P2.y();
01637     const int y2 = P2.x();
01638 
01639     float slope = ((float)x1 - (float)x2) / ((float)y2 - (float)y1);
01640     float b = (y2 - slope*x2);
01641     float A = (slope * slope) + 1;
01642     float B = (2*slope*b) - (2*x2) - (2*slope*y2);
01643     float C = (b*b) - (Distance*Distance) + (x2*x2) + (y2*y2) - (2*b*y2);
01644     float t = B*B - 4*A*C;
01645     if (t < 0) {
01646         return QPoint(-1, -1);
01647     }
01648     float sol_1 = ((-1* B) + sqrt(t) ) / (2*A);
01649 
01650     float sol_2 = ((-1*B) - sqrt(t) ) / (2*A);
01651 
01652     if(sol_1 < 0 && sol_2 < 0) {
01653         return QPoint(-1, -1);
01654     }
01655     QPoint sol1Point((int)(slope*sol_1 + b), (int)sol_1);
01656     QPoint sol2Point((int)(slope*sol_2 + b), (int)sol_2);
01657     if(sol_1 < 0 && sol_2 >=0) {
01658         return sol2Point;
01659     } else if(sol_1 >= 0 && sol_2 < 0) {
01660         return sol1Point;
01661     } else {    // Choose one solution , either will work fine
01662         if(slope >= 0) {
01663             if(sol_1 <= sol_2)
01664                 return sol2Point;
01665             else
01666                 return sol1Point;
01667         } else {
01668             if(sol_1 <= sol_2)
01669                 return sol1Point;
01670             else
01671                 return sol2Point;
01672         }
01673 
01674     }
01675     return QPoint(-1, -1);  // never reached, just keep compilers happy
01676 }
01677 
01681 float AssociationWidget::perpendicularProjection(QPoint P1, QPoint P2, QPoint P3,
01682         QPoint& ResultingPoint) {
01683     //line P1P2 is Line 1 = y=slope1*x + b1
01684 
01685     //line P3PS is Line 1 = y=slope2*x + b2
01686 
01687     float slope2 = 0;
01688     float slope1 = 0;
01689     float sx = 0, sy = 0;
01690     int y2 = P2.x();
01691     int y1 = P1.x();
01692     int x2 = P2.y();
01693     int x1 = P1.y();
01694     int y3 = P3.x();
01695     int x3 = P3.y();
01696     float distance = 0;
01697     float b1 = 0;
01698 
01699     float b2 = 0;
01700 
01701     if(x2 == x1) {
01702         sx = x2;
01703         sy = y3;
01704     } else if(y2 == y1) {
01705         sy = y2;
01706         sx = x3;
01707     } else {
01708         slope1 = (y2 - y1)/ (x2 - x1);
01709         slope2 = (x1 - x2)/ (y2 - y1);
01710         b1 = y2 - (slope1 * x2);
01711         b2 = y3 - (slope2 * x3);
01712         sx = (b2 - b1) / (slope1 - slope2);
01713         sy = slope1*sx + b1;
01714     }
01715     distance = (int)( sqrt( ((x3 - sx)*(x3 - sx)) + ((y3 - sy)*(y3 - sy)) ) );
01716 
01717     ResultingPoint.setX( (int)sy );
01718     ResultingPoint.setY( (int)sx );
01719 
01720     return distance;
01721 }
01722 
01723 QPoint AssociationWidget::calculateTextPosition(Text_Role role) {
01724     const int SPACE = 2;
01725     QPoint p(-1, -1), q(-1, -1);
01726 
01727     // used to find out if association end point (p)
01728     // is at top or bottom edge of widget.
01729     bool is_top_or_bottom(false);
01730     UMLWidget *pWidget(0);
01731 
01732     if (role == tr_MultiA || role == tr_ChangeA || role == tr_RoleAName) {
01733         p = m_LinePath.getPoint( 0 );
01734         q = m_LinePath.getPoint( 1 );
01735         pWidget = m_role[A].m_pWidget;
01736     } else if (role == tr_MultiB || role == tr_ChangeB || role == tr_RoleBName) {
01737         const uint lastSegment = m_LinePath.count() - 1;
01738         p = m_LinePath.getPoint(lastSegment);
01739         q = m_LinePath.getPoint(lastSegment - 1);
01740         pWidget = m_role[B].m_pWidget;
01741     } else if (role != tr_Name) {
01742         kError() << "AssociationWidget::calculateTextPosition called with unsupported Text_Role "
01743                   << role << endl;
01744         return QPoint(-1, -1);
01745     }
01746 
01747     if ( pWidget && ( pWidget->getY() == p.y() || pWidget->getY() + pWidget->height() == p.y() ))
01748         is_top_or_bottom = true;
01749 
01750     FloatingTextWidget *text = getTextWidgetByRole(role);
01751     int textW = 0, textH = 0;
01752     if (text) {
01753         textW = text->width();
01754         textH = text->height();
01755     }
01756 
01757     int x = 0, y = 0;
01758 
01759     if (role == tr_MultiA || role == tr_MultiB) {
01760         const bool isHorizontal = (p.y() == q.y());
01761         const int atBottom = p.y() + SPACE;
01762         const int atTop = p.y() - SPACE - textH;
01763         const int atLeft = p.x() - SPACE - textW;
01764         const int atRight = p.x() + SPACE;
01765         y = (p.y() > q.y()) == isHorizontal ? atBottom : atTop;
01766         x = (p.x() < q.x()) == isHorizontal ? atRight : atLeft;
01767 
01768     } else if (role == tr_ChangeA || role == tr_ChangeB) {
01769 
01770         if( p.y() > q.y() )
01771             y = p.y() - SPACE - (textH * 2);
01772         else
01773             y = p.y() + SPACE + textH;
01774 
01775         if( p.x() < q.x() )
01776             x = p.x() + SPACE;
01777         else
01778             x = p.x() - SPACE - textW;
01779 
01780     } else if (role == tr_RoleAName || role == tr_RoleBName) {
01781 
01782         if( p.y() > q.y() )
01783             y = p.y() - SPACE - textH;
01784         else
01785             y = p.y() + SPACE;
01786 
01787         if( p.x() < q.x() )
01788             x = p.x() + SPACE;
01789         else
01790             x = p.x() - SPACE - textW;
01791 
01792     } else if (role == tr_Name) {
01793 
01794         calculateNameTextSegment();
01795         x = (int)( ( m_LinePath.getPoint(m_unNameLineSegment).x() +
01796                      m_LinePath.getPoint(m_unNameLineSegment + 1).x() ) / 2 );
01797 
01798         y = (int)( ( m_LinePath.getPoint(m_unNameLineSegment).y() +
01799                      m_LinePath.getPoint(m_unNameLineSegment + 1).y() ) / 2 );
01800     }
01801 
01802     if (text) {
01803         constrainTextPos(x, y, textW, textH, role);
01804     }
01805     p = QPoint( x, y );
01806     return p;
01807 }
01808 
01809 QPoint AssociationWidget::midPoint(QPoint p0, QPoint p1) {
01810     QPoint midP;
01811     if (p0.x() < p1.x())
01812         midP.setX(p0.x() + (p1.x() - p0.x()) / 2);
01813     else
01814         midP.setX(p1.x() + (p0.x() - p1.x()) / 2);
01815     if (p0.y() < p1.y())
01816         midP.setY(p0.y() + (p1.y() - p0.y()) / 2);
01817     else
01818         midP.setY(p1.y() + (p0.y() - p1.y()) / 2);
01819     return midP;
01820 }
01821 
01822 void AssociationWidget::constrainTextPos(int &textX, int &textY,
01823                                          int textWidth, int textHeight,
01824                                          Uml::Text_Role tr) {
01825     const int textCenterX = textX + textWidth / 2;
01826     const int textCenterY = textY + textHeight / 2;
01827     const uint lastSegment = m_LinePath.count() - 1;
01828     QPoint p0, p1;
01829     switch (tr) {
01830         case tr_RoleAName:
01831         case tr_MultiA:
01832         case tr_ChangeA:
01833             p0 = m_LinePath.getPoint(0);
01834             p1 = m_LinePath.getPoint(1);
01835             // If we are dealing with a single line then tie the
01836             // role label to the proper half of the line, i.e.
01837             // the role label must be closer to the "other"
01838             // role object.
01839             if (lastSegment == 1)
01840                 p1 = midPoint(p0, p1);
01841             break;
01842         case tr_RoleBName:
01843         case tr_MultiB:
01844         case tr_ChangeB:
01845             p0 = m_LinePath.getPoint(lastSegment - 1);
01846             p1 = m_LinePath.getPoint(lastSegment);
01847             if (lastSegment == 1)
01848                 p0 = midPoint(p0, p1);
01849             break;
01850         case tr_Name:
01851         case tr_Coll_Message:  // CHECK: collab.msg texts seem to be tr_Name
01852         case tr_State:         // CHECK: is this used?
01853             // Find the linepath segment to which the (textX,textY) is closest
01854             // and constrain to the corridor of that segment (see farther below)
01855             {
01856                 int minDistSquare = 100000;  // utopian initial value
01857                 int lpIndex = 0;
01858                 for (uint i = 0; i < lastSegment; i++) {
01859                     p0 = m_LinePath.getPoint(i);
01860                     p1 = m_LinePath.getPoint(i + 1);
01861                     QPoint midP = midPoint(p0, p1);
01862                     const int deltaX = textCenterX - midP.x();
01863                     const int deltaY = textCenterY - midP.y();
01864                     const int cSquare = deltaX * deltaX + deltaY * deltaY;
01865                     if (cSquare < minDistSquare) {
01866                         minDistSquare = cSquare;
01867                         lpIndex = i;
01868                     }
01869                 }
01870                 p0 = m_LinePath.getPoint(lpIndex);
01871                 p1 = m_LinePath.getPoint(lpIndex + 1);
01872             }
01873             break;
01874         default:
01875             kError() << "AssociationWidget::constrainTextPos(): unexpected Text_Role "
01876                       << tr << endl;
01877             return;
01878             break;
01879     }
01880     /* Constraint:
01881        The midpoint between p0 and p1 is taken to be the center of a circle
01882        with radius D/2 where D is the distance between p0 and p1.
01883        The text center needs to be within this circle else it is constrained
01884        to the nearest point on the circle.
01885      */
01886     p0 = swapXY(p0);    // go to the natural coordinate system
01887     p1 = swapXY(p1);    // with (0,0) in the lower left corner
01888     QPoint midP = midPoint(p0, p1);
01889     // If (textX,textY) is not inside the circle around midP then
01890     // constrain (textX,textY) to the nearest point on that circle.
01891     const int x0 = p0.x();
01892     const int y0 = p0.y();
01893     const int x1 = p1.x();
01894     const int y1 = p1.y();
01895     double r = sqrt((x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0)) / 2;
01896     if (textWidth > r)
01897         r = textWidth;
01898     // swap textCenter{X,Y} to convert from Qt coord.system.
01899     const QPoint origTextCenter(textCenterY, textCenterX);
01900     const int relX = abs(origTextCenter.x() - midP.x());
01901     const int relY = abs(origTextCenter.y() - midP.y());
01902     const double negativeWhenInsideCircle = relX * relX + relY * relY - r * r;
01903     if (negativeWhenInsideCircle <= 0.0) {
01904         return;
01905     }
01906     /*
01907      The original constraint was to snap the text position to the
01908      midpoint but that creates unpleasant visual jitter:
01909     textX = midP.y() - textWidth / 2;   // go back to Qt coord.sys.
01910     textY = midP.x() - textHeight / 2;  // go back to Qt coord.sys.
01911 
01912      Rather, we project the text position onto the closest point
01913      on the circle:
01914 
01915      Circle equation:
01916        relX^2 + relY^2 - r^2 = 0   , or in other words
01917        relY^2 = r^2 - relX^2       , or
01918        relY = sqrt(r^2 - relX^2)
01919      Line equation:
01920        relY = a * relX + b
01921          We can omit "b" because relX and relY are already relative to
01922          the circle origin, therefore we can also write:
01923        a = relY / relX
01924      To obtain the point of intersection between the circle of radius r
01925      and the line connecting the circle origin with the point (relX, relY),
01926      we equate the relY:
01927        a * x = sqrt(r^2 - x^2)     , or in other words
01928        a^2 * x^2 = r^2 - x^2       , or
01929        x^2 * (a^2 + 1) = r^2       , or
01930        x^2 = r^2 / (a^2 + 1)       , or
01931        x = sqrt(r^2 / (a^2 + 1))
01932      and then
01933        y = a * x
01934      The resulting x and y are relative to the circle origin so we just add
01935      the circle origin (X,Y) to obtain the constrained (textX,textY).
01936      */
01937     // Handle the special case, relX = 0.
01938     if (relX == 0) {
01939         if (origTextCenter.y() > midP.y())
01940             textX = midP.y() + (int)r;   // go back to Qt coord.sys.
01941         else
01942             textX = midP.y() - (int)r;   // go back to Qt coord.sys.
01943         textX -= textWidth / 2;
01944         return;
01945     }
01946     const double a = (double)relY / (double)relX;
01947     const double x = sqrt(r*r / (a*a + 1));
01948     const double y = a * x;
01949     if (origTextCenter.x() > midP.x())
01950         textY = midP.x() + (int)x;   // go back to Qt coord.sys.
01951     else
01952         textY = midP.x() - (int)x;   // go back to Qt coord.sys.
01953     textY -= textHeight / 2;
01954     if (origTextCenter.y() > midP.y())
01955         textX = midP.y() + (int)y;   // go back to Qt coord.sys.
01956     else
01957         textX = midP.y() - (int)y;   // go back to Qt coord.sys.
01958     textX -= textWidth / 2;
01959 }
01960 
01961 void AssociationWidget::calculateNameTextSegment() {
01962     if(!m_pName) {
01963         return;
01964     }
01965     //changed to use the middle of the text
01966     //i think this will give a better result.
01967     //never know what sort of lines people come up with
01968     //and text could be long to give a false reading
01969     int xt = m_pName -> getX();
01970     int yt = m_pName -> getY();
01971     xt += m_pName -> getWidth() / 2;
01972     yt += m_pName -> getHeight() / 2;
01973     uint size = m_LinePath.count();
01974     //sum of length(PTP1) and length(PTP2)
01975     float total_length = 0;
01976     float smallest_length = 0;
01977     for(uint i = 0; i < size - 1; i++) {
01978         QPoint pi = m_LinePath.getPoint( i );
01979         QPoint pj = m_LinePath.getPoint( i+1 );
01980         int xtiDiff = xt - pi.x();
01981         int xtjDiff = xt - pj.x();
01982         int ytiDiff = yt - pi.y();
01983         int ytjDiff = yt - pj.y();
01984         total_length =  sqrt( double(xtiDiff * xtiDiff + ytiDiff * ytiDiff) )
01985                         + sqrt( double(xtjDiff * xtjDiff + ytjDiff * ytjDiff) );
01986         //this gives the closest point
01987         if( total_length < smallest_length || i == 0) {
01988             smallest_length = total_length;
01989             m_unNameLineSegment = i;
01990         }
01991     }
01992 }
01993 
01994 void AssociationWidget::setTextPosition(Text_Role role) {
01995     bool startMove = false;
01996     if( m_role[A].m_pMulti && m_role[A].m_pMulti->getStartMove() )
01997         startMove = true;
01998     else if( m_role[B].m_pMulti && m_role[B].m_pMulti->getStartMove() )
01999         startMove = true;
02000     else if( m_role[A].m_pChangeWidget && m_role[A].m_pChangeWidget->getStartMove() )
02001         startMove = true;
02002     else if( m_role[B].m_pChangeWidget && m_role[B].m_pChangeWidget->getStartMove() )
02003         startMove = true;
02004     else if( m_role[A].m_pRole  && m_role[A].m_pRole->getStartMove() )
02005         startMove = true;
02006     else if( m_role[B].m_pRole  && m_role[B].m_pRole->getStartMove() )
02007         startMove = true;
02008     else if( m_pName && m_pName->getStartMove() )
02009         startMove = true;
02010 
02011     if (startMove) {
02012         return;
02013     }
02014     FloatingTextWidget *ft = getTextWidgetByRole(role);
02015     if (ft == NULL)
02016         return;
02017     QPoint pos = calculateTextPosition(role);
02018     int x = pos.x();
02019     int y = pos.y();
02020     if ( (x < 0 || x > FloatingTextWidget::restrictPositionMax) ||
02021             (y < 0 || y > FloatingTextWidget::restrictPositionMax) ) {
02022         kDebug() << "AssociationWidget::setTextPosition( " << x << " , " << y << " ) "
02023         << "- was blocked because at least one value is out of bounds: ["
02024         << "0 ... " << FloatingTextWidget::restrictPositionMax << "]"
02025         << endl;
02026         return;
02027     }
02028     ft->setX( x );
02029     ft->setY( y );
02030 }
02031 
02032 void AssociationWidget::setTextPositionRelatively(Text_Role role, const QPoint &oldPosition) {
02033     bool startMove = false;
02034     if( m_role[A].m_pMulti && m_role[A].m_pMulti->getStartMove() )
02035         startMove = true;
02036     else if( m_role[B].m_pMulti && m_role[B].m_pMulti->getStartMove() )
02037         startMove = true;
02038     else if( m_role[A].m_pChangeWidget && m_role[A].m_pChangeWidget->getStartMove() )
02039         startMove = true;
02040     else if( m_role[B].m_pChangeWidget && m_role[B].m_pChangeWidget->getStartMove() )
02041         startMove = true;
02042     else if( m_role[A].m_pRole  && m_role[A].m_pRole->getStartMove() )
02043         startMove = true;
02044     else if( m_role[B].m_pRole  && m_role[B].m_pRole->getStartMove() )
02045         startMove = true;
02046     else if( m_pName && m_pName->getStartMove() )
02047         startMove = true;
02048 
02049     if (startMove) {
02050         return;
02051     }
02052     FloatingTextWidget *ft = getTextWidgetByRole(role);
02053     if (ft == NULL)
02054         return;
02055     int ftX = ft->getX();
02056     int ftY = ft->getY();
02057     if ( (ftX < 0 || ftX > FloatingTextWidget::restrictPositionMax) ||
02058             (ftY < 0 || ftY > FloatingTextWidget::restrictPositionMax) ) {
02059         kDebug() << "AssociationWidget::setTextPositionRelatively: "
02060         << "blocked because the FloatingTextWidget original position ("
02061         << ftX << "," << ftY << " is out of bounds: [0 ... "
02062         << FloatingTextWidget::restrictPositionMax << "]" << endl;
02063         return;
02064     }
02065     QPoint pos = calculateTextPosition(role);
02066     int relX = pos.x() - oldPosition.x();
02067     int relY = pos.y() - oldPosition.y();
02068     int ftNewX = ftX + relX;
02069     int ftNewY = ftY + relY;
02070     if ( (ftNewX < 0 || ftNewX > FloatingTextWidget::restrictPositionMax) ||
02071             (ftNewY < 0 || ftNewY > FloatingTextWidget::restrictPositionMax) ) {
02072         kDebug() << "AssociationWidget::setTextPositionRelatively: "
02073         << "blocked because the FloatingTextWidget new position ("
02074         << ftNewX << "," << ftNewY << " is out of bounds: [0 ... "
02075         << FloatingTextWidget::restrictPositionMax << "]" << endl;
02076         return;
02077     }
02078     bool oldIgnoreSnapToGrid = ft->getIgnoreSnapToGrid();
02079     ft->setIgnoreSnapToGrid( true );
02080     ft->setX( ftNewX );
02081     ft->setY( ftNewY );
02082     ft->setIgnoreSnapToGrid( oldIgnoreSnapToGrid );
02083 }
02084 
02085 void AssociationWidget::removeAssocClassLine() {
02086     selectAssocClassLine(false);
02087     if (m_pAssocClassLine) {
02088         delete m_pAssocClassLine;
02089         m_pAssocClassLine = NULL;
02090     }
02091     if (m_pAssocClassWidget) {
02092         m_pAssocClassWidget->setClassAssocWidget(NULL);
02093         m_pAssocClassWidget = NULL;
02094     }
02095 }
02096 
02097 void AssociationWidget::createAssocClassLine() {
02098     if (m_pAssocClassLine == NULL)
02099         m_pAssocClassLine = new QCanvasLine(m_pView->canvas());
02100     computeAssocClassLine();
02101     QPen pen(getLineColor(), getLineWidth(), Qt::DashLine);
02102     m_pAssocClassLine->setPen(pen);
02103     m_pAssocClassLine->setVisible(true);
02104 }
02105 
02106 void AssociationWidget::createAssocClassLine(ClassifierWidget* classifier,
02107                                              int linePathSegmentIndex) {
02108     m_nLinePathSegmentIndex = linePathSegmentIndex;
02109 
02110     if (m_nLinePathSegmentIndex < 0) {
02111         return;
02112     }
02113 
02114     m_pAssocClassWidget = classifier;
02115     m_pAssocClassWidget->setClassAssocWidget(this);
02116 
02117     createAssocClassLine();
02118 }
02119 
02120 void AssociationWidget::computeAssocClassLine() {
02121     if (m_pAssocClassWidget == NULL || m_pAssocClassLine == NULL)
02122         return;
02123     if (m_nLinePathSegmentIndex < 0) {
02124         kError() << "AssociationWidget::computeAssocClassLine: "
02125         << "m_nLinePathSegmentIndex is not set" << endl;
02126         return;
02127     }
02128     QPoint segStart = m_LinePath.getPoint(m_nLinePathSegmentIndex);
02129     QPoint segEnd = m_LinePath.getPoint(m_nLinePathSegmentIndex + 1);
02130     const int midSegX = segStart.x() + (segEnd.x() - segStart.x()) / 2;
02131     const int midSegY = segStart.y() + (segEnd.y() - segStart.y()) / 2;
02132 
02133     QPoint segmentMidPoint(midSegX, midSegY);
02134     QRect classRectangle = m_pAssocClassWidget->rect();
02135     QPoint cwEdgePoint = findIntercept(classRectangle, segmentMidPoint);
02136     int acwMinX = cwEdgePoint.x();
02137     int acwMinY = cwEdgePoint.y();
02138 
02139     m_pAssocClassLine->setPoints(midSegX, midSegY, acwMinX, acwMinY);
02140 }
02141 
02142 void AssociationWidget::selectAssocClassLine(bool sel /* =true */) {
02143     if (!sel) {
02144         if (m_pAssocClassLineSel0) {
02145             delete m_pAssocClassLineSel0;
02146             m_pAssocClassLineSel0 = NULL;
02147         }
02148         if (m_pAssocClassLineSel1) {
02149             delete m_pAssocClassLineSel1;
02150             m_pAssocClassLineSel1 = NULL;
02151         }
02152         return;
02153     }
02154     if (m_pAssocClassLine == NULL) {
02155         kError() << "AssociationWidget::selectAssocClassLine: "
02156         << "cannot select because m_pAssocClassLine is NULL"
02157         << endl;
02158         return;
02159     }
02160     if (m_pAssocClassLineSel0)
02161         delete m_pAssocClassLineSel0;
02162     m_pAssocClassLineSel0 = Widget_Utils::decoratePoint(m_pAssocClassLine->startPoint());
02163     if (m_pAssocClassLineSel1)
02164         delete m_pAssocClassLineSel1;
02165     m_pAssocClassLineSel1 = Widget_Utils::decoratePoint(m_pAssocClassLine->endPoint());
02166 }
02167 
02168 void AssociationWidget::mousePressEvent(QMouseEvent * me) {
02169     m_nMovingPoint = -1;
02170     //make sure we should be here depending on the button
02171     if(me -> button() != Qt::RightButton && me->button() != Qt::LeftButton)
02172         return;
02173     QPoint mep = me->pos();
02174     // See if `mep' is on the connecting line to the association class
02175     if (onAssocClassLine(mep)) {
02176         m_bSelected = true;
02177         selectAssocClassLine();
02178         return;
02179     }
02180     // See if the user has clicked on a point to start moving the line segment
02181     // from that point
02182     checkPoints(mep);
02183     if( me -> state() != Qt::ShiftButton )
02184         m_pView -> clearSelected();
02185     setSelected( !m_bSelected );
02186 }
02187 
02188 void AssociationWidget::mouseReleaseEvent(QMouseEvent * me) {
02189     if(me -> button() != Qt::RightButton && me->button() != Qt::LeftButton) {
02190         setSelected( false );
02191         return;
02192     }
02193 
02194     // Check whether a point was moved and whether the moved point is
02195     // located on the straight line between its neighbours.
02196     // if yes, remove it
02198     if (m_nMovingPoint > 0 && m_nMovingPoint < m_LinePath.count() - 1)
02199     {
02200         QPoint m = m_LinePath.getPoint(m_nMovingPoint);
02201         QPoint b = m_LinePath.getPoint(m_nMovingPoint - 1);
02202         QPoint a = m_LinePath.getPoint(m_nMovingPoint + 1);
02203         if ( (b.x() == m.x() && a.x() == m.x()) ||
02204              (b.y() == m.y() && a.y() == m.y()) )
02205             m_LinePath.removePoint(m_nMovingPoint, m, POINT_DELTA);
02206     }
02207     m_nMovingPoint = -1;
02208     const QPoint p = me->pos();
02209 
02210     if (me->button() != Qt::RightButton) {
02211         return;
02212     }
02213 
02214     // right button action:
02215     //work out the type of menu we want
02216     //work out if the association allows rolenames, multiplicity, etc
02217     //also must be within a certain distance to be a multiplicity menu
02218     ListPopupMenu::Menu_Type menuType = ListPopupMenu::mt_Undefined;
02219     const int DISTANCE = 40;//must be within this many pixels for it to be a multi menu
02220     const QPoint lpStart = m_LinePath.getPoint(0);
02221     const QPoint lpEnd = m_LinePath.getPoint(m_LinePath.count() - 1);
02222     const int startXDiff = lpStart.x() - p.x();
02223     const int startYDiff = lpStart.y() - p.y();
02224     const int endXDiff = lpEnd.x() - p.x();
02225     const int endYDiff = lpEnd.y() - p.y();
02226     const float lengthMAP = sqrt( double(startXDiff * startXDiff + startYDiff * startYDiff) );
02227     const float lengthMBP = sqrt( double(endXDiff * endXDiff + endYDiff * endYDiff) );
02228     const Association_Type type = getAssocType();
02229     //allow multiplicity
02230     if( AssocRules::allowMultiplicity( type, getWidget(A) -> getBaseType() ) ) {
02231         if(lengthMAP < DISTANCE)
02232             menuType =  ListPopupMenu::mt_MultiA;
02233         else if(lengthMBP < DISTANCE)
02234             menuType = ListPopupMenu::mt_MultiB;
02235     }
02236     if( menuType == ListPopupMenu::mt_Undefined ) {
02237         if (type == at_Anchor || onAssocClassLine(p))
02238             menuType = ListPopupMenu::mt_Anchor;
02239         else if (isCollaboration())
02240             menuType = ListPopupMenu::mt_Collaboration_Message;
02241         else if( AssocRules::allowRole( type ) )
02242             menuType = ListPopupMenu::mt_FullAssociation;
02243         else
02244             menuType = ListPopupMenu::mt_Association_Selected;
02245     }
02246     m_pMenu = new ListPopupMenu(m_pView, menuType);
02247     m_pMenu->popup(me -> globalPos());
02248     connect(m_pMenu, SIGNAL(activated(int)), this, SLOT(slotMenuSelection(int)));
02249     setSelected();
02250 }//end method mouseReleaseEvent
02251 
02252 bool AssociationWidget::showDialog() {
02253     AssocPropDlg dlg(static_cast<QWidget*>(m_pView), this );
02254     if (! dlg.exec())
02255         return false;
02256     QString name = getName();
02257     QString doc = getDoc();
02258     QString roleADoc = getRoleDoc(A), roleBDoc = getRoleDoc(B);
02259     QString rnA = getRoleName(A), rnB = getRoleName(B);
02260     QString ma = getMulti(A), mb = getMulti(B);
02261     Uml::Visibility vA = getVisibility(A), vB = getVisibility(B);
02262     Changeability_Type cA = getChangeability(A), cB = getChangeability(B);
02263     //rules built into these functions to stop updating incorrect values
02264     setName(name);
02265     setRoleName(rnA, A);
02266     setRoleName(rnB, B);
02267     setDoc(doc);
02268     setRoleDoc(roleADoc, A);
02269     setRoleDoc(roleBDoc, B);
02270     setMulti(ma, A);
02271     setMulti(mb, B);
02272     setVisibility(vA, A);
02273     setVisibility(vB, B);
02274     setChangeability(cA, A);
02275     setChangeability(cB, B);
02276     m_pView -> showDocumentation( this, true );
02277     return true;
02278 }
02279 
02280 void AssociationWidget::slotMenuSelection(int sel) {
02281     QString oldText, newText;
02282     QFont font;
02283     QRegExpValidator v(QRegExp(".*"), 0);
02284     Uml::Association_Type atype = getAssocType();
02285     Uml::Role_Type r = Uml::B;
02286 
02287     //if it's a collaboration message we now just use the code in floatingtextwidget
02288     //this means there's some redundant code below but that's better than duplicated code
02289     if (isCollaboration() && sel != ListPopupMenu::mt_Delete) {
02290         m_pName->slotMenuSelection(sel);
02291         return;
02292     }
02293 
02294     switch(sel) {
02295     case ListPopupMenu::mt_Properties:
02296         if(atype == at_Seq_Message || atype == at_Seq_Message_Self) {
02297             // show op dlg for seq. diagram here
02298             // don't worry about here, I don't think it can get here as
02299             // line is widget on seq. diagram
02300             // here just in case - remove later after testing
02301             kDebug() << "AssociationWidget::slotMenuSelection(mt_Properties): "
02302             << "assoctype is " << atype << endl;
02303         } else {  //standard assoc dialog
02304             m_pView -> updateDocumentation( false );
02305             showDialog();
02306         }
02307         break;
02308 
02309     case ListPopupMenu::mt_Delete:
02310         if (m_pAssocClassLineSel0)
02311             removeAssocClassLine();
02312         else
02313             m_pView->removeAssocInViewAndDoc(this);
02314         break;
02315 
02316     case ListPopupMenu::mt_Rename_MultiA:
02317         r = Uml::A;   // fall through
02318     case ListPopupMenu::mt_Rename_MultiB:
02319         if (m_role[r].m_pMulti)
02320             oldText = m_role[r].m_pMulti->getText();
02321         else
02322             oldText = "";
02323         newText = KInputDialog::getText(i18n("Multiplicity"),
02324                                         i18n("Enter multiplicity:"),
02325                                         oldText, NULL, m_pView, NULL, &v);
02326         if (newText != oldText) {
02327             if (FloatingTextWidget::isTextValid(newText)) {
02328                 setMulti(newText, r);
02329             } else {
02330                 m_pView->removeWidget(m_role[r].m_pMulti);
02331                 m_role[r].m_pMulti = NULL;
02332             }
02333         }
02334         break;
02335 
02336     case ListPopupMenu::mt_Rename_Name:
02337         if(m_pName)
02338             oldText = m_pName->getText();
02339         else
02340             oldText = "";
02341         newText = KInputDialog::getText(i18n("Association Name"),
02342                                         i18n("Enter association name:"),
02343                                         oldText, NULL, m_pView, NULL, &v);
02344         if (newText != oldText) {
02345             if (FloatingTextWidget::isTextValid(newText)) {
02346                 setName(newText);
02347             } else {
02348                 m_pView->removeWidget(m_pName);
02349                 m_pName = NULL;
02350             }
02351         }
02352         break;
02353 
02354     case ListPopupMenu::mt_Rename_RoleAName:
02355         r = Uml::A;   // fall through
02356     case ListPopupMenu::mt_Rename_RoleBName:
02357         if (m_role[r].m_pRole)
02358             oldText = m_role[r].m_pRole->getText();
02359         else
02360             oldText = "";
02361         newText = KInputDialog::getText(i18n("Role Name"),
02362                                         i18n("Enter role name:"),
02363                                         oldText, NULL, m_pView, NULL, &v);
02364         if (newText != oldText) {
02365             if (FloatingTextWidget::isTextValid(newText)) {
02366                 setRoleName(newText, r);
02367             } else {
02368                 m_pView->removeWidget(m_role[r].m_pRole);
02369                 m_role[r].m_pRole = NULL;
02370             }
02371         }
02372         break;
02373 
02374     case ListPopupMenu::mt_Change_Font:
02375         font = getFont();
02376         if( KFontDialog::getFont( font, false, m_pView ) )
02377             lwSetFont(font);
02378         break;
02379 
02380     case ListPopupMenu::mt_Change_Font_Selection:
02381         font = getFont();
02382         if( KFontDialog::getFont( font, false, m_pView ) ) {
02383             m_pView -> selectionSetFont( font );
02384             m_umldoc->setModified(true);
02385         }
02386         break;
02387 
02388     case ListPopupMenu::mt_Line_Color:
02389     case ListPopupMenu::mt_Line_Color_Selection:
02390         {
02391             QColor newColour;
02392             if( KColorDialog::getColor(newColour) ) {
02393                 m_pView->selectionSetLineColor(newColour);
02394                 m_umldoc->setModified(true);
02395             }
02396         }
02397         break;
02398 
02399     case ListPopupMenu::mt_Cut:
02400         m_pView->setStartedCut();
02401         UMLApp::app()->slotEditCut();
02402         break;
02403 
02404     case ListPopupMenu::mt_Copy:
02405         UMLApp::app()->slotEditCopy();
02406         break;
02407 
02408     case ListPopupMenu::mt_Paste:
02409         UMLApp::app()->slotEditPaste();
02410         break;
02411 
02412     case ListPopupMenu::mt_Reset_Label_Positions:
02413         resetTextPositions();
02414         break;
02415     }//end switch
02416 }
02417 
02418 
02419 void AssociationWidget::lwSetFont (QFont font) {
02420     if( m_pName) {
02421         m_pName->setFont( font );
02422     }
02423     if( m_role[A].m_pRole ) {
02424         m_role[A].m_pRole->setFont( font );
02425     }
02426     if( m_role[B].m_pRole ) {
02427         m_role[B].m_pRole->setFont( font );
02428     }
02429     if( m_role[A].m_pMulti ) {
02430         m_role[A].m_pMulti->setFont( font );
02431     }
02432     if( m_role[B].m_pMulti ) {
02433         m_role[B].m_pMulti->setFont( font );
02434     }
02435     if( m_role[A].m_pChangeWidget)
02436         m_role[A].m_pChangeWidget->setFont( font );
02437     if( m_role[B].m_pChangeWidget)
02438         m_role[B].m_pChangeWidget->setFont( font );
02439 }
02440 
02441 // find a general font for the association
02442 QFont AssociationWidget::getFont() const {
02443     QFont font;
02444 
02445     if( m_role[A].m_pRole )
02446         font = m_role[A].m_pRole -> getFont( );
02447     else    if( m_role[B].m_pRole)
02448         font = m_role[B].m_pRole -> getFont( );
02449     else    if( m_role[A].m_pMulti )
02450         font = m_role[A].m_pMulti -> getFont( );
02451     else    if( m_role[B].m_pMulti )
02452         font = m_role[B].m_pMulti -> getFont( );
02453     else    if( m_role[A].m_pChangeWidget)
02454         font = m_role[A].m_pChangeWidget-> getFont( );
02455     else    if( m_role[B].m_pChangeWidget)
02456         font = m_role[B].m_pChangeWidget-> getFont( );
02457     else    if( m_pName)
02458         font = m_pName-> getFont( );
02459     else
02460         font = m_role[A].m_pWidget -> getFont();
02461 
02462     return font;
02463 }
02464 
02465 void AssociationWidget::setLineColor(const QColor &colour) {
02466     WidgetBase::setLineColor(colour);
02467     m_LinePath.setLineColor(colour);
02468 }
02469 
02470 void AssociationWidget::setLineWidth(uint width) {
02471     WidgetBase::setLineWidth(width);
02472     m_LinePath.setLineWidth(width);
02473 }
02474 
02475 void AssociationWidget::checkPoints(const QPoint &p) {
02476     m_nMovingPoint = -1;
02477     //only check if more than the two endpoints
02478     int size = m_LinePath.count();
02479     if( size <= 2 )
02480         return;
02481     //check all points except the end points to see if we clicked on one of them
02482     QPoint tempPoint;
02483     int x, y;
02484     const int BOUNDARY = 4; // check for pixels around the point
02485     for(int i=1;i<size-1;i++) {
02486         tempPoint = m_LinePath.getPoint( i );
02487         x = tempPoint.x();
02488         y = tempPoint.y();
02489         if( x - BOUNDARY <= p.x() && x + BOUNDARY >= p.x() &&
02490                 y - BOUNDARY <= p.y() && y + BOUNDARY >= p.y() ) {
02491             m_nMovingPoint = i;
02492             break; //no need to check the rest
02493         }//end if
02494     }//end for
02495 }
02496 
02497 void AssociationWidget::mouseMoveEvent(QMouseEvent* me) {
02498     if( me->state() != Qt::LeftButton) {
02499         return;
02500     }
02501 
02502     // if we have no moving point,create one
02503     if (m_nMovingPoint == -1)
02504     {
02505         //create moving point near the mouse on the line
02506         int i = m_LinePath.onLinePath(me->pos());
02507 
02508         if (i == -1)
02509             return;
02510         m_LinePath.insertPoint( i + 1, me->pos() );
02511         m_nMovingPoint = i + 1;
02512     }
02513 
02514     setSelected();
02515     //new position for point
02516     QPoint p = me->pos();
02517     QPoint oldp = m_LinePath.getPoint(m_nMovingPoint);
02518 
02519     if( m_pView -> getSnapToGrid() ) {
02520         int newX = m_pView->snappedX( p.x() );
02521         int newY = m_pView->snappedY( p.y() );
02522         p.setX(newX);
02523         p.setY(newY);
02524     }
02525 
02526     // Prevent the moving vertex from disappearing underneath a widget
02527     // (else there's no way to get it back.)
02528     UMLWidget *onW = m_pView->getWidgetAt(p);
02529     if (onW && onW->getBaseType() != Uml::wt_Box) {  // boxes are transparent
02530         const int pX = p.x();
02531         const int pY = p.y();
02532         const int wX = onW->getX();
02533         const int wY = onW->getY();
02534         const int wWidth = onW->getWidth();
02535         const int wHeight = onW->getHeight();
02536         if (pX > wX && pX < wX + wWidth) {
02537             const int midX = wX + wWidth / 2;
02538             if (pX <= midX)
02539                 p.setX(wX);
02540             else
02541                 p.setX(wX + wWidth);
02542         }
02543         if (pY > wY && pY < wY + wHeight) {
02544             const int midY = wY + wHeight / 2;
02545             if (pY <= midY)
02546                 p.setY(wY);
02547             else
02548                 p.setY(wY + wHeight);
02549         }
02550     }
02551 
02552     //move event called now
02553     QMoveEvent m(p, oldp);
02554     moveEvent(&m);
02555     m_pView->resizeCanvasToItems();
02556 }
02557 
02558 AssociationWidget::Region AssociationWidget::getWidgetRegion(AssociationWidget * widget) const {
02559     if(widget -> getWidget(A) == m_role[A].m_pWidget)
02560         return m_role[A].m_WidgetRegion;
02561     if(widget -> getWidget(B) == m_role[B].m_pWidget)
02562         return m_role[B].m_WidgetRegion;
02563     return Error;
02564 }
02565 
02566 int AssociationWidget::getRegionCount(AssociationWidget::Region region, Role_Type role) {
02567     if(region == Error)
02568         return 0;
02569     int widgetCount = 0;
02570     AssociationWidgetList list = m_pView -> getAssociationList();
02571     AssociationWidgetListIt assoc_it(list);
02572     AssociationWidget* assocwidget = 0;
02573     while((assocwidget = assoc_it.current())) {
02574         ++assoc_it;
02575         //don't count this association
02576         if (assocwidget == this)
02577             continue;
02578         const WidgetRole& otherA = assocwidget->m_role[A];
02579         const WidgetRole& otherB = assocwidget->m_role[B];
02580         const UMLWidget *a = otherA.m_pWidget;
02581         const UMLWidget *b = otherB.m_pWidget;
02582         /*
02583         //don't count associations to self if both of their end points are on the same region
02584         //they are different and placement won't interfere with them
02585         if( a == b && otherA.m_WidgetRegion == otherB.m_WidgetRegion )
02586                 continue;
02587          */
02588         if (m_role[role].m_pWidget == a && region == otherA.m_WidgetRegion)
02589             widgetCount++;
02590         else if (m_role[role].m_pWidget == b && region == otherB.m_WidgetRegion)
02591             widgetCount++;
02592     }//end while
02593     return widgetCount;
02594 }
02595 
02596 QPoint AssociationWidget::findIntercept(const QRect &rect, const QPoint &point) {
02597     Region region = findPointRegion(rect, point.x(), point.y());
02598     /*
02599     const char *regionStr[] = { "Error",
02600         "West", "North", "East", "South",
02601         "NorthWest", "NorthEast", "SouthEast", "SouthWest",
02602         "Center"
02603     };
02604     kDebug() << "findPointRegion(rect(" << rect.x() << "," << rect.y()
02605           << "," << rect.width() << "," << rect.height() << "), p("
02606           << point.x() << "," << point.y() << ")) = " << regionStr[region]
02607           << endl;
02608      */
02609     // Move some regions to the standard ones.
02610     switch (region) {
02611     case NorthWest:
02612         region = North;
02613         break;
02614     case NorthEast:
02615         region = East;
02616         break;
02617     case SouthEast:
02618         region = South;
02619         break;
02620     case SouthWest:
02621     case Center:
02622         region = West;
02623         break;
02624     default:
02625         break;
02626     }
02627     // The Qt coordinate system has (0,0) in the top left corner.
02628     // In order to go to the regular XY coordinate system with (0,0)
02629     // in the bottom left corner, we swap the X and Y axis.
02630     // That's why the following assignments look twisted.
02631     const int rectHalfWidth = rect.height() / 2;
02632     const int rectHalfHeight = rect.width() / 2;
02633     const int rectMidX = rect.y() + rectHalfWidth;
02634     const int rectMidY = rect.x() + rectHalfHeight;
02635     const int pX = point.y();
02636     const int pY = point.x();
02637     const int dX = rectMidX - pX;
02638     const int dY = rectMidY - pY;
02639     switch (region) {
02640     case West:
02641         region = South;
02642         break;
02643     case North:
02644         region = East;
02645         break;
02646     case East:
02647         region = North;
02648         break;
02649     case South:
02650         region = West;
02651         break;
02652     default:
02653         break;
02654     }
02655     // Now we have regular coordinates with the point (0,0) in the
02656     // bottom left corner.
02657     if (region == North || region == South) {
02658         int yoff = rectHalfHeight;
02659         if (region == North)
02660             yoff = -yoff;
02661         if (dX == 0) {
02662             return QPoint(rectMidY + yoff, rectMidX);  // swap back X and Y
02663         }
02664         if (dY == 0) {
02665             kError() << "AssociationWidget::findIntercept usage error: "
02666             << "North/South (dY == 0)" << endl;
02667             return QPoint(0,0);
02668         }
02669         const float m = (float)dY / (float)dX;
02670         const float b = (float)pY - m * pX;
02671         const int inputY = rectMidY + yoff;
02672         const float outputX = ((float)inputY - b) / m;
02673         return QPoint(inputY, (int)outputX);  // swap back X and Y
02674     } else {
02675         int xoff = rectHalfWidth;
02676         if (region == East)
02677             xoff = -xoff;
02678         if (dY == 0)
02679             return QPoint(rectMidY, rectMidX + xoff);  // swap back X and Y
02680         if (dX == 0) {
02681             kError() << "AssociationWidget::findIntercept usage error: "
02682             << "East/West (dX == 0)" << endl;
02683             return QPoint(0,0);
02684         }
02685         const float m = (float)dY / (float)dX;
02686         const float b = (float)pY - m * pX;
02687         const int inputX = rectMidX + xoff;
02688         const float outputY = m * (float)inputX + b;
02689         return QPoint((int)outputY, inputX);  // swap back X and Y
02690     }
02691 }
02692 
02693 int AssociationWidget::findInterceptOnEdge(const QRect &rect,
02694         AssociationWidget::Region region,
02695         const QPoint &point)
02696 {
02697     // The Qt coordinate system has (0,0) in the top left corner.
02698     // In order to go to the regular XY coordinate system with (0,0)
02699     // in the bottom left corner, we swap the X and Y axis.
02700     // That's why the following assignments look twisted.
02701     const int rectHalfWidth = rect.height() / 2;
02702     const int rectHalfHeight = rect.width() / 2;
02703     const int rectMidX = rect.y() + rectHalfWidth;
02704     const int rectMidY = rect.x() + rectHalfHeight;
02705     const int dX = rectMidX - point.y();
02706     const int dY = rectMidY - point.x();
02707     switch (region) {
02708     case West:
02709         region = South;
02710         break;
02711     case North:
02712         region = West;
02713         break;
02714     case East:
02715         region = North;
02716         break;
02717     case South:
02718         region = East;
02719         break;
02720     default:
02721         break;
02722     }
02723     // Now we have regular coordinates with the point (0,0) in the
02724     // bottom left corner.
02725     if (region == North || region == South) {
02726         if (dX == 0)
02727             return rectMidY;
02728         // should be rectMidX, but we go back to Qt coord.sys.
02729         if (dY == 0) {
02730             kError() << "AssociationWidget::findInterceptOnEdge usage error: "
02731             << "North/South (dY == 0)" << endl;
02732             return -1;
02733         }
02734         const float m = (float)dY / (float)dX;
02735         float relativeX;
02736         if (region == North)
02737             relativeX = (float)rectHalfHeight / m;
02738         else
02739             relativeX = -(float)rectHalfHeight / m;
02740         return (rectMidY + (int)relativeX);
02741         // should be rectMidX, but we go back to Qt coord.sys.
02742     } else {
02743         if (dY == 0)
02744             return rectMidX;
02745         // should be rectMidY, but we go back to Qt coord.sys.
02746         if (dX == 0) {
02747             kError() << "AssociationWidget::findInterceptOnEdge usage error: "
02748             << "East/West (dX == 0)" << endl;
02749             return -1;
02750         }
02751         const float m = (float)dY / (float)dX;
02752         float relativeY = m * (float)rectHalfWidth;
02753         if (region == West)
02754             relativeY = -relativeY;
02755         return (rectMidX + (int)relativeY);
02756         // should be rectMidY, but we go back to Qt coord.sys.
02757     }
02758 }
02759 
02760 void AssociationWidget::insertIntoLists(int position, const AssociationWidget* assoc)
02761 {
02762     bool did_insertion = false;
02763     for (int index = 0; index < m_positions_len; index++) {
02764         if (position < m_positions[index]) {
02765             for (int moveback = m_positions_len; moveback > index; moveback--)
02766                 m_positions[moveback] = m_positions[moveback - 1];
02767             m_positions[index] = position;
02768             m_ordered.insert(index, assoc);
02769             did_insertion = true;
02770             break;
02771         }
02772     }
02773     if (! did_insertion) {
02774         m_positions[m_positions_len] = position;
02775         m_ordered.append(assoc);
02776     }
02777     m_positions_len++;
02778 }
02779 
02780 void AssociationWidget::updateAssociations(int totalCount,
02781         AssociationWidget::Region region,
02782         Role_Type role)
02783 {
02784     if( region == Error )
02785         return;
02786     AssociationWidgetList list = m_pView -> getAssociationList();
02787     AssociationWidgetListIt assoc_it(list);
02788     AssociationWidget* assocwidget = 0;
02789     UMLWidget *ownWidget = m_role[role].m_pWidget;
02790     m_positions_len = 0;
02791     m_ordered.clear();
02792     // we order the AssociationWidget list by region and x/y value
02793     while ( (assocwidget = assoc_it.current()) ) {
02794         ++assoc_it;
02795         WidgetRole *roleA = &assocwidget->m_role[A];
02796         WidgetRole *roleB = &assocwidget->m_role[B];
02797         UMLWidget *wA = roleA->m_pWidget;
02798         UMLWidget *wB = roleB->m_pWidget;
02799         // Skip self associations.
02800         if (wA == wB)
02801             continue;
02802         // Now we must find out with which end the assocwidget connects
02803         // to the input widget (ownWidget).
02804         bool inWidgetARegion = ( ownWidget == wA &&
02805                                  region == roleA->m_WidgetRegion );
02806         bool inWidgetBRegion = ( ownWidget == wB &&
02807                                  region == roleB->m_WidgetRegion);
02808         if ( !inWidgetARegion && !inWidgetBRegion )
02809             continue;
02810         // Determine intercept position on the edge indicated by `region'.
02811         UMLWidget * otherWidget = (inWidgetARegion ? wB : wA);
02812         LinePath *linepath = assocwidget->getLinePath();
02813         QPoint refpoint;
02814         if (assocwidget->linePathStartsAt(otherWidget))
02815             refpoint = linepath->getPoint(linepath->count() - 2);
02816         else
02817             refpoint = linepath->getPoint(1);
02818         // The point is authoritative if we're called for the second time
02819         // (i.e. role==B) or it is a waypoint on the line path.
02820         bool pointIsAuthoritative = (role == B || linepath->count() > 2);
02821         if (! pointIsAuthoritative) {
02822             // If the point is not authoritative then we use the other
02823             // widget's center.
02824             refpoint.setX(otherWidget->getX() + otherWidget->getWidth() / 2);
02825             refpoint.setY(otherWidget->getY() + otherWidget->getHeight() / 2);
02826         }
02827         int intercept = findInterceptOnEdge(ownWidget->rect(), region, refpoint);
02828         if (intercept < 0) {
02829             kDebug() << "updateAssociations: error from findInterceptOnEdge for"
02830             << " assocType=" << assocwidget->getAssocType()
02831             << " ownWidget=" << ownWidget->getName()
02832             << " otherWidget=" << otherWidget->getName() << endl;
02833             continue;
02834         }
02835         insertIntoLists(intercept, assocwidget);
02836     } // while ( (assocwidget = assoc_it.current()) )
02837 
02838     // we now have an ordered list and we only have to call updateRegionLineCount
02839     int index = 1;
02840     for (assocwidget = m_ordered.first(); assocwidget; assocwidget = m_ordered.next()) {
02841         if (ownWidget == assocwidget->getWidget(A)) {
02842             assocwidget->updateRegionLineCount(index++, totalCount, region, A);
02843         } else if (ownWidget == assocwidget->getWidget(B)) {
02844             assocwidget->updateRegionLineCount(index++, totalCount, region, B);
02845         }
02846     } // for (assocwidget = ordered.first(); ...)
02847 }
02848 
02849 void AssociationWidget::updateRegionLineCount(int index, int totalCount,
02850         AssociationWidget::Region region,
02851         Role_Type role) {
02852     if( region == Error )
02853         return;
02854     // If the association is to self and the line ends are on the same region then
02855     // use a different calculation.
02856     if (m_role[A].m_pWidget == m_role[B].m_pWidget &&
02857             m_role[A].m_WidgetRegion == m_role[B].m_WidgetRegion) {
02858         UMLWidget * pWidget = m_role[A].m_pWidget;
02859         int x = pWidget -> getX();
02860         int y = pWidget -> getY();
02861         int wh = pWidget -> height();
02862         int ww = pWidget -> width();
02863         int size = m_LinePath.count();
02864         // See if above widget ok to place assoc.
02865         switch( m_role[A].m_WidgetRegion ) {
02866         case North:
02867             m_LinePath.setPoint( 0, QPoint( x + ( ww / 4 ), y ) );
02868             m_LinePath.setPoint( size - 1, QPoint(x + ( ww * 3 / 4 ), y ) );
02869             break;
02870 
02871         case South:
02872             m_LinePath.setPoint( 0, QPoint( x + ( ww / 4 ), y + wh ) );
02873             m_LinePath.setPoint( size - 1, QPoint( x + ( ww * 3 / 4 ), y + wh ) );
02874             break;
02875 
02876         case East:
02877             m_LinePath.setPoint( 0, QPoint( x + ww, y + ( wh / 4 ) ) );
02878             m_LinePath.setPoint( size - 1, QPoint( x + ww, y + ( wh * 3 / 4 ) ) );
02879             break;
02880 
02881         case West:
02882             m_LinePath.setPoint( 0, QPoint( x, y + ( wh / 4 ) ) );
02883             m_LinePath.setPoint( size - 1, QPoint( x, y + ( wh * 3 / 4 ) ) );
02884             break;
02885         default:
02886             break;
02887         }//end switch
02888         m_role[A].m_OldCorner.setX( x );
02889         m_role[A].m_OldCorner.setY( y );
02890         m_role[B].m_OldCorner.setX( x );
02891         m_role[B].m_OldCorner.setY( y );
02892 
02893         return;
02894     }
02895 
02896     WidgetRole& robj = m_role[role];
02897     UMLWidget * pWidget = robj.m_pWidget;
02898 
02899     robj.m_nIndex = index;
02900     robj.m_nTotalCount = totalCount;
02901     int x = pWidget->getX();
02902     int y = pWidget->getY();
02903     robj.m_OldCorner.setX(x);
02904     robj.m_OldCorner.setY(y);
02905     int ww = pWidget->getWidth();
02906     int wh = pWidget->getHeight();
02907     const bool angular = Settings::getOptionState().generalState.angularlines;
02908     int ch = 0;
02909     int cw = 0;
02910     if (angular) {
02911         uint nind = (role == A ? 1 : m_LinePath.count() - 2);
02912         QPoint neighbour = m_LinePath.getPoint(nind);
02913         if (neighbour.x() < x)
02914             cw = 0;
02915         else if (neighbour.x() > x + ww)
02916             cw = 0 + ww;
02917         else
02918             cw = neighbour.x() - x;
02919         if (neighbour.y() < y)
02920             ch = 0;
02921         else if (neighbour.y() > y + wh)
02922             ch = 0 + wh;
02923         else
02924             ch = neighbour.y() - y;
02925     } else {
02926         ch = wh * index / totalCount;
02927         cw = ww * index / totalCount;
02928     }
02929 
02930     int snapX = m_pView->snappedX(x + cw);
02931     int snapY = m_pView->snappedY(y + ch);
02932 
02933     QPoint pt;
02934     if (angular) {
02935         pt = QPoint(snapX, snapY);
02936     } else {
02937         switch(region) {
02938             case West:
02939                 pt.setX(x);
02940                 pt.setY(snapY);
02941                 break;
02942             case North:
02943                 pt.setX(snapX);
02944                 pt.setY(y);
02945                 break;
02946             case East:
02947                 pt.setX(x + ww);
02948                 pt.setY(snapY);
02949                 break;
02950             case South:
02951                 pt.setX(snapX);
02952                 pt.setY(y + wh);
02953                 break;
02954             case Center:
02955                 pt.setX(x + ww / 2);
02956                 pt.setY(y + wh / 2);
02957                 break;
02958             default:
02959                 break;
02960         }
02961     }
02962     if (role == A)
02963         m_LinePath.setPoint( 0, pt );
02964     else {
02965         m_LinePath.setPoint( m_LinePath.count() - 1, pt );
02966         LinePath::Region r = ( region == South || region == North ) ?
02967                              LinePath::TopBottom : LinePath::LeftRight;
02968         m_LinePath.setDockRegion( r );
02969     }
02970 }
02971 
02972 void AssociationWidget::setSelected(bool _select /* = true */) {
02973     m_bSelected = _select;
02974     if( m_pName)
02975         m_pName-> setSelected( _select );
02976     if( m_role[A].m_pRole )
02977         m_role[A].m_pRole -> setSelected( _select );
02978     if( m_role[B].m_pRole )
02979         m_role[B].m_pRole -> setSelected( _select );
02980     if( m_role[A].m_pMulti )
02981         m_role[A].m_pMulti -> setSelected( _select );
02982     if( m_role[B].m_pMulti )
02983         m_role[B].m_pMulti -> setSelected( _select );
02984     if( m_role[A].m_pChangeWidget)
02985         m_role[A].m_pChangeWidget-> setSelected( _select );
02986     if( m_role[B].m_pChangeWidget)
02987         m_role[B].m_pChangeWidget-> setSelected( _select );
02988     kapp->processEvents();
02989     //Update the docwindow for this association.
02990     // This is done last because each of the above setSelected calls
02991     // overwrites the docwindow, but we want the main association doc
02992     // to win.
02993     if( _select ) {
02994         if( m_pView -> getSelectCount() == 0 )
02995                 m_pView -> showDocumentation( this, false );
02996     } else
02997         m_pView -> updateDocumentation( true );
02998     kapp->processEvents();
02999     m_LinePath.setSelected( _select );
03000     if (! _select) {
03001         // For now, if _select is true we don't make the assoc class line
03002         // selected. But that's certainly open for discussion.
03003         // At any rate, we need to deselect the assoc class line
03004         // if _select is false.
03005         selectAssocClassLine(false);
03006     }
03007 }
03008 
03009 bool AssociationWidget::onAssocClassLine(const QPoint &point) {
03010     if (m_pAssocClassLine == NULL)
03011         return false;
03012     QCanvasItemList list = m_pView->canvas()->collisions(point);
03013     QCanvasItemList::iterator end(list.end());
03014     for (QCanvasItemList::iterator item_it(list.begin()); item_it != end; ++item_it) {
03015         if (*item_it == m_pAssocClassLine)
03016             return true;
03017     }
03018     return false;
03019 }
03020 
03021 bool AssociationWidget::onAssociation(const QPoint & point) {
03022     if (m_LinePath.onLinePath(point) != -1)
03023         return true;
03024     return onAssocClassLine(point);
03025 }
03026 
03027 void AssociationWidget::slotRemovePopupMenu()
03028 {
03029     if(m_pMenu) {
03030         disconnect(m_pMenu, SIGNAL(activated(int)), this, SLOT(slotMenuSelection(int)));
03031         delete m_pMenu;
03032         m_pMenu = 0;
03033     }
03034 }
03035 
03036 void AssociationWidget::slotClearAllSelected() {
03037     setSelected( false );
03038 }
03039 
03040 void AssociationWidget::moveMidPointsBy( int x, int y ) {
03041     int pos = m_LinePath.count() - 1;
03042     for( int i=1 ; i < (int)pos ; i++ ) {
03043         QPoint p = m_LinePath.getPoint( i );
03044         int newX = p.x() + x;
03045         int newY = p.y() + y;
03046         newX = m_pView -> snappedX( newX );
03047         newY = m_pView -> snappedY( newY );
03048         p.setX( newX );
03049         p.setY( newY );
03050         m_LinePath.setPoint( i, p );
03051     }
03052 }
03053 
03054 void AssociationWidget::moveEntireAssoc( int x, int y ) {
03055     //TODO: ADD SUPPORT FOR ASSOC. ON SEQ. DIAGRAMS WHEN NOTES BACK IN.
03056     moveMidPointsBy( x, y );
03057     calculateEndingPoints();
03058     calculateNameTextSegment();
03059     resetTextPositions();
03060 }
03061 
03062 QRect AssociationWidget::getAssocLineRectangle()
03063 {
03064     QRect rectangle;
03065     QPoint p;
03066     uint pen_width;
03067 
03068     /* we also want the end points connected to the other widget */
03069     int pos = m_LinePath.count();
03070 
03071     /* go through all points on the linepath */
03072     for( int i=0 ; i < (int) pos; i++ )
03073     {
03074         p = m_LinePath.getPoint( i );
03075 
03076         /* the first point is our starting point */
03077         if (i == 0) {
03078             rectangle.setRect(p.x(), p.y(), 0, 0);
03079             continue;
03080         }
03081 
03082         /* the lines have the width of the pen */
03083         pen_width = m_LinePath.getPen().width();
03084         if (pen_width == 0)
03085             pen_width = 1; // width must be at least 1
03086 
03087         if (p.x() < rectangle.x())
03088             rectangle.setX(p.x());
03089         if (p.y() < rectangle.y())
03090             rectangle.setY(p.y());
03091         if (p.x() > rectangle.x() + rectangle.width()) {
03092             int newX = p.x() - rectangle.x() + pen_width;
03093             rectangle.setWidth(abs(newX));
03094         }
03095         if (p.y() > rectangle.y() + rectangle.height()) {
03096             int newY = p.y() - rectangle.y() + pen_width;
03097             rectangle.setHeight(abs(newY));
03098         }
03099     }
03100     return rectangle;
03101 }
03102 
03103 void AssociationWidget::setUMLObject(UMLObject *obj) {
03104     WidgetBase::setUMLObject(obj);
03105     if (obj == NULL)
03106         return;
03107     Uml::Object_Type ot = obj->getBaseType();
03108     if (ot == Uml::ot_Attribute) {
03109         UMLClassifier *klass = static_cast<UMLClassifier*>(obj->parent());
03110         connect(klass, SIGNAL(attributeRemoved(UMLClassifierListItem*)),
03111                 this, SLOT(slotAttributeRemoved(UMLClassifierListItem*)));
03112     } else if (ot == Uml::ot_EntityAttribute) {
03113         UMLEntity *ent = static_cast<UMLEntity*>(obj->parent());
03114         connect(ent, SIGNAL(entityAttributeRemoved(UMLClassifierListItem*)),
03115                 this, SLOT(slotAttributeRemoved(UMLClassifierListItem*)));
03116     }
03117 }
03118 
03119 void AssociationWidget::slotAttributeRemoved(UMLClassifierListItem* obj) {
03120     if (obj != m_pObject)
03121         kDebug() << "AssociationWidget::slotAttributeRemoved:(obj=" << obj
03122                   << "): m_pObject=" << m_pObject << endl;
03123     m_pObject = NULL;
03124     m_pView->removeAssoc(this);
03125 }
03126 
03127 void AssociationWidget::init (UMLView *view)
03128 {
03129     WidgetBase::init(view, wt_Association);
03130 
03131     // pointers to floating text widgets objects owned by this association
03132     m_pName = 0;
03133     m_role[A].m_pChangeWidget = 0;
03134     m_role[B].m_pChangeWidget = 0;
03135     m_role[A].m_pMulti = 0;
03136     m_role[B].m_pMulti = 0;
03137     m_role[A].m_pRole = 0;
03138     m_role[B].m_pRole = 0;
03139     m_role[A].m_pWidget = 0;
03140     m_role[B].m_pWidget = 0;
03141 
03142     // associationwidget attributes
03143     m_role[A].m_WidgetRegion = Error;
03144     m_role[B].m_WidgetRegion = Error;
03145     m_role[A].m_nIndex = 0;
03146     m_role[B].m_nIndex = 0;
03147     m_role[A].m_nTotalCount = 0;
03148     m_role[B].m_nTotalCount = 0;
03149     m_role[A].m_Visibility = Uml::Visibility::Public;
03150     m_role[B].m_Visibility = Uml::Visibility::Public;
03151     m_role[A].m_Changeability = Uml::chg_Changeable;
03152     m_role[B].m_Changeability = Uml::chg_Changeable;
03153     m_positions_len = 0;
03154     m_bActivated = false;
03155     m_unNameLineSegment = 0;
03156     m_pMenu = 0;
03157     m_bSelected = false;
03158     m_nMovingPoint = -1;
03159     m_nLinePathSegmentIndex = -1;
03160     m_pAssocClassWidget = NULL;
03161     m_pAssocClassLine = NULL;
03162     m_pAssocClassLineSel0 = m_pAssocClassLineSel1 = NULL;
03163 
03164     // Initialize local members.
03165     // These are only used if we don't have a UMLAssociation attached.
03166     m_AssocType = Uml::at_Association;
03167     m_umldoc = UMLApp::app()->getDocument();
03168     m_LinePath.setAssociation( this );
03169 
03170     connect(m_pView, SIGNAL(sigRemovePopupMenu()), this, SLOT(slotRemovePopupMenu()));
03171     connect(m_pView, SIGNAL( sigClearAllSelected() ), this, SLOT( slotClearAllSelected() ) );
03172 }
03173 
03174 void AssociationWidget::resetTextPositions() {
03175     if (m_role[A].m_pMulti) {
03176         setTextPosition( tr_MultiA );
03177     }
03178     if (m_role[B].m_pMulti) {
03179         setTextPosition( tr_MultiB );
03180     }
03181     if (m_role[A].m_pChangeWidget) {
03182         setTextPosition( tr_ChangeA );
03183     }
03184     if (m_role[B].m_pChangeWidget) {
03185         setTextPosition( tr_ChangeB );
03186     }
03187     if (m_pName) {
03188         setTextPosition( tr_Name );
03189     }
03190     if (m_role[A].m_pRole) {
03191         setTextPosition( tr_RoleAName );
03192     }
03193     if (m_role[B].m_pRole) {
03194         setTextPosition( tr_RoleBName );
03195     }
03196 }
03197 
03198 void AssociationWidget::setIndex(int index, Role_Type role) {
03199     m_role[role].m_nIndex = index;
03200 }
03201 
03202 int AssociationWidget::getIndex(Role_Type role) const {
03203     return m_role[role].m_nIndex;
03204 }
03205 
03206 void AssociationWidget::setTotalCount(int count, Role_Type role) {
03207     m_role[role].m_nTotalCount = count;
03208 }
03209 
03210 int AssociationWidget::getTotalCount(Role_Type role) const {
03211     return  m_role[role].m_nTotalCount;
03212 }
03213 
03214 UMLOperation *AssociationWidget::getOperation() {
03215     return dynamic_cast<UMLOperation*>(m_pObject);
03216 }
03217 
03218 void AssociationWidget::setOperation(UMLOperation *op) {
03219     if (m_pObject)
03220         disconnect(m_pObject, SIGNAL(modified()), m_pName, SLOT(setMessageText()));
03221     m_pObject = op;
03222     if (m_pObject)
03223         connect(m_pObject, SIGNAL(modified()), m_pName, SLOT(setMessageText()));
03224 }
03225 
03226 UMLClassifier *AssociationWidget::getOperationOwner() {
03227     Role_Type role = (isCollaboration() ? B : A);
03228     UMLObject *o = getWidget(role)->getUMLObject();
03229     if (o == NULL)
03230         return NULL;
03231     UMLClassifier *c = dynamic_cast<UMLClassifier*>(o);
03232     if (c == NULL)
03233         kError() << "AssociationWidget::getOperationOwner: "
03234         << "getWidget(" << role << ") is not a classifier"
03235         << endl;
03236     return c;
03237 }
03238 
03239 void AssociationWidget::setSeqNumAndOp(const QString &seqNum, const QString &op) {
03240     if (! op.isEmpty())
03241         setName(op);
03242     setMulti(seqNum, A);
03243 }
03244 
03245 UMLClassifier *AssociationWidget::getSeqNumAndOp(QString& seqNum, QString& op) {
03246     seqNum = getMulti(A);
03247     op = getName();
03248     UMLObject *o = getWidget(B)->getUMLObject();
03249     UMLClassifier *c = dynamic_cast<UMLClassifier*>(o);
03250     return c;
03251 }
03252 
03253 void AssociationWidget::setCustomOpText(const QString &opText) {
03254     setName(opText);
03255 }
03256 
03257 QString AssociationWidget::getCustomOpText() {
03258     return getName();
03259 }
03260 
03261 void AssociationWidget::setWidget( UMLWidget* widget, Role_Type role) {
03262     m_role[role].m_pWidget = widget;
03263     if (widget) {
03264         m_role[role].m_pWidget->addAssoc(this);
03265         if (m_pObject && m_pObject->getBaseType() == ot_Association)
03266             getAssociation()->setObject(widget->getUMLObject(), role);
03267     }
03268 }
03269 
03270 void AssociationWidget::saveToXMI( QDomDocument & qDoc, QDomElement & qElement ) {
03271     QDomElement assocElement = qDoc.createElement( "assocwidget" );
03272 
03273     WidgetBase::saveToXMI(qDoc, assocElement);
03274     if (m_pObject) {
03275         assocElement.setAttribute( "xmi.id", ID2STR(m_pObject->getID()) );
03276     }
03277     assocElement.setAttribute( "type", m_AssocType );
03278     if (getAssociation() == NULL) {
03279         assocElement.setAttribute( "visibilityA", m_role[A].m_Visibility);
03280         assocElement.setAttribute( "visibilityB", m_role[B].m_Visibility);
03281         assocElement.setAttribute( "changeabilityA", m_role[A].m_Changeability);
03282         assocElement.setAttribute( "changeabilityB", m_role[B].m_Changeability);
03283         if (m_pObject == NULL) {
03284             assocElement.setAttribute( "roleAdoc", m_role[A].m_RoleDoc);
03285             assocElement.setAttribute( "roleBdoc", m_role[B].m_RoleDoc);
03286             assocElement.setAttribute( "documentation", m_Doc );
03287         }
03288     }
03289     assocElement.setAttribute( "widgetaid", ID2STR(getWidgetID(A)) );
03290     assocElement.setAttribute( "widgetbid", ID2STR(getWidgetID(B)) );
03291     assocElement.setAttribute( "indexa", m_role[A].m_nIndex );
03292     assocElement.setAttribute( "indexb", m_role[B].m_nIndex );
03293     assocElement.setAttribute( "totalcounta", m_role[A].m_nTotalCount );
03294     assocElement.setAttribute( "totalcountb", m_role[B].m_nTotalCount );
03295     m_LinePath.saveToXMI( qDoc, assocElement );
03296 
03297     if( m_pName )
03298         m_pName -> saveToXMI( qDoc, assocElement );
03299 
03300     if( m_role[A].m_pMulti )
03301         m_role[A].m_pMulti -> saveToXMI( qDoc, assocElement );
03302 
03303     if( m_role[B].m_pMulti )
03304         m_role[B].m_pMulti -> saveToXMI( qDoc, assocElement );
03305 
03306     if( m_role[A].m_pRole )
03307         m_role[A].m_pRole -> saveToXMI( qDoc, assocElement );
03308 
03309     if( m_role[B].m_pRole )
03310         m_role[B].m_pRole -> saveToXMI( qDoc, assocElement );
03311 
03312     if( m_role[A].m_pChangeWidget )
03313         m_role[A].m_pChangeWidget -> saveToXMI( qDoc, assocElement );
03314 
03315     if( m_role[B].m_pChangeWidget )
03316         m_role[B].m_pChangeWidget -> saveToXMI( qDoc, assocElement );
03317 
03318     if (m_pAssocClassWidget) {
03319         QString acid = ID2STR(m_pAssocClassWidget->getID());
03320         assocElement.setAttribute("assocclass", acid);
03321         assocElement.setAttribute("aclsegindex", m_nLinePathSegmentIndex);
03322     }
03323 
03324     qElement.appendChild( assocElement );
03325 }
03326 
03327 bool AssociationWidget::loadFromXMI( QDomElement & qElement,
03328                                      const UMLWidgetList& widgets,
03329                                      const MessageWidgetList* pMessages )
03330 {
03331     WidgetBase::loadFromXMI(qElement);
03332 
03333     // load child widgets first
03334     QString widgetaid = qElement.attribute( "widgetaid", "-1" );
03335     QString widgetbid = qElement.attribute( "widgetbid", "-1" );
03336     Uml::IDType aId = STR2ID(widgetaid);
03337     Uml::IDType bId = STR2ID(widgetbid);
03338     UMLWidget *pWidgetA = Widget_Utils::findWidget( aId, widgets, pMessages );
03339     if (!pWidgetA) {
03340         kError() << "AssociationWidget::loadFromXMI(): "
03341         << "cannot find widget for roleA id " << ID2STR(aId) << endl;
03342         return false;
03343     }
03344     UMLWidget *pWidgetB = Widget_Utils::findWidget( bId, widgets, pMessages );
03345     if (!pWidgetB) {
03346         kError() << "AssociationWidget::loadFromXMI(): "
03347         << "cannot find widget for roleB id " << ID2STR(bId) << endl;
03348         return false;
03349     }
03350     setWidget(pWidgetA, A);
03351     setWidget(pWidgetB, B);
03352 
03353     QString type = qElement.attribute( "type", "-1" );
03354     Uml::Association_Type aType = (Uml::Association_Type) type.toInt();
03355 
03356     QString id = qElement.attribute( "xmi.id", "-1" );
03357     bool oldStyleLoad = false;
03358     if (id == "-1") {
03359         // xmi.id not present, ergo either a pure widget association,
03360         // or old (pre-1.2) style:
03361         // Everything is loaded from the AssociationWidget.
03362         // UMLAssociation may or may not be saved - if it is, it's a dummy.
03363         // Create the UMLAssociation if both roles are UML objects;
03364         // else load the info locally.
03365 
03366         if (UMLAssociation::assocTypeHasUMLRepresentation(aType)) {
03367             // lack of an association in our widget AND presence of
03368             // both uml objects for each role clearly identifies this
03369             // as reading in an old-school file. Note it as such, and
03370             // create, and add, the UMLAssociation for this widget.
03371             // Remove this special code when backwards compatibility
03372             // with older files isn't important anymore. -b.t.
03373             UMLObject* umlRoleA = pWidgetA->getUMLObject();
03374             UMLObject* umlRoleB = pWidgetB->getUMLObject();
03375             if (!m_pObject && umlRoleA && umlRoleB)
03376             {
03377                 oldStyleLoad = true; // flag for further special config below
03378                 if (aType == at_Aggregation || aType == at_Composition) {
03379                     kWarning()<<" Old Style save file? swapping roles on association widget"<<this<<endl;
03380                     // We have to swap the A and B widgets to compensate
03381                     // for the long standing bug in LinePath of drawing
03382                     // the diamond at the wrong end which was fixed
03383                     // just before the 1.2 release.
03384                     // The logic here is that the user has understood
03385                     // that the diamond belongs at the SOURCE end of the
03386                     // the association (i.e. at the container, not at the
03387                     // contained), and has compensated for this anomaly
03388                     // by drawing the aggregations/compositions from
03389                     // target to source.
03390                     UMLWidget *tmpWidget = pWidgetA;
03391                     pWidgetA = pWidgetB;
03392                     pWidgetB = tmpWidget;
03393                     setWidget(pWidgetA, A);
03394                     setWidget(pWidgetB, B);
03395                     umlRoleA = pWidgetA->getUMLObject();
03396                     umlRoleB = pWidgetB->getUMLObject();
03397                 }
03398 
03399                 setUMLAssociation(m_umldoc->createUMLAssociation(umlRoleA, umlRoleB, aType));
03400             }
03401         }
03402 
03403         setDoc( qElement.attribute("documentation", "") );
03404         setRoleDoc( qElement.attribute("roleAdoc", ""), A );
03405         setRoleDoc( qElement.attribute("roleBdoc", ""), B );
03406 
03407         // visibilty defaults to Public if it cant set it here..
03408         QString visibilityA = qElement.attribute( "visibilityA", "0");
03409         if (visibilityA.toInt() > 0)
03410           setVisibility( (Uml::Visibility::Value)visibilityA.toInt(), A);
03411 
03412         QString visibilityB = qElement.attribute( "visibilityB", "0");
03413         if (visibilityB.toInt() > 0)
03414           setVisibility( (Uml::Visibility::Value)visibilityB.toInt(), B);
03415 
03416         // Changeability defaults to "Changeable" if it cant set it here..
03417         QString changeabilityA = qElement.attribute( "changeabilityA", "0");
03418         if (changeabilityA.toInt() > 0)
03419             setChangeability( (Changeability_Type)changeabilityA.toInt(), A);
03420 
03421         QString changeabilityB = qElement.attribute( "changeabilityB", "0");
03422         if (changeabilityB.toInt() > 0)
03423             setChangeability( (Changeability_Type)changeabilityB.toInt(), B);
03424 
03425     } else {
03426 
03427         // we should disconnect any prior association (can this happen??)
03428         if (m_pObject && m_pObject->getBaseType() == ot_Association)
03429         {
03430             UMLAssociation *umla = getAssociation();
03431             umla->disconnect(this);
03432             umla->nrof_parent_widgets--;
03433         }
03434 
03435         // New style: The xmi.id is a reference to the UMLAssociation.
03436         // If the UMLObject is not found right now, we try again later
03437         // during the type resolution pass - see activate().
03438         m_nId = STR2ID(id);
03439         UMLObject *myObj = m_umldoc->findObjectById(m_nId);
03440         if (myObj) {
03441             const Uml::Object_Type ot = myObj->getBaseType();
03442             if (ot != ot_Association) {
03443                 setUMLObject(myObj);
03444             } else {
03445                 UMLAssociation * myAssoc = static_cast<UMLAssociation*>(myObj);
03446                 setUMLAssociation(myAssoc);
03447                 if (type == "-1")
03448                     aType = myAssoc->getAssocType();
03449             }
03450         }
03451     }
03452 
03453     setAssocType(aType);
03454 
03455     QString indexa = qElement.attribute( "indexa", "0" );
03456     QString indexb = qElement.attribute( "indexb", "0" );
03457     QString totalcounta = qElement.attribute( "totalcounta", "0" );
03458     QString totalcountb = qElement.attribute( "totalcountb", "0" );
03459     m_role[A].m_nIndex = indexa.toInt();
03460     m_role[B].m_nIndex = indexb.toInt();
03461     m_role[A].m_nTotalCount = totalcounta.toInt();
03462     m_role[B].m_nTotalCount = totalcountb.toInt();
03463 
03464     QString assocclassid = qElement.attribute("assocclass", "");
03465     if (! assocclassid.isEmpty()) {
03466         Uml::IDType acid = STR2ID(assocclassid);
03467         UMLWidget *w = Widget_Utils::findWidget(acid, widgets);
03468         if (w) {
03469             m_pAssocClassWidget = static_cast<ClassifierWidget*>(w);
03470             m_pAssocClassWidget->setClassAssocWidget(this);
03471             // Preparation of the assoc class line is done in activate()
03472             QString aclsegindex = qElement.attribute("aclsegindex", "0");
03473             m_nLinePathSegmentIndex = aclsegindex.toInt();
03474         } else {
03475             kError() << "AssociationWidget::loadFromXMI: "
03476             << "cannot find assocclass " << assocclassid
03477             << endl;
03478         }
03479     }
03480 
03481     //now load child elements
03482     QDomNode node = qElement.firstChild();
03483     QDomElement element = node.toElement();
03484     while( !element.isNull() ) {
03485         QString tag = element.tagName();
03486         if( tag == "linepath" ) {
03487             if( !m_LinePath.loadFromXMI( element ) )
03488                 return false;
03489             else {
03490                 // set up 'old' corner from first point in line
03491                 // as IF this ISNT done, then the subsequent call to
03492                 // widgetMoved will inadvertantly think we have made a
03493                 // big move in the position of the association when we haven't.
03494                 QPoint p = m_LinePath.getPoint(0);
03495                 m_role[A].m_OldCorner.setX(p.x());
03496                 m_role[A].m_OldCorner.setY(p.y());
03497             }
03498         } else if (tag == "floatingtext" ||
03499                    tag == "UML:FloatingTextWidget") {  // for bkwd compatibility
03500             QString r = element.attribute( "role", "-1");
03501             if( r == "-1" )
03502                 return false;
03503             Uml::Text_Role role = (Uml::Text_Role)r.toInt();
03504             FloatingTextWidget *ft = new FloatingTextWidget(m_pView, role, "", Uml::id_Reserved);
03505             if( ! ft->loadFromXMI(element) ) {
03506                 // Most likely cause: The FloatingTextWidget is empty.
03507                 delete ft;
03508                 node = element.nextSibling();
03509                 element = node.toElement();
03510                 continue;
03511             }
03512             // always need this
03513             ft->setLink(this);
03514 
03515             switch( role ) {
03516             case Uml::tr_MultiA:
03517                 m_role[A].m_pMulti = ft;
03518                 if(oldStyleLoad)
03519                     setMulti(m_role[A].m_pMulti->getText(), A);
03520                 break;
03521 
03522             case Uml::tr_MultiB:
03523                 m_role[B].m_pMulti = ft;
03524                 if(oldStyleLoad)
03525                     setMulti(m_role[B].m_pMulti->getText(), B);
03526                 break;
03527 
03528             case Uml::tr_ChangeA:
03529                 m_role[A].m_pChangeWidget = ft;
03530                 break;
03531 
03532             case Uml::tr_ChangeB:
03533                 m_role[B].m_pChangeWidget = ft;
03534                 break;
03535 
03536             case Uml::tr_Name:
03537                 m_pName = ft;
03538                 if(oldStyleLoad)
03539                     setName(m_pName->getText());
03540                 break;
03541 
03542             case Uml::tr_Coll_Message:
03543             case Uml::tr_Coll_Message_Self:
03544                 m_pName = ft;
03545                 ft->setLink(this);
03546                 ft->setActivated();
03547                 if(FloatingTextWidget::isTextValid(ft->getText()))
03548                     ft -> show();
03549                 else
03550                     ft -> hide();
03551                 break;
03552 
03553             case Uml::tr_RoleAName:
03554                 m_role[A].m_pRole = ft;
03555                 setRoleName( ft->getText(), A );
03556                 break;
03557             case Uml::tr_RoleBName:
03558                 m_role[B].m_pRole = ft;
03559                 setRoleName( ft->getText(), B );
03560                 break;
03561             default:
03562                 kDebug() << "AssociationWidget::loadFromXMI(): "
03563                 << "unexpected FloatingTextWidget (textrole "
03564                 << role << ")" << endl;
03565                 delete ft;
03566                 break;
03567             }
03568         }
03569         node = element.nextSibling();
03570         element = node.toElement();
03571     }
03572 
03573     return true;
03574 }
03575 
03576 bool AssociationWidget::loadFromXMI( QDomElement & qElement ) {
03577     const MessageWidgetList& messages = m_pView->getMessageList();
03578     return loadFromXMI( qElement, m_pView->getWidgetList(), &messages );
03579 }
03580 
03581 #include "associationwidget.moc"
KDE Logo
This file is part of the documentation for umbrello Version 3.1.0.
Documentation copyright © 1996-2004 the KDE developers.
Generated on Tue Jun 26 08:07:54 2007 by doxygen 1.4.1 written by Dimitri van Heesch, © 1997-2003