diff --git a/umbrello/toolbarstateassociation.cpp b/umbrello/toolbarstateassociation.cpp index 92afee9ee..4befe3779 100644 --- a/umbrello/toolbarstateassociation.cpp +++ b/umbrello/toolbarstateassociation.cpp @@ -1,332 +1,336 @@ /*************************************************************************** * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * copyright (C) 2004-2014 * * Umbrello UML Modeller Authors * ***************************************************************************/ // own header #include "toolbarstateassociation.h" // app includes #include "assocrules.h" #include "association.h" #include "associationline.h" #include "associationwidget.h" #include "classifierwidget.h" #include "floatingtextwidget.h" #include "debug_utils.h" #include "folder.h" #include "model_utils.h" #include "uml.h" #include "umldoc.h" #include "umlobject.h" #include "umlscene.h" #include "umlview.h" #include "umlwidget.h" // kde includes #include #include /** * Creates a new ToolBarStateAssociation. * * @param umlScene The UMLScene to use. */ ToolBarStateAssociation::ToolBarStateAssociation(UMLScene *umlScene) : ToolBarStatePool(umlScene), m_firstWidget(0), m_associationLine(0) { } /** * Destroys this ToolBarStateAssociation. * Deletes the association line. */ ToolBarStateAssociation::~ToolBarStateAssociation() { cleanAssociation(); } /** * Goes back to the initial state. */ void ToolBarStateAssociation::init() { ToolBarStatePool::init(); cleanAssociation(); } /** * Called when the current tool is changed to use another tool. * Executes base method and cleans the association. */ void ToolBarStateAssociation::cleanBeforeChange() { ToolBarStatePool::cleanBeforeChange(); cleanAssociation(); } /** * Called when a mouse event happened. * It executes the base method and then updates the position of the * association line, if any. */ void ToolBarStateAssociation::mouseMove(QGraphicsSceneMouseEvent* ome) { ToolBarStatePool::mouseMove(ome); if (m_associationLine) { QPointF sp = m_associationLine->line().p1(); m_associationLine->setLine(sp.x(), sp.y(), m_pMouseEvent->scenePos().x(), m_pMouseEvent->scenePos().y()); } } /** * A widget was removed from the UMLScene. * If the widget removed was the current widget, the current widget is set * to 0. * Also, if it was the first widget, the association is cleaned. */ void ToolBarStateAssociation::slotWidgetRemoved(UMLWidget* widget) { ToolBarState::slotWidgetRemoved(widget); if (widget == m_firstWidget) { cleanAssociation(); } } /** * Called when the release event happened on an association. * If the button pressed isn't left button, the association being created is * cleaned. If it is left button, and the first widget is set and is a * classifier widget, it creates an association class. Otherwise, the * association being created is cleaned. */ void ToolBarStateAssociation::mouseReleaseAssociation() { if (m_pMouseEvent->button() != Qt::LeftButton || !m_firstWidget || !m_firstWidget->isClassWidget()) { cleanAssociation(); return; } currentAssociation()->createAssocClassLine( static_cast(m_firstWidget), currentAssociation()->associationLine()->closestSegmentIndex(m_pMouseEvent->scenePos())); m_firstWidget->addAssoc(currentAssociation()); cleanAssociation(); } /** * Called when the release event happened on a widget. * If the button pressed isn't left button, the association is cleaned. If * it is left button, sets the first widget or the second, depending on * whether the first widget is already set or not. */ void ToolBarStateAssociation::mouseReleaseWidget() { if (m_pMouseEvent->button() != Qt::LeftButton) { cleanAssociation(); return; } // TODO In old code in ToolBarState there was a TODO that said: Should not //be called by a Sequence message Association. Here's the check for that, //although I don't know why it is needed, but it seems that it's not needed, //as the old code worked fine without it... if (getAssociationType() == Uml::AssociationType::Seq_Message) { return; } if (!m_firstWidget) { setFirstWidget(); } else { setSecondWidget(); } } /** * Called when the release event happened on an empty space. * Cleans the association. */ void ToolBarStateAssociation::mouseReleaseEmpty() { cleanAssociation(); } /** * Sets the first widget in the association using the current widget. * If the widget can't be associated using the current type of association, * an error is shown and the widget isn't set. * Otherwise, the temporal visual association is created and the mouse * tracking is enabled, so move events will be delivered. */ void ToolBarStateAssociation::setFirstWidget() { UMLWidget* widget = currentWidget(); Uml::AssociationType::Enum type = getAssociationType(); if (!AssocRules::allowAssociation(type, widget)) { //TODO improve error feedback: tell the user what are the valid type of associations for //that widget KMessageBox::error(0, i18n("Incorrect use of associations."), i18n("Association Error")); return; } //set up position QPoint pos; pos.setX(widget->scenePos().x() + (widget->width() / 2)); pos.setY(widget->scenePos().y() + (widget->height() / 2)); //TODO why is this needed? m_pUMLScene->setPos(pos); cleanAssociation(); m_firstWidget = widget; m_associationLine = new QGraphicsLineItem(); m_pUMLScene->addItem(m_associationLine); m_associationLine->setLine(pos.x(), pos.y(), pos.x(), pos.y()); m_associationLine->setPen(QPen(m_pUMLScene->lineColor(), m_pUMLScene->lineWidth(), Qt::DashLine)); m_associationLine->setVisible(true); m_pUMLScene->activeView()->viewport()->setMouseTracking(true); } /** * Sets the second widget in the association using the current widget and * creates the association. * If the association between the two widgets using the current type of * association is illegitimate, an error is shown and the association cancelled. * Otherwise, the association is created and added to the scene, and the tool * is changed to the default tool. * * @todo Why change to the default tool? Shouldn't it better to stay on * association and let the user change with a right click? The tool to * create widgets doesn't change to default after creating a widget */ void ToolBarStateAssociation::setSecondWidget() { Uml::AssociationType::Enum type = getAssociationType(); UMLWidget* widgetA = m_firstWidget; UMLWidget* widgetB = currentWidget(); WidgetBase::WidgetType at = widgetA->baseType(); bool valid = true; if (type == Uml::AssociationType::Generalization) { type = AssocRules::isGeneralisationOrRealisation(widgetA, widgetB); } if (widgetA == widgetB) { valid = AssocRules::allowSelf(type, at); if (valid && type == Uml::AssociationType::Association) { type = Uml::AssociationType::Association_Self; } } else { valid = AssocRules::allowAssociation(type, widgetA, widgetB); } if (valid) { AssociationWidget *temp = AssociationWidget::create(m_pUMLScene, widgetA, type, widgetB); + if (widgetA->changesShape()) + widgetA->updateGeometry(); + if (widgetB->changesShape()) + widgetB->updateGeometry(); FloatingTextWidget *wt = temp->textWidgetByRole(Uml::TextRole::Coll_Message); if (wt) wt->showOperationDialog(); if (addAssociationInViewAndDoc(temp)) { if (type == Uml::AssociationType::Containment) { UMLObject *newContainer = widgetA->umlObject(); UMLObject *objToBeMoved = widgetB->umlObject(); if (newContainer && objToBeMoved) { Model_Utils::treeViewMoveObjectTo(newContainer, objToBeMoved); } } UMLApp::app()->document()->setModified(); } } else { //TODO improve error feedback: tell the user what are the valid type of associations for //the second widget using the first widget KMessageBox::error(0, i18n("Incorrect use of associations."), i18n("Association Error")); } cleanAssociation(); } /** * Returns the association type of this tool. * * @return The association type of this tool. */ Uml::AssociationType::Enum ToolBarStateAssociation::getAssociationType() { Uml::AssociationType::Enum at; switch(getButton()) { case WorkToolBar::tbb_Anchor: at = Uml::AssociationType::Anchor; break; case WorkToolBar::tbb_Association: at = Uml::AssociationType::Association; break; case WorkToolBar::tbb_UniAssociation: at = Uml::AssociationType::UniAssociation; break; case WorkToolBar::tbb_Generalization: at = Uml::AssociationType::Generalization; break; case WorkToolBar::tbb_Composition: at = Uml::AssociationType::Composition; break; case WorkToolBar::tbb_Aggregation: at = Uml::AssociationType::Aggregation; break; case WorkToolBar::tbb_Relationship: at = Uml::AssociationType::Relationship; break; case WorkToolBar::tbb_Dependency: at = Uml::AssociationType::Dependency; break; case WorkToolBar::tbb_Containment: at = Uml::AssociationType::Containment; break; case WorkToolBar::tbb_Seq_Message_Synchronous: case WorkToolBar::tbb_Seq_Combined_Fragment: case WorkToolBar::tbb_Seq_Precondition: case WorkToolBar::tbb_Seq_Message_Asynchronous: at = Uml::AssociationType::Seq_Message; break; case WorkToolBar::tbb_Coll_Message_Synchronous: at = Uml::AssociationType::Coll_Message_Synchronous; break; case WorkToolBar::tbb_Coll_Message_Asynchronous: at = Uml::AssociationType::Coll_Message_Asynchronous; break; case WorkToolBar::tbb_State_Transition: at = Uml::AssociationType::State; break; case WorkToolBar::tbb_Activity_Transition: at = Uml::AssociationType::Activity; break; case WorkToolBar::tbb_Exception: at = Uml::AssociationType::Exception; break; case WorkToolBar::tbb_Category2Parent: at = Uml::AssociationType::Category2Parent; break; case WorkToolBar::tbb_Child2Category: at = Uml::AssociationType::Child2Category; break; default: at = Uml::AssociationType::Unknown; break; } return at; } /** * Adds an AssociationWidget to the association list and creates the * corresponding UMLAssociation in the current UMLDoc. * If the association can't be added, is deleted. * * @param assoc The AssociationWidget to add. * @return True on success */ bool ToolBarStateAssociation::addAssociationInViewAndDoc(AssociationWidget* assoc) { // append in view if (m_pUMLScene->addAssociation(assoc, false)) { // if view went ok, then append in document UMLAssociation *umla = assoc->association(); if (umla) { // association with model representation in UMLDoc Uml::ModelType::Enum m = Model_Utils::convert_DT_MT(m_pUMLScene->type()); UMLDoc *umldoc = UMLApp::app()->document(); umla->setUMLPackage(umldoc->rootFolder(m)); umldoc->addAssociation(umla); } return true; } else { uError() << "cannot addAssocInViewAndDoc(), deleting"; delete assoc; return false; } } /** * Cleans the first widget and the temporal association line, if any. * Both are set to null, and the association line is also deleted. */ void ToolBarStateAssociation::cleanAssociation() { m_firstWidget = 0; delete m_associationLine; m_associationLine = 0; } diff --git a/umbrello/umlwidgets/associationwidget.cpp b/umbrello/umlwidgets/associationwidget.cpp index 1da3ea875..e4a06b7df 100644 --- a/umbrello/umlwidgets/associationwidget.cpp +++ b/umbrello/umlwidgets/associationwidget.cpp @@ -1,4389 +1,4393 @@ /*************************************************************************** * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * copyright (C) 2002-2014 * * Umbrello UML Modeller Authors * ***************************************************************************/ // own header #include "associationwidget.h" // app includes #include "association.h" #include "associationline.h" #include "associationpropertiesdialog.h" #include "associationwidgetpopupmenu.h" #include "assocrules.h" #include "attribute.h" #include "classifier.h" #include "classifierwidget.h" #include "debug_utils.h" #include "dialog_utils.h" #include "docwindow.h" #include "entity.h" #include "floatingtextwidget.h" #include "messagewidget.h" #include "objectwidget.h" #include "operation.h" #include "optionstate.h" #include "uml.h" #include "umldoc.h" #include "umlscene.h" #include "umlview.h" #include "umlwidget.h" #include "widget_utils.h" #include "instance.h" #include "instanceattribute.h" // kde includes #if QT_VERSION < 0x050000 #include #include #endif #include // qt includes #if QT_VERSION >= 0x050000 #include #include #endif #include #include #include // system includes #include #define DBG_AW() DEBUG(QLatin1String("AssociationWidget")) DEBUG_REGISTER_DISABLED(AssociationWidget) using namespace Uml; /** * Constructor is private because the static create() methods shall * be used for constructing AssociationWidgets. * * @param scene The parent view of this widget. */ AssociationWidget::AssociationWidget(UMLScene *scene) : WidgetBase(scene, WidgetBase::wt_Association), m_positions_len(0), m_activated(false), m_unNameLineSegment(-1), m_nLinePathSegmentIndex(-1), m_pAssocClassLine(0), m_pAssocClassLineSel0(0), m_pAssocClassLineSel1(0), m_associationLine(new AssociationLine(this)), m_associationClass(0), m_associationType(Uml::AssociationType::Association), m_nameWidget(0) { // propagate line color and width set by base class constructor // which does not call the virtual methods from this class. setLineColor(lineColor()); setLineWidth(lineWidth()); // floating text widgets objects owned by this association m_role[RoleType::A].changeabilityWidget = 0; m_role[RoleType::B].changeabilityWidget = 0; m_role[RoleType::A].multiplicityWidget = 0; m_role[RoleType::B].multiplicityWidget = 0; m_role[RoleType::A].roleWidget = 0; m_role[RoleType::B].roleWidget = 0; m_role[RoleType::A].umlWidget = 0; m_role[RoleType::B].umlWidget = 0; // associationwidget attributes m_role[RoleType::A].m_WidgetRegion = Uml::Region::Error; m_role[RoleType::B].m_WidgetRegion = Uml::Region::Error; m_role[RoleType::A].m_nIndex = 0; m_role[RoleType::B].m_nIndex = 0; m_role[RoleType::A].m_nTotalCount = 0; m_role[RoleType::B].m_nTotalCount = 0; m_role[RoleType::A].visibility = Uml::Visibility::Public; m_role[RoleType::B].visibility = Uml::Visibility::Public; m_role[RoleType::A].changeability = Uml::Changeability::Changeable; m_role[RoleType::B].changeability = Uml::Changeability::Changeable; setFlag(QGraphicsLineItem::ItemIsSelectable); setAcceptHoverEvents(true); } /** * This constructor is really only for loading from XMI, otherwise it * should not be allowed as it creates an incomplete associationwidget. * * @param scene The parent view of this widget. */ AssociationWidget* AssociationWidget::create(UMLScene *scene) { AssociationWidget* instance = new AssociationWidget(scene); return instance; } /** * Preferred constructor (static factory method.) * * @param scene The parent view of this widget. * @param pWidgetA Pointer to the role A widget for the association. * @param assocType The AssociationType::Enum for this association. * @param pWidgetB Pointer to the role B widget for the association. * @param umlobject Pointer to the underlying UMLObject (if applicable.) */ AssociationWidget* AssociationWidget::create (UMLScene *scene, UMLWidget* pWidgetA, Uml::AssociationType::Enum assocType, UMLWidget* pWidgetB, UMLObject *umlobject /* = 0 */) { AssociationWidget* instance = new AssociationWidget(scene); if (umlobject) { instance->setUMLObject(umlobject); } else { // set up UMLAssociation obj if assoc is represented and both roles are UML objects if (Uml::AssociationType::hasUMLRepresentation(assocType)) { UMLObject* umlRoleA = pWidgetA->umlObject(); UMLObject* umlRoleB = pWidgetB->umlObject(); if (umlRoleA != 0 && umlRoleB != 0) { bool swap; // This is not correct. We could very easily have more than one // of the same type of association between the same two objects. // Just create the association. This search should have been // done BEFORE creation of the widget, if it mattered to the code. // But lets leave check in here for the time being so that debugging // output is shown, in case there is a collision with code elsewhere. UMLDoc *doc = UMLApp::app()->document(); UMLAssociation *myAssoc = doc->findAssociation(assocType, umlRoleA, umlRoleB, &swap); if (myAssoc != 0) { switch (assocType) { case Uml::AssociationType::Generalization: case Uml::AssociationType::Dependency: case Uml::AssociationType::Association_Self: case Uml::AssociationType::Coll_Message_Self: case Uml::AssociationType::Seq_Message_Self: case Uml::AssociationType::Containment: case Uml::AssociationType::Realization: DBG_AW() << "Ignoring second construction of same assoctype " << assocType << " between " << umlRoleA->name() << " and " << umlRoleB->name(); break; default: DBG_AW() << "constructing a similar or exact same assoctype " << assocType << " between " << umlRoleA->name() << " and " << umlRoleB->name() << "as an already existing assoc (swap=" << swap << ")"; // now, just create a new association anyways myAssoc = 0; break; } } if (myAssoc == 0) { myAssoc = new UMLAssociation(assocType, umlRoleA, umlRoleB); // CHECK: myAssoc is not yet inserted at any parent UMLPackage - // need to check carefully that all callers do this, lest it be // orphaned. // ToolBarStateAssociation::addAssociationInViewAndDoc() is // okay in this regard. } instance->setUMLAssociation(myAssoc); } } } instance->setWidgetForRole(pWidgetA, RoleType::A); instance->setWidgetForRole(pWidgetB, RoleType::B); instance->setAssociationType(assocType); instance->calculateEndingPoints(); instance->associationLine()->calculateInitialEndPoints(); instance->associationLine()->reconstructSymbols(); //The AssociationWidget is set to Activated because it already has its side widgets instance->setActivated(true); // sync UML meta-data to settings here instance->mergeAssociationDataIntoUMLRepresentation(); // Collaboration messages need a name label because it's that // which lets operator== distinguish them, which in turn // permits us to have more than one message between two objects. if (instance->isCollaboration()) { // Create a temporary name to bring on setName() int collabID = instance->m_scene->generateCollaborationId(); instance->setName(QLatin1Char('m') + QString::number(collabID)); } return instance; } /** * Destructor. */ AssociationWidget::~AssociationWidget() { cleanup(); delete m_associationLine; } /** * Overriding the method from WidgetBase because we need to do * something extra in case this AssociationWidget represents * an attribute of a classifier. */ void AssociationWidget::setUMLObject(UMLObject *obj) { WidgetBase::setUMLObject(obj); if (obj == 0) return; UMLClassifier *klass = 0; UMLAttribute *attr = 0; UMLEntity *ent = 0; const UMLObject::ObjectType ot = obj->baseType(); switch (ot) { case UMLObject::ot_Association: setUMLAssociation(obj->asUMLAssociation()); break; case UMLObject::ot_Operation: setOperation(obj->asUMLOperation()); break; case UMLObject::ot_Attribute: klass = obj->umlParent()->asUMLClassifier(); connect(klass, SIGNAL(attributeRemoved(UMLClassifierListItem*)), this, SLOT(slotClassifierListItemRemoved(UMLClassifierListItem*))); attr = obj->asUMLAttribute(); connect(attr, SIGNAL(attributeChanged()), this, SLOT(slotAttributeChanged())); break; case UMLObject::ot_EntityAttribute: ent = obj->umlParent()->asUMLEntity(); connect(ent, SIGNAL(entityAttributeRemoved(UMLClassifierListItem*)), this, SLOT(slotClassifierListItemRemoved(UMLClassifierListItem*))); break; case UMLObject::ot_ForeignKeyConstraint: ent = obj->umlParent()->asUMLEntity(); connect(ent, SIGNAL(entityConstraintRemoved(UMLClassifierListItem*)), this, SLOT(slotClassifierListItemRemoved(UMLClassifierListItem*))); break; case UMLObject::ot_InstanceAttribute: connect(obj->umlParent(), SIGNAL(attributeRemoved()), this, SLOT(slotClassifierListItemRemoved())); attr = obj->asUMLInstanceAttribute(); connect(attr, SIGNAL(attributeChanged()), this, SLOT(slotAttributeChanged())); break; default: uError() << "cannot associate UMLObject of type " << UMLObject::toString(ot); break; } } /** * Set all 'owned' child widgets to this font. */ void AssociationWidget::lwSetFont (QFont font) { if (m_nameWidget) { m_nameWidget->setFont(font); } if (m_role[RoleType::A].roleWidget) { m_role[RoleType::A].roleWidget->setFont(font); } if (m_role[RoleType::B].roleWidget) { m_role[RoleType::B].roleWidget->setFont(font); } if (m_role[RoleType::A].multiplicityWidget) { m_role[RoleType::A].multiplicityWidget->setFont(font); } if (m_role[RoleType::B].multiplicityWidget) { m_role[RoleType::B].multiplicityWidget->setFont(font); } if (m_role[RoleType::A].changeabilityWidget) m_role[RoleType::A].changeabilityWidget->setFont(font); if (m_role[RoleType::B].changeabilityWidget) m_role[RoleType::B].changeabilityWidget->setFont(font); } /** * Overrides operation from LinkWidget. * Required by FloatingTextWidget. * @todo Move to LinkWidget. */ UMLClassifier *AssociationWidget::operationOwner() { Uml::RoleType::Enum role = (isCollaboration() ? Uml::RoleType::B : Uml::RoleType::A); UMLObject *o = widgetForRole(role)->umlObject(); if (!o) { return 0; } UMLClassifier *c = o->asUMLClassifier(); if (!c) { uError() << "widgetForRole(" << role << ") is not a classifier"; } return c; } /** * Implements operation from LinkWidget. * Motivated by FloatingTextWidget. */ UMLOperation *AssociationWidget::operation() { return m_umlObject->asUMLOperation(); } /** * Implements operation from LinkWidget. * Motivated by FloatingTextWidget. */ void AssociationWidget::setOperation(UMLOperation *op) { if (m_umlObject) disconnect(m_umlObject, SIGNAL(modified()), m_nameWidget, SLOT(setMessageText())); m_umlObject = op; if (m_umlObject) connect(m_umlObject, SIGNAL(modified()), m_nameWidget, SLOT(setMessageText())); if (m_nameWidget) m_nameWidget->setMessageText(); } /** * Overrides operation from LinkWidget. * Required by FloatingTextWidget. */ QString AssociationWidget::customOpText() { return name(); } /** * Overrides operation from LinkWidget. * Required by FloatingTextWidget. */ void AssociationWidget::setCustomOpText(const QString &opText) { setName(opText); } /** * Calls setTextPosition on all the labels. * Overrides operation from LinkWidget. */ void AssociationWidget::resetTextPositions() { if (m_role[RoleType::A].multiplicityWidget) { setTextPosition(TextRole::MultiA); } if (m_role[RoleType::B].multiplicityWidget) { setTextPosition(Uml::TextRole::MultiB); } if (m_role[RoleType::A].changeabilityWidget) { setTextPosition(Uml::TextRole::ChangeA); } if (m_role[RoleType::B].changeabilityWidget) { setTextPosition(Uml::TextRole::ChangeB); } if (m_nameWidget) { setTextPosition(Uml::TextRole::Name); } if (m_role[RoleType::A].roleWidget) { setTextPosition(Uml::TextRole::RoleAName); } if (m_role[RoleType::B].roleWidget) { setTextPosition(Uml::TextRole::RoleBName); } } /** * Overrides operation from LinkWidget. * Required by FloatingTextWidget. * * @param ft The text widget which to update. */ void AssociationWidget::setMessageText(FloatingTextWidget *ft) { if (isCollaboration()) { ft->setSequenceNumber(m_SequenceNumber); if (m_umlObject != 0) { ft->setText(operationText(m_scene)); } else { ft->setText(name()); } } else { ft->setText(name()); } } /** * Sets the text of the given FloatingTextWidget. * Overrides operation from LinkWidget. * Required by FloatingTextWidget. */ void AssociationWidget::setText(FloatingTextWidget *ft, const QString &text) { Uml::TextRole::Enum role = ft->textRole(); switch (role) { case Uml::TextRole::Name: setName(text); break; case Uml::TextRole::RoleAName: setRoleName(text, RoleType::A); break; case Uml::TextRole::RoleBName: setRoleName(text, RoleType::B); break; case Uml::TextRole::MultiA: setMultiplicity(text, RoleType::A); break; case Uml::TextRole::MultiB: setMultiplicity(text, RoleType::B); break; default: uWarning() << "Unhandled TextRole: " << Uml::TextRole::toString(role); break; } } /** * Shows the association properties dialog and updates the * corresponding texts if its execution is successful. */ bool AssociationWidget::showPropertiesDialog() { bool result = false; UMLApp::app()->docWindow()->updateDocumentation(); QPointer dlg = new AssociationPropertiesDialog(static_cast(m_scene->activeView()), this); if (dlg->exec()) { UMLApp::app()->docWindow()->showDocumentation(this, true); result = true; } delete dlg; return result; } /** * Overrides operation from LinkWidget. * Required by FloatingTextWidget. */ QString AssociationWidget::lwOperationText() { return name(); } /** * Overrides operation from LinkWidget. * Required by FloatingTextWidget. * * @return classifier */ UMLClassifier* AssociationWidget::lwClassifier() { UMLObject *o = widgetForRole(RoleType::B)->umlObject(); UMLClassifier *c = o->asUMLClassifier(); return c; } /** * Overrides operation from LinkWidget. * Required by FloatingTextWidget. * * @param op The new operation string to set. */ void AssociationWidget::setOperationText(const QString &op) { if (!op.isEmpty()) { setName(op); } } /** * Calculates the m_unNameLineSegment value according to the new * NameText topleft corner PT. * It iterates through all AssociationLine's segments and for each one * calculates the sum of PT's distance to the start point + PT's * distance to the end point. The segment with the smallest sum will * be the RoleTextSegment (if this segment moves then the RoleText * will move with it). It sets m_unNameLineSegment to the start point * of the chosen segment. * * Overrides operation from LinkWidget (i.e. this method is also * required by FloatingTextWidget.) */ void AssociationWidget::calculateNameTextSegment() { if (!m_nameWidget) { return; } //changed to use the middle of the text //i think this will give a better result. //never know what sort of lines people come up with //and text could be long to give a false reading qreal xt = m_nameWidget->x(); qreal yt = m_nameWidget->y(); xt += m_nameWidget->width() / 2; yt += m_nameWidget->height() / 2; int size = m_associationLine->count(); //sum of length(PTP1) and length(PTP2) qreal total_length = 0; qreal smallest_length = 0; for (int i = 0; i < size - 1; ++i) { QPointF pi = m_associationLine->point( i ); QPointF pj = m_associationLine->point( i+1 ); qreal xtiDiff = xt - pi.x(); qreal xtjDiff = xt - pj.x(); qreal ytiDiff = yt - pi.y(); qreal ytjDiff = yt - pj.y(); total_length = sqrt( double(xtiDiff * xtiDiff + ytiDiff * ytiDiff) ) + sqrt( double(xtjDiff * xtjDiff + ytjDiff * ytjDiff) ); //this gives the closest point if (total_length < smallest_length || i == 0) { smallest_length = total_length; m_unNameLineSegment = i; } } } /** * Returns the UMLAssociation representation of this object. * * @return Pointer to the UMLAssociation that is represented by * this AsociationWidget. */ UMLAssociation* AssociationWidget::association() const { if (m_umlObject == 0 || m_umlObject->baseType() != UMLObject::ot_Association) return 0; return m_umlObject->asUMLAssociation(); } /** * Returns the UMLAttribute representation of this object. * * @return Pointer to the UMLAttribute that is represented by * this AsociationWidget. */ UMLAttribute* AssociationWidget::attribute() const { if (m_umlObject == 0) return 0; UMLObject::ObjectType ot = m_umlObject->baseType(); if (ot != UMLObject::ot_Attribute && ot != UMLObject::ot_EntityAttribute && ot != UMLObject::ot_InstanceAttribute) return 0; return m_umlObject->asUMLAttribute(); } #if 0 //:TODO: /** * Overrides the assignment operator. */ AssociationWidget& AssociationWidget::operator=(const AssociationWidget& other) { *m_associationLine = *other.m_associationLine; if (other.m_nameWidget) { m_nameWidget = new FloatingTextWidget(m_scene); *m_nameWidget = *(other.m_nameWidget); } else { m_nameWidget = 0; } for (unsigned r = (unsigned)A; r <= (unsigned)B; ++r) { WidgetRole& lhs = m_role[r]; const WidgetRole& rhs = other.m_role[r]; lhs.m_nIndex = rhs.m_nIndex; lhs.m_nTotalCount = rhs.m_nTotalCount; if (rhs.multiplicityWidget) { lhs.multiplicityWidget = new FloatingTextWidget(m_scene); *(lhs.multiplicityWidget) = *(rhs.multiplicityWidget); } else { lhs.multiplicityWidget = 0; } if (rhs.roleWidget) { lhs.roleWidget = new FloatingTextWidget(m_scene); *(lhs.roleWidget) = *(rhs.roleWidget); } else { lhs.roleWidget = 0; } if (rhs.changeabilityWidget) { lhs.changeabilityWidget = new FloatingTextWidget(m_scene); *(lhs.changeabilityWidget) = *(rhs.changeabilityWidget); } else { lhs.changeabilityWidget = 0; } lhs.umlWidget = rhs.umlWidget; lhs.m_WidgetRegion = rhs.m_WidgetRegion; } m_activated = other.m_activated; m_unNameLineSegment = other.m_unNameLineSegment; setUMLAssociation(other.association()); setSelected(other.isSelected()); return *this; } #endif //:TODO: /** * Overrides the equality test operator. */ bool AssociationWidget::operator==(const AssociationWidget& other) const { if (this == &other) return true; // if no model representation exists, then the widgets are not equal if (association() == 0 && other.association() == 0) return false; if (!m_umlObject || !other.m_umlObject ) { if (!other.m_umlObject && m_umlObject) return false; if (other.m_umlObject && !m_umlObject) return false; } else if (m_umlObject != other.m_umlObject) return false; if (associationType() != other.associationType()) return false; if (widgetIDForRole(RoleType::A) != other.widgetIDForRole(RoleType::A)) return false; if (widgetIDForRole(RoleType::B) != other.widgetIDForRole(RoleType::B)) return false; if (widgetForRole(RoleType::A)->isObjectWidget() && other.widgetForRole(RoleType::A)->isObjectWidget()) { ObjectWidget *ownA = static_cast(widgetForRole(RoleType::A)); ObjectWidget *otherA = static_cast(other.widgetForRole(RoleType::A)); if (ownA->localID() != otherA->localID()) return false; } if (widgetForRole(RoleType::B)->isObjectWidget() && other.widgetForRole(RoleType::B)->isObjectWidget()) { ObjectWidget *ownB = static_cast(widgetForRole(RoleType::B)); ObjectWidget *otherB = static_cast(other.widgetForRole(RoleType::B)); if (ownB->localID() != otherB->localID()) return false; } // Two objects in a collaboration can have multiple messages between each other. // Here we depend on the messages having names, and the names must be different. // That is the reason why collaboration messages have strange initial names like // "m29997" or similar. return (name() == other.name()); } /** * Overrides the != operator. */ bool AssociationWidget::operator!=(AssociationWidget& other) const { return !(*this == other); } /** * Returns a pointer to the association widget's line path. */ AssociationLine* AssociationWidget::associationLine() const { return m_associationLine; } /** * Activates the AssociationWidget after a load. * * @return true for success */ bool AssociationWidget::activate() { if (m_umlObject == 0 && AssociationType::hasUMLRepresentation(m_associationType)) { UMLObject *myObj = umlDoc()->findObjectById(m_nId); if (myObj == 0) { uError() << "cannot find UMLObject " << Uml::ID::toString(m_nId); return false; } else { const UMLObject::ObjectType ot = myObj->baseType(); if (ot == UMLObject::ot_Association) { UMLAssociation * myAssoc = myObj->asUMLAssociation(); setUMLAssociation(myAssoc); } else { setUMLObject(myObj); setAssociationType(m_associationType); } } } if (m_activated) return true; Uml::AssociationType::Enum type = associationType(); if (m_role[RoleType::A].umlWidget == 0) setWidgetForRole(m_scene->findWidget(widgetIDForRole(RoleType::A)), RoleType::A); if (m_role[RoleType::B].umlWidget == 0) setWidgetForRole(m_scene->findWidget(widgetIDForRole(RoleType::B)), RoleType::B); if (!m_role[RoleType::A].umlWidget || !m_role[RoleType::B].umlWidget) { DEBUG(DBG_SRC) << "Cannot make association!"; return false; } calculateEndingPoints(); if (AssocRules::allowRole(type)) { for (unsigned r = RoleType::A; r <= RoleType::B; ++r) { WidgetRole& robj = m_role[r]; if (robj.roleWidget == 0) continue; robj.roleWidget->setLink(this); TextRole::Enum tr = (r == RoleType::A ? TextRole::RoleAName : TextRole::RoleBName); robj.roleWidget->setTextRole(tr); Uml::Visibility::Enum vis = visibility(Uml::RoleType::fromInt(r)); robj.roleWidget->setPreText(Uml::Visibility::toString(vis, true)); if (FloatingTextWidget::isTextValid(robj.roleWidget->text())) robj.roleWidget->show(); else robj.roleWidget->hide(); if (m_scene->type() == DiagramType::Collaboration) robj.roleWidget->setUMLObject(robj.umlWidget->umlObject()); robj.roleWidget->activate(); } } if (m_nameWidget != 0) { m_nameWidget->setLink(this); m_nameWidget->setTextRole(calculateNameType(TextRole::Name)); if (FloatingTextWidget::isTextValid(m_nameWidget->text())) { m_nameWidget->show(); } else { m_nameWidget->hide(); } m_nameWidget->activate(); calculateNameTextSegment(); } for (unsigned r = RoleType::A; r <= RoleType::B; ++r) { WidgetRole& robj = m_role[r]; FloatingTextWidget* pMulti = robj.multiplicityWidget; if (pMulti != 0 && AssocRules::allowMultiplicity(type, robj.umlWidget->baseType())) { pMulti->setLink(this); TextRole::Enum tr = (r == RoleType::A ? TextRole::MultiA : TextRole::MultiB); pMulti->setTextRole(tr); if (FloatingTextWidget::isTextValid(pMulti->text())) pMulti->show(); else pMulti->hide(); pMulti->activate(); } FloatingTextWidget* pChangeWidget = robj.changeabilityWidget; if (pChangeWidget != 0) { pChangeWidget->setLink(this); TextRole::Enum tr = (r == RoleType::A ? TextRole::ChangeA : TextRole::ChangeB); pChangeWidget->setTextRole(tr); if (FloatingTextWidget::isTextValid(pChangeWidget->text())) pChangeWidget->show(); else pChangeWidget->hide (); pChangeWidget->activate(); } } // Prepare the association class line if needed. if (m_associationClass && !m_pAssocClassLine) { createAssocClassLine(); } m_activated = true; return true; } /** * Set the widget of the given role. * Add this AssociationWidget at the widget. * If this AssociationWidget has an underlying UMLAssociation then set * the widget's underlying UMLObject at the UMLAssociation's role object. * * @param widget Pointer to the UMLWidget. * @param role Role for which to set the widget. */ void AssociationWidget::setWidgetForRole(UMLWidget* widget, Uml::RoleType::Enum role) { m_role[role].umlWidget = widget; if (widget) { m_role[role].umlWidget->addAssoc(this); if (m_umlObject && m_umlObject->baseType() == UMLObject::ot_Association) association()->setObject(widget->umlObject(), role); } } /** * Return the multiplicity FloatingTextWidget widget of the given role. * * @return Pointer to the multiplicity FloatingTextWidget object. */ FloatingTextWidget* AssociationWidget::multiplicityWidget(Uml::RoleType::Enum role) const { return m_role[role].multiplicityWidget; } /** * Read property of FloatingTextWidget* m_nameWidget. * * @return Pointer to the FloatingTextWidget name widget. */ FloatingTextWidget* AssociationWidget::nameWidget() const { return m_nameWidget; } /** * Return the given role's FloatingTextWidget object. * * @return Pointer to the role's FloatingTextWidget widget. */ FloatingTextWidget* AssociationWidget::roleWidget(Uml::RoleType::Enum role) const { return m_role[role].roleWidget; } /** * Return the given role's changeability FloatingTextWidget widget. */ FloatingTextWidget* AssociationWidget::changeabilityWidget(Uml::RoleType::Enum role) const { return m_role[role].changeabilityWidget; } /** * Return the FloatingTextWidget object indicated by the given TextRole::Enum. * * @return Pointer to the text role's FloatingTextWidget widget. */ FloatingTextWidget* AssociationWidget::textWidgetByRole(Uml::TextRole::Enum tr) const { switch (tr) { case Uml::TextRole::MultiA: return m_role[RoleType::A].multiplicityWidget; case Uml::TextRole::MultiB: return m_role[RoleType::B].multiplicityWidget; case Uml::TextRole::Name: case Uml::TextRole::Coll_Message: return m_nameWidget; case Uml::TextRole::RoleAName: return m_role[RoleType::A].roleWidget; case Uml::TextRole::RoleBName: return m_role[RoleType::B].roleWidget; case Uml::TextRole::ChangeA: return m_role[RoleType::A].changeabilityWidget; case Uml::TextRole::ChangeB: return m_role[RoleType::B].changeabilityWidget; default: break; } return 0; } /** * Returns the m_nameWidget's text. * * @return Text of the FloatingTextWidget name widget. */ QString AssociationWidget::name() const { if (m_nameWidget == 0) return QString(); return m_nameWidget->text(); } /** * Sets the text in the FloatingTextWidget widget representing the Name * of this association. */ void AssociationWidget::setName(const QString &strName) { // set attribute of UMLAssociation associated with this associationwidget UMLAssociation *umla = association(); if (umla) umla->setName(strName); bool newLabel = false; if (!m_nameWidget) { // Don't construct the FloatingTextWidget if the string is empty. if (! FloatingTextWidget::isTextValid(strName)) return; newLabel = true; m_nameWidget = new FloatingTextWidget(m_scene, calculateNameType(Uml::TextRole::Name), strName); m_nameWidget->setParentItem(this); m_nameWidget->setLink(this); } else { m_nameWidget->setText(strName); if (! FloatingTextWidget::isTextValid(strName)) { //m_nameWidget->hide(); m_scene->removeWidget(m_nameWidget); m_nameWidget = 0; return; } } setTextPosition(Uml::TextRole::Name); if (newLabel) { m_nameWidget->setActivated(); m_scene->addFloatingTextWidget(m_nameWidget); } m_nameWidget->show(); } void AssociationWidget::setStereotype(const QString &stereo) { UMLAssociation *umlassoc = association(); if (umlassoc) { umlassoc->setStereotype(stereo); if (!m_nameWidget) { QString text = umlassoc->stereotype(true); // Don't construct the FloatingTextWidget if the string is empty. if (! FloatingTextWidget::isTextValid(text)) return; m_nameWidget = new FloatingTextWidget(m_scene, calculateNameType(Uml::TextRole::Name), text); m_nameWidget->setParentItem(this); m_nameWidget->setLink(this); m_nameWidget->activate(); setTextPosition(calculateNameType(Uml::TextRole::Name)); } else { m_nameWidget->setText(umlassoc->stereotype(true)); } } else { uDebug() << "not setting " << stereo << " because association is NULL"; } } /** * Return the given role's FloatingTextWidget widget text. * * @return The name set at the FloatingTextWidget. */ QString AssociationWidget::roleName(Uml::RoleType::Enum role) const { if (m_role[role].roleWidget == 0) return QString(); return m_role[role].roleWidget->text(); } /** * Sets the text to the FloatingTextWidget that display the Role text of this * association. * For this function to work properly, the associated widget * should already be set. */ void AssociationWidget::setRoleName(const QString &strRole, Uml::RoleType::Enum role) { Uml::AssociationType::Enum type = associationType(); //if the association is not supposed to have a Role FloatingTextWidget if (!AssocRules::allowRole(type)) { return; } TextRole::Enum tr = (role == RoleType::A ? TextRole::RoleAName : TextRole::RoleBName); setFloatingText(tr, strRole, m_role[role].roleWidget); if (m_role[role].roleWidget) { Uml::Visibility::Enum vis = visibility(role); if (FloatingTextWidget::isTextValid(m_role[role].roleWidget->text())) { m_role[role].roleWidget->setPreText(Uml::Visibility::toString(vis, true)); //m_role[role].roleWidget->show(); } else { m_role[role].roleWidget->setPreText(QString()); //m_role[role].roleWidget->hide(); } } // set attribute of UMLAssociation associated with this associationwidget if (m_umlObject && m_umlObject->baseType() == UMLObject::ot_Association) association()->setRoleName(strRole, role); } /** * Set the documentation on the given role. */ void AssociationWidget::setRoleDocumentation(const QString &doc, Uml::RoleType::Enum role) { if (m_umlObject && m_umlObject->baseType() == UMLObject::ot_Association) association()->setRoleDoc(doc, role); else m_role[role].roleDocumentation = doc; } /** * Returns the given role's documentation. */ QString AssociationWidget::roleDocumentation(Uml::RoleType::Enum role) const { if (m_umlObject == 0 || m_umlObject->baseType() != UMLObject::ot_Association) return QString(); UMLAssociation *umla = m_umlObject->asUMLAssociation(); return umla->getRoleDoc(role); } /** * Change, create, or delete the FloatingTextWidget indicated by the given TextRole::Enum. * * @param tr TextRole::Enum of the FloatingTextWidget to change or create. * @param text Text string that controls the action: * If empty and ft is NULL then setFloatingText() is a no-op. * If empty and ft is non-NULL then the existing ft is deleted. * If non-empty and ft is NULL then a new FloatingTextWidget is created * and returned in ft with the text set. * If non-empty and ft is non-NULL then the existing ft text is modified. * @param ft Reference to the pointer to FloatingTextWidget to change or create. * On creation/deletion, the pointer value will be changed. */ void AssociationWidget::setFloatingText(Uml::TextRole::Enum role, const QString &text, FloatingTextWidget* &ft) { if (! FloatingTextWidget::isTextValid(text)) { if (ft) { // Remove preexisting FloatingTextWidget m_scene->removeWidget(ft); // physically deletes ft ft = 0; } return; } if (ft == 0) { ft = new FloatingTextWidget(m_scene, role, text); ft->setParentItem(this); ft->setLink(this); ft->activate(); setTextPosition(role); m_scene->addFloatingTextWidget(ft); } else { bool newLabel = ft->text().isEmpty(); ft->setText(text); if (newLabel) setTextPosition(role); } ft->show(); } /** * Return the given role's multiplicity text. * * @return Text of the given role's multiplicity widget. */ QString AssociationWidget::multiplicity(Uml::RoleType::Enum role) const { if (m_role[role].multiplicityWidget == 0) return QString(); return m_role[role].multiplicityWidget->text(); } /** * Sets the text in the FloatingTextWidget representing the multiplicity * at the given side of the association. */ void AssociationWidget::setMultiplicity(const QString& text, Uml::RoleType::Enum role) { TextRole::Enum tr = (role == RoleType::A ? TextRole::MultiA : TextRole::MultiB); setFloatingText(tr, text, m_role[role].multiplicityWidget); if (m_umlObject && m_umlObject->baseType() == UMLObject::ot_Association) association()->setMultiplicity(text, role); } /** * Gets the visibility on the given role of the association. */ Visibility::Enum AssociationWidget::visibility(Uml::RoleType::Enum role) const { const UMLAssociation *assoc = association(); if (assoc) return assoc->visibility(role); const UMLAttribute *attr = attribute(); if (attr) return attr->visibility(); return m_role[role].visibility; } /** * Sets the visibility on the given role of the association. */ void AssociationWidget::setVisibility(Visibility::Enum value, Uml::RoleType::Enum role) { if (value != visibility(role) && m_umlObject) { // update our model object const UMLObject::ObjectType ot = m_umlObject->baseType(); if (ot == UMLObject::ot_Association) { UMLAssociation *a = association(); a->blockSignals(true); a->setVisibility(value, role); a->blockSignals(false); } else if (ot == UMLObject::ot_Attribute) { UMLAttribute *a = attribute(); a->blockSignals(true); a->setVisibility(value); a->blockSignals(false); } } m_role[role].visibility = value; // update role pre-text attribute as appropriate if (m_role[role].roleWidget) { QString scopeString = Visibility::toString(value, true); m_role[role].roleWidget->setPreText(scopeString); } } /** * Gets the changeability on the given end of the Association. */ Uml::Changeability::Enum AssociationWidget::changeability(Uml::RoleType::Enum role) const { if (m_umlObject == 0 || m_umlObject->baseType() != UMLObject::ot_Association) return m_role[role].changeability; UMLAssociation *umla = m_umlObject->asUMLAssociation(); return umla->changeability(role); } /** * Sets the changeability on the given end of the Association. */ void AssociationWidget::setChangeability(Uml::Changeability::Enum value, Uml::RoleType::Enum role) { if (value == changeability(role)) return; QString changeString = Uml::Changeability::toString(value); if (m_umlObject && m_umlObject->baseType() == UMLObject::ot_Association) // update our model object association()->setChangeability(value, role); m_role[role].changeability = value; // update our string representation setChangeWidget(changeString, role); } /** * For internal purposes only. * Other classes/users should use setChangeability() instead. */ void AssociationWidget::setChangeWidget(const QString &strChangeWidget, Uml::RoleType::Enum role) { bool newLabel = false; TextRole::Enum tr = (role == RoleType::A ? TextRole::ChangeA : TextRole::ChangeB); if (!m_role[role].changeabilityWidget) { // Don't construct the FloatingTextWidget if the string is empty. if (strChangeWidget.isEmpty()) return; newLabel = true; m_role[role].changeabilityWidget = new FloatingTextWidget(m_scene, tr, strChangeWidget); m_role[role].changeabilityWidget->setParentItem(this); m_role[role].changeabilityWidget->setLink(this); m_scene->addFloatingTextWidget(m_role[role].changeabilityWidget); m_role[role].changeabilityWidget->setPreText(QLatin1String("{")); // all types have this m_role[role].changeabilityWidget->setPostText(QLatin1String("}")); // all types have this } else { if (m_role[role].changeabilityWidget->text().isEmpty()) { newLabel = true; } m_role[role].changeabilityWidget->setText(strChangeWidget); } m_role[role].changeabilityWidget->setActivated(); if (newLabel) { setTextPosition(tr); } if (FloatingTextWidget::isTextValid(m_role[role].changeabilityWidget->text())) m_role[role].changeabilityWidget->show(); else m_role[role].changeabilityWidget->hide(); } /** * Returns true if the line path starts at the given widget. */ bool AssociationWidget::linePathStartsAt(const UMLWidget* widget) { //:TODO: // QPointF lpStart = m_associationLine->point(0); // int startX = lpStart.x(); // int startY = lpStart.y(); // int wX = widget->x(); // int wY = widget->y(); // int wWidth = widget->width(); // int wHeight = widget->height(); // bool result = (startX >= wX && startX <= wX + wWidth && // startY >= wY && startY <= wY + wHeight); // return result; bool result = widget->contains(m_associationLine->point(0)); DEBUG(DBG_SRC) << "widget=" << widget->name() << " / result=" << result; return result; } /** * This function calculates which role should be set for the m_nameWidget FloatingTextWidget. */ Uml::TextRole::Enum AssociationWidget::calculateNameType(Uml::TextRole::Enum defaultRole) { TextRole::Enum result = defaultRole; if (m_scene->type() == DiagramType::Collaboration) { if (m_role[RoleType::A].umlWidget == m_role[RoleType::B].umlWidget) { result = TextRole::Coll_Message;//for now same as other Coll_Message } else { result = TextRole::Coll_Message; } } else if (m_scene->type() == DiagramType::Sequence) { if (m_role[RoleType::A].umlWidget == m_role[RoleType::B].umlWidget) { result = TextRole::Seq_Message_Self; } else { result = TextRole::Seq_Message; } } return result; } /** * Gets the given role widget. * * @return Pointer to the role's UMLWidget. */ UMLWidget* AssociationWidget::widgetForRole(Uml::RoleType::Enum role) const { return m_role[role].umlWidget; } /** * Sets the associated widgets. * * @param widgetA Pointer the role A widget for the association. * @param assocType The AssociationType::Enum for this association. * @param widgetB Pointer the role B widget for the association. */ bool AssociationWidget::setWidgets(UMLWidget* widgetA, Uml::AssociationType::Enum assocType, UMLWidget* widgetB) { //if the association already has a WidgetB or WidgetA associated, then //it cannot be changed to other widget, that would require a deletion //of the association and the creation of a new one if ((m_role[RoleType::A].umlWidget && (m_role[RoleType::A].umlWidget != widgetA)) || (m_role[RoleType::B].umlWidget && (m_role[RoleType::B].umlWidget != widgetB))) { return false; } setWidgetForRole(widgetA, RoleType::A); setAssociationType(assocType); setWidgetForRole(widgetB, RoleType::B); calculateEndingPoints(); return true; } /** * CleansUp all the association's data in the related widgets. */ void AssociationWidget::cleanup() { //let any other associations know we are going so they can tidy their positions up if (m_role[RoleType::A].m_nTotalCount > 2) updateAssociations(m_role[RoleType::A].m_nTotalCount - 1, m_role[RoleType::A].m_WidgetRegion, RoleType::A); if (m_role[RoleType::B].m_nTotalCount > 2) updateAssociations(m_role[RoleType::B].m_nTotalCount - 1, m_role[RoleType::B].m_WidgetRegion, RoleType::B); for (unsigned r = RoleType::A; r <= RoleType::B; ++r) { WidgetRole& robj = m_role[r]; if (robj.umlWidget) { robj.umlWidget->removeAssoc(this); robj.umlWidget = 0; } if (robj.roleWidget) { m_scene->removeWidget(robj.roleWidget); robj.roleWidget = 0; } if (robj.multiplicityWidget) { m_scene->removeWidget(robj.multiplicityWidget); robj.multiplicityWidget = 0; } if (robj.changeabilityWidget) { m_scene->removeWidget(robj.changeabilityWidget); robj.changeabilityWidget = 0; } } if (m_nameWidget) { m_scene->removeWidget(m_nameWidget); m_nameWidget = 0; } if (m_umlObject && m_umlObject->baseType() == UMLObject::ot_Association) { /* We do not remove the UMLAssociation from the document. Why? - Well, for example we might be in the middle of a cut/paste. If the UMLAssociation is removed by the cut then upon pasteing we have a problem. This is not quite clean yet - there should be a way to explicitly delete a UMLAssociation. The Right Thing would be to have a ListView representation for UMLAssociation. ` IF we are cut n pasting, why are we handling this association as a pointer? We should be using the XMI representation for a cut and paste. This allows us to be clean here, AND a choice of recreating the object w/ same id IF its a "cut", or a new object if its a "copy" operation (in which case we wouldnt be here, in cleanup()). */ setUMLAssociation(0); } m_associationLine->cleanup(); removeAssocClassLine(); } /** * @brief Return state if the assocation line point in the near of the last context * menu event position is addable or not. * A point is addable if the association is not an Exception and there is no point in the near. * * @return true if point is addable */ bool AssociationWidget::isPointAddable() { if (!isSelected() || associationType() == Uml::AssociationType::Exception) return false; int i = m_associationLine->closestPointIndex(m_eventScenePos); return i == -1; } /** * @brief Return state if the assocation line point in the near of the last context * menu event position is removable or not. * A point is removable if the association is not an Exception and is not the start or end point. * * @return true if point is removable */ bool AssociationWidget::isPointRemovable() { if (!isSelected() || associationType() == Uml::AssociationType::Exception || m_associationLine->count() <= 2) return false; int i = m_associationLine->closestPointIndex(m_eventScenePos); return i > 0 && i < m_associationLine->count() - 1; } /** * Set our internal umlAssociation. */ void AssociationWidget::setUMLAssociation (UMLAssociation * assoc) { if (m_umlObject && m_umlObject->baseType() == UMLObject::ot_Association) { UMLAssociation *umla = association(); // safety check. Did some num-nuts try to set the existing // association again? If so, just bail here if (umla == assoc) return; //umla->disconnect(this); //Qt does disconnect automatically upon destruction. umla->nrof_parent_widgets--; // ANSWER: This is the wrong treatment of cut and paste. Associations that // are being cut/n pasted should be serialized to XMI, then reconstituted // (IF a paste operation) rather than passing around object pointers. Its // just too hard otherwise to prevent problems in the code. Bottom line: we need to // delete orphaned associations or we well get code crashes and memory leaks. if (umla->nrof_parent_widgets <= 0) { //umla->deleteLater(); } m_umlObject = 0; } if (assoc) { m_umlObject = assoc; // move counter to "0" from "-1" (which means, no assocwidgets) if (assoc->nrof_parent_widgets < 0) assoc->nrof_parent_widgets = 0; assoc->nrof_parent_widgets++; connect(assoc, SIGNAL(modified()), this, SLOT(syncToModel())); } } /** * Returns true if the Widget is either at the starting or ending side of the association. */ bool AssociationWidget::containsAsEndpoint(UMLWidget* widget) { return (widget == m_role[RoleType::A].umlWidget || widget == m_role[RoleType::B].umlWidget); } /** * Returns true if this AssociationWidget represents a collaboration message. */ bool AssociationWidget::isCollaboration() const { Uml::AssociationType::Enum at = associationType(); return (at == AssociationType::Coll_Message_Synchronous || at == AssociationType::Coll_Message_Asynchronous || at == AssociationType::Coll_Message_Self); } /** * Returns true if this AssociationWidget represents a self message. */ bool AssociationWidget::isSelf() const { return widgetForRole(Uml::RoleType::A) == widgetForRole(Uml::RoleType::B); } /** * Gets the association's type. * * @return This AssociationWidget's AssociationType::Enum. */ Uml::AssociationType::Enum AssociationWidget::associationType() const { if (m_umlObject == 0 || m_umlObject->baseType() != UMLObject::ot_Association) return m_associationType; UMLAssociation *umla = m_umlObject->asUMLAssociation(); return umla->getAssocType(); } /** * Sets the association's type. * * @param type The AssociationType::Enum to set. */ void AssociationWidget::setAssociationType(Uml::AssociationType::Enum type) { if (m_umlObject && m_umlObject->baseType() == UMLObject::ot_Association) { association()->setAssociationType(type); } m_associationType = type; // If the association new type is not supposed to have Multiplicity // FloatingTexts and a Role FloatingTextWidget then set the texts // to empty. // NB We do not physically delete the floatingtext widgets here because // those widgets are also stored in the UMLView::m_WidgetList. if (!AssocRules::allowMultiplicity(type, widgetForRole(RoleType::A)->baseType())) { if (m_role[RoleType::A].multiplicityWidget) { m_role[RoleType::A].multiplicityWidget->setName(QString()); } if (m_role[RoleType::B].multiplicityWidget) { m_role[RoleType::B].multiplicityWidget->setName(QString()); } } if (!AssocRules::allowRole(type)) { if (m_role[RoleType::A].roleWidget) { m_role[RoleType::A].roleWidget->setName(QString()); } if (m_role[RoleType::B].roleWidget) { m_role[RoleType::B].roleWidget->setName(QString()); } setRoleDocumentation(QString(), RoleType::A); setRoleDocumentation(QString(), RoleType::B); } m_associationLine->reconstructSymbols(); m_associationLine->updatePenStyle(); } /** * Gets the ID of the given role widget. */ Uml::ID::Type AssociationWidget::widgetIDForRole(Uml::RoleType::Enum role) const { if (m_role[role].umlWidget == 0) { if (m_umlObject && m_umlObject->baseType() == UMLObject::ot_Association) { UMLAssociation *umla = m_umlObject->asUMLAssociation(); return umla->getObjectId(role); } uError() << "umlWidget is NULL"; return Uml::ID::None; } if (m_role[role].umlWidget->isObjectWidget()) return static_cast(m_role[role].umlWidget)->localID(); Uml::ID::Type id = m_role[role].umlWidget->id(); return id; } /** * Gets the local ID of the given role widget. */ Uml::ID::Type AssociationWidget::widgetLocalIDForRole(Uml::RoleType::Enum role) const { if (m_role[role].umlWidget == 0) { if (m_umlObject && m_umlObject->baseType() == UMLObject::ot_Association) { UMLAssociation *umla = m_umlObject->asUMLAssociation(); return umla->getObjectId(role); } uError() << "umlWidget is NULL"; return Uml::ID::None; } if (m_role[role].umlWidget->isObjectWidget()) return static_cast(m_role[role].umlWidget)->localID(); Uml::ID::Type id = m_role[role].umlWidget->localID(); return id; } /** * Returns a QString Object representing this AssociationWidget. */ QString AssociationWidget::toString() const { QString string; static const QChar colon(QLatin1Char(':')); static const QChar space(QLatin1Char(' ')); if (widgetForRole(RoleType::A)) { string = widgetForRole(RoleType::A)->name(); } string.append(colon); if (m_role[RoleType::A].roleWidget) { string += m_role[RoleType::A].roleWidget->text(); } string.append(space); string.append(Uml::AssociationType::toStringI18n(associationType())); string.append(space); if (widgetForRole(RoleType::B)) { string += widgetForRole(RoleType::B)->name(); } string.append(colon); if (m_role[RoleType::B].roleWidget) { string += m_role[RoleType::B].roleWidget->text(); } return string; } /** * Adds a break point (if left mouse button). */ void AssociationWidget::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) { if (event->button() == Qt::LeftButton) { uDebug() << "widget = " << name() << " / type = " << baseTypeStr(); showPropertiesDialog(); event->accept(); } } /** * Overrides moveEvent. */ void AssociationWidget::moveEvent(QGraphicsSceneMouseEvent *me) { // 2004-04-30: Achim Spangler // Simple Approach to block moveEvent during load of XMI /// @todo avoid trigger of this event during load if (umlDoc()->loading()) { // hmmh - change of position during load of XMI // -> there is something wrong // -> avoid movement during opening // -> print warn and stay at old position uWarning() << "called during load of XMI for ViewType: " << m_scene->type() << ", and BaseType: " << baseType(); return; } /*to be here a line segment has moved. we need to see if the three text widgets needs to be moved. there are a few things to check first though: 1) Do they exist 2) does it need to move: 2a) for the multi widgets only move if they changed region, otherwise they are close enough 2b) for role name move if the segment it is on moves. */ //first see if either the first or last segments moved, else no need to recalculate their point positions QPointF oldNamePoint = calculateTextPosition(TextRole::Name); QPointF oldMultiAPoint = calculateTextPosition(TextRole::MultiA); QPointF oldMultiBPoint = calculateTextPosition(TextRole::MultiB); QPointF oldChangeAPoint = calculateTextPosition(TextRole::ChangeA); QPointF oldChangeBPoint = calculateTextPosition(TextRole::ChangeB); QPointF oldRoleAPoint = calculateTextPosition(TextRole::RoleAName); QPointF oldRoleBPoint = calculateTextPosition(TextRole::RoleBName); int movingPoint = m_associationLine->closestPointIndex(me->scenePos()); if (movingPoint != -1) m_associationLine->setPoint(movingPoint, me->scenePos()); int pos = m_associationLine->count() - 1;//set to last point for widget b if ( movingPoint == 1 || (movingPoint == pos-1) ) { calculateEndingPoints(); } if (m_role[RoleType::A].changeabilityWidget && (movingPoint == 1)) { setTextPositionRelatively(TextRole::ChangeA, oldChangeAPoint); } if (m_role[RoleType::B].changeabilityWidget && (movingPoint == 1)) { setTextPositionRelatively(TextRole::ChangeB, oldChangeBPoint); } if (m_role[RoleType::A].multiplicityWidget && (movingPoint == 1)) { setTextPositionRelatively(TextRole::MultiA, oldMultiAPoint); } if (m_role[RoleType::B].multiplicityWidget && (movingPoint == pos-1)) { setTextPositionRelatively(TextRole::MultiB, oldMultiBPoint); } if (m_nameWidget) { if (movingPoint == m_unNameLineSegment || movingPoint - 1 == m_unNameLineSegment) { setTextPositionRelatively(TextRole::Name, oldNamePoint); } } if (m_role[RoleType::A].roleWidget) { setTextPositionRelatively(TextRole::RoleAName, oldRoleAPoint); } if (m_role[RoleType::B].roleWidget) { setTextPositionRelatively(TextRole::RoleBName, oldRoleBPoint); } if (m_pAssocClassLine) { computeAssocClassLine(); } } /** * Calculates and sets the first and last point in the Association's AssociationLine. * Each point is a middle point of its respecting UMLWidget's Bounding rectangle * or a corner of it. * This method picks which sides to use for the association. */ void AssociationWidget::calculateEndingPoints() { /* * For each UMLWidget the diagram is divided in four regions by its diagonals * as indicated below * Region 2 * \ / * \ / * +--------+ * | \ / | * Region 1 | >< | Region 3 * | / \ | * +--------+ * / \ * / \ * Region 4 * * Each diagonal is defined by two corners of the bounding rectangle * * To calculate the first point in the AssociationLine we have to find out in which * Region (defined by WidgetA's diagonals) is WidgetB's center * (let's call it Region M.) After that the first point will be the middle * point of the rectangle's side contained in Region M. * * To calculate the last point in the AssociationLine we repeat the above but * in the opposite direction (from widgetB to WidgetA) */ UMLWidget *pWidgetA = m_role[RoleType::A].umlWidget; UMLWidget *pWidgetB = m_role[RoleType::B].umlWidget; if (!pWidgetA || !pWidgetB) { uWarning() << "Returning - one of the role widgets is not set."; return; } int size = m_associationLine->count(); if (size < 2) { QPointF pA = pWidgetA->pos(); QPointF pB = pWidgetB->pos(); QPolygonF polyA = pWidgetA->shape().toFillPolygon().translated(pA); QPolygonF polyB = pWidgetB->shape().toFillPolygon().translated(pB); QLineF nearestPoints = Widget_Utils::closestPoints(polyA, polyB); if (nearestPoints.isNull()) { uError() << "Widget_Utils::closestPoints failed, falling back to simple widget positions"; } else { pA = nearestPoints.p1(); pB = nearestPoints.p2(); } m_associationLine->setEndPoints(pA, pB); } // See if an association to self. // See if it needs to be set up before we continue: // If self association/message and doesn't have the minimum 4 points // then create it. Make sure no points are out of bounds of viewing area. // This only happens on first time through that we are worried about. if (isSelf() && size < 4) { createPointsSelfAssociation(); return; } if (associationType() == AssociationType::Exception && size < 4) { createPointsException(); updatePointsException(); return; } // If the line has more than one segment change the values to calculate // from widget to point 1. qreal xB = pWidgetB->x() + pWidgetB->width() / 2; qreal yB = pWidgetB->y() + pWidgetB->height() / 2; if (size > 2) { QPointF p = m_associationLine->point(1); xB = p.x(); yB = p.y(); } doUpdates(QPointF(xB, yB), RoleType::A); // Now do the same for widgetB. // If the line has more than one segment change the values to calculate // from widgetB to the last point away from it. qreal xA = pWidgetA->x() + pWidgetA->width() / 2; qreal yA = pWidgetA->y() + pWidgetA->height() / 2; if (size > 2 ) { QPointF p = m_associationLine->point(size - 2); xA = p.x(); yA = p.y(); } doUpdates(QPointF(xA, yA), RoleType::B); computeAssocClassLine(); } /** * Used by @ref calculateEndingPoints. */ void AssociationWidget::doUpdates(const QPointF &otherP, RoleType::Enum role) { // Find widget region. Uml::Region::Enum oldRegion = m_role[role].m_WidgetRegion; UMLWidget *pWidget = m_role[role].umlWidget; QRectF rc(pWidget->x(), pWidget->y(), pWidget->width(), pWidget->height()); Uml::Region::Enum region = m_role[role].m_WidgetRegion; // alias for brevity region = findPointRegion(rc, otherP); // Move some regions to the standard ones. switch( region ) { case Uml::Region::NorthWest: region = Uml::Region::North; break; case Uml::Region::NorthEast: region = Uml::Region::East; break; case Uml::Region::SouthEast: region = Uml::Region::South; break; case Uml::Region::SouthWest: case Uml::Region::Center: region = Uml::Region::West; break; default: break; } int regionCount = getRegionCount(region, role) + 2; //+2 = (1 for this one and one to halve it) int totalCount = m_role[role].m_nTotalCount; if (oldRegion != region) { updateRegionLineCount(regionCount - 1, regionCount, region, role); updateAssociations(totalCount - 1, oldRegion, role); } else if (totalCount != regionCount) { updateRegionLineCount(regionCount - 1, regionCount, region, role); } else { updateRegionLineCount(m_role[role].m_nIndex, totalCount, region, role); } updateAssociations(regionCount, region, role); } /** * Read property of bool m_activated. */ bool AssociationWidget::isActivated() const { return m_activated; } /** * Set the m_activated flag of a widget but does not perform the Activate method. */ void AssociationWidget::setActivated(bool active) { m_activated = active; } /** * Synchronize this widget from the UMLAssociation. */ void AssociationWidget::syncToModel() { UMLAssociation *uml = association(); if (uml == 0) { UMLAttribute *attr = attribute(); if (attr == 0) return; setVisibility(attr->visibility(), RoleType::B); setRoleName(attr->name(), RoleType::B); return; } // block signals until finished uml->blockSignals(true); setName(uml->name()); setRoleName(uml->getRoleName(RoleType::A), RoleType::A); setRoleName(uml->getRoleName(RoleType::B), RoleType::B); setVisibility(uml->visibility(RoleType::A), RoleType::A); setVisibility(uml->visibility(RoleType::B), RoleType::B); setChangeability(uml->changeability(RoleType::A), RoleType::A); setChangeability(uml->changeability(RoleType::B), RoleType::B); setMultiplicity(uml->getMultiplicity(RoleType::A), RoleType::A); setMultiplicity(uml->getMultiplicity(RoleType::B), RoleType::B); uml->blockSignals(false); } /** * Merges/syncs the association widget data into UML object * representation. * This will synchronize UMLAssociation w/ this new Widget * CHECK: Can we get rid of this. */ void AssociationWidget::mergeAssociationDataIntoUMLRepresentation() { UMLAssociation *umlassoc = association(); UMLAttribute *umlattr = attribute(); if (umlassoc == 0 && umlattr == 0) return; // block emit modified signal, or we get a horrible loop m_umlObject->blockSignals(true); // would be desirable to do the following // so that we can be sure its back to initial state // in case we missed something here. //uml->init(); // floating text widgets FloatingTextWidget *text = nameWidget(); if (text) m_umlObject->setName(text->text()); text = roleWidget(RoleType::A); if (text && umlassoc) umlassoc->setRoleName(text->text(), RoleType::A); text = roleWidget(RoleType::B); if (text) { if (umlassoc) umlassoc->setRoleName(text->text(), RoleType::B); else if (umlattr) umlattr->setName(text->text()); } text = multiplicityWidget(RoleType::A); if (text && umlassoc) umlassoc->setMultiplicity(text->text(), RoleType::A); text = multiplicityWidget(RoleType::B); if (text && umlassoc) umlassoc->setMultiplicity(text->text(), RoleType::B); // unblock m_umlObject->blockSignals(false); } /** * Auxiliary method for widgetMoved(): * Saves all ideally computed floatingtext positions before doing any * kind of change. This is necessary because a single invocation of * calculateEndingPoints() modifies the AssociationLine ending points on ALL * AssociationWidgets. This means that if we don't save the old ideal * positions then they are irretrievably lost as soon as * calculateEndingPoints() is invoked. */ void AssociationWidget::saveIdealTextPositions() { m_oldNamePoint = calculateTextPosition(TextRole::Name); m_oldMultiAPoint = calculateTextPosition(TextRole::MultiA); m_oldMultiBPoint = calculateTextPosition(TextRole::MultiB); m_oldChangeAPoint = calculateTextPosition(TextRole::ChangeA); m_oldChangeBPoint = calculateTextPosition(TextRole::ChangeB); m_oldRoleAPoint = calculateTextPosition(TextRole::RoleAName); m_oldRoleBPoint = calculateTextPosition(TextRole::RoleBName); } /** * Adjusts the ending point of the association that connects to Widget. */ void AssociationWidget::widgetMoved(UMLWidget* widget, qreal dx, qreal dy) { Q_UNUSED(dx); Q_UNUSED(dy); // Simple Approach to block moveEvent during load of XMI /// @todo avoid trigger of this event during load if (umlDoc()->loading()) { // change of position during load of XMI // -> there is something wrong // -> avoid movement during opening // -> print warn and stay at old position DEBUG(DBG_SRC) << "called during load of XMI for ViewType: " << m_scene->type() << ", and BaseType: " << baseTypeStr(); return; } DEBUG(DBG_SRC) << "association type=" << Uml::AssociationType::toString(associationType()); if (associationType() == AssociationType::Exception) { updatePointsException(); setTextPosition(TextRole::Name); } else { calculateEndingPoints(); computeAssocClassLine(); } // Assoc to self - move all points: if (isSelf()) { updatePointsSelfAssociation(); if (m_nameWidget && !m_nameWidget->isSelected()) { setTextPositionRelatively(TextRole::Name, m_oldNamePoint); } }//end if widgetA = widgetB else if (m_role[RoleType::A].umlWidget == widget) { if (m_nameWidget && m_unNameLineSegment == 0 && !m_nameWidget->isSelected() ) { //only calculate position and move text if the segment it is on is moving setTextPositionRelatively(TextRole::Name, m_oldNamePoint); } + if (m_role[RoleType::B].umlWidget->changesShape()) + m_role[RoleType::B].umlWidget->updateGeometry(); }//end if widgetA moved else if (m_role[RoleType::B].umlWidget == widget) { const int size = m_associationLine->count(); if (m_nameWidget && (m_unNameLineSegment == size-2) && !m_nameWidget->isSelected() ) { //only calculate position and move text if the segment it is on is moving setTextPositionRelatively(TextRole::Name, m_oldNamePoint); } + if (m_role[RoleType::A].umlWidget->changesShape()) + m_role[RoleType::A].umlWidget->updateGeometry(); }//end if widgetB moved if (m_role[RoleType::A].roleWidget && !m_role[RoleType::A].roleWidget->isSelected()) { setTextPositionRelatively(TextRole::RoleAName, m_oldRoleAPoint); } if (m_role[RoleType::B].roleWidget && !m_role[RoleType::B].roleWidget->isSelected()) { setTextPositionRelatively(TextRole::RoleBName, m_oldRoleBPoint); } if (m_role[RoleType::A].multiplicityWidget && !m_role[RoleType::A].multiplicityWidget->isSelected()) { setTextPositionRelatively(TextRole::MultiA, m_oldMultiAPoint); } if (m_role[RoleType::B].multiplicityWidget && !m_role[RoleType::B].multiplicityWidget->isSelected()) { setTextPositionRelatively(TextRole::MultiB, m_oldMultiBPoint); } if (m_role[RoleType::A].changeabilityWidget && !m_role[RoleType::A].changeabilityWidget->isSelected()) { setTextPositionRelatively(TextRole::ChangeA, m_oldChangeAPoint); } if (m_role[RoleType::B].changeabilityWidget && !m_role[RoleType::B].changeabilityWidget->isSelected()) { setTextPositionRelatively(TextRole::ChangeB, m_oldChangeBPoint); } } /** * Creates the points of the self association. * Method called when a widget end points are calculated by calculateEndingPoints(). */ void AssociationWidget::createPointsSelfAssociation() { UMLWidget *pWidgetA = m_role[RoleType::A].umlWidget; const int DISTANCE = 50; qreal x = pWidgetA->x(); qreal y = pWidgetA->y(); qreal h = pWidgetA->height(); qreal w = pWidgetA->width(); // see if above widget ok to start if (y - DISTANCE > 0) { m_associationLine->setEndPoints(QPointF(x + w / 4, y) , QPointF(x + w * 3 / 4, y)); m_associationLine->insertPoint(1, QPointF(x + w / 4, y - DISTANCE)); m_associationLine->insertPoint(2, QPointF(x + w * 3 / 4, y - DISTANCE)); m_role[RoleType::A].m_WidgetRegion = m_role[RoleType::B].m_WidgetRegion = Uml::Region::North; } else { m_associationLine->setEndPoints(QPointF(x + w / 4, y + h), QPointF(x + w * 3 / 4, y + h)); m_associationLine->insertPoint(1, QPointF(x + w / 4, y + h + DISTANCE)); m_associationLine->insertPoint(2, QPointF(x + w * 3 / 4, y + h + DISTANCE)); m_role[RoleType::A].m_WidgetRegion = m_role[RoleType::B].m_WidgetRegion = Uml::Region::South; } } /** * Adjusts the points of the self association. * Method called when a widget was moved by widgetMoved(widget, x, y). */ void AssociationWidget::updatePointsSelfAssociation() { UMLWidget *pWidgetA = m_role[RoleType::A].umlWidget; const int DISTANCE = 50; qreal x = pWidgetA->x(); qreal y = pWidgetA->y(); qreal h = pWidgetA->height(); qreal w = pWidgetA->width(); // see if above widget ok to start if (y - DISTANCE > 0) { m_associationLine->setEndPoints(QPointF(x + w / 4, y) , QPointF(x + w * 3 / 4, y)); m_associationLine->setPoint(1, QPointF(x + w / 4, y - DISTANCE)); m_associationLine->setPoint(2, QPointF(x + w * 3 / 4, y - DISTANCE)); m_role[RoleType::A].m_WidgetRegion = m_role[RoleType::B].m_WidgetRegion = Uml::Region::North; } else { m_associationLine->setEndPoints(QPointF(x + w / 4, y + h), QPointF(x + w * 3 / 4, y + h)); m_associationLine->setPoint(1, QPointF(x + w / 4, y + h + DISTANCE)); m_associationLine->setPoint(2, QPointF(x + w * 3 / 4, y + h + DISTANCE)); m_role[RoleType::A].m_WidgetRegion = m_role[RoleType::B].m_WidgetRegion = Uml::Region::South; } } /** * Creates the points of the association exception. * Method called when a widget end points are calculated by calculateEndingPoints(). */ void AssociationWidget::createPointsException() { UMLWidget *pWidgetA = m_role[RoleType::A].umlWidget; UMLWidget *pWidgetB = m_role[RoleType::B].umlWidget; qreal xa = pWidgetA->x(); qreal ya = pWidgetA->y(); qreal ha = pWidgetA->height(); qreal wa = pWidgetA->width(); qreal xb = pWidgetB->x(); qreal yb = pWidgetB->y(); qreal hb = pWidgetB->height(); //qreal wb = pWidgetB->width(); m_associationLine->setEndPoints(QPointF(xa + wa , ya + ha/2) , QPointF(xb , yb + hb/2)); m_associationLine->insertPoint(1, QPointF(xa + wa , ya + ha/2)); m_associationLine->insertPoint(2, QPointF(xb , yb + hb/2)); } /** * Adjusts the points of the association exception. * Method called when a widget was moved by widgetMoved(widget, x, y). */ void AssociationWidget::updatePointsException() { UMLWidget *pWidgetA = m_role[RoleType::A].umlWidget; UMLWidget *pWidgetB = m_role[RoleType::B].umlWidget; qreal xa = pWidgetA->x(); qreal ya = pWidgetA->y(); qreal ha = pWidgetA->height(); qreal wa = pWidgetA->width(); qreal xb = pWidgetB->x(); qreal yb = pWidgetB->y(); qreal hb = pWidgetB->height(); qreal wb = pWidgetB->width(); qreal xmil, ymil; qreal xdeb, ydeb; qreal xfin, yfin; qreal ESPACEX, ESPACEY; QPointF p1; QPointF p2; //calcul des coordonnées au milieu de la flèche eclair if (xb - xa - wa >= 45) { ESPACEX = 0; xdeb = xa + wa; xfin = xb; } else if (xa - xb - wb > 45) { ESPACEX = 0; xdeb = xa; xfin = xb + wb; } else { ESPACEX = 15; xdeb = xa + wa/2; xfin = xb + wb/2; } xmil = xdeb + (xfin - xdeb)/2; if (yb - ya - ha >= 45) { ESPACEY = 0; ydeb = ya + ha; yfin = yb; } else if (ya - yb - hb > 45) { ESPACEY = 0; ydeb = ya; yfin = yb + hb; } else { ESPACEY = 15; ydeb = ya + ha/2; yfin = yb + hb/2; } ymil = ydeb + (yfin - ydeb)/2; p1.setX(xmil + (xfin - xmil)*1/2); p1.setY(ymil + (yfin - ymil)*1/3); p2.setX(xmil - (xmil - xdeb)*1/2); p2.setY(ymil - (ymil - ydeb)*1/3); if (fabs(p1.x() - p2.x()) <= 10) ESPACEX = 15; if (fabs(p1.y() - p2.y()) <= 10) ESPACEY = 15; m_associationLine->setEndPoints(QPointF(xdeb, ydeb), QPointF(xfin, yfin)); m_associationLine->setPoint(1, QPointF(p1.x() + ESPACEX, p1.y() + ESPACEY)); m_associationLine->setPoint(2, QPointF(p2.x() - ESPACEX, p2.y() - ESPACEY)); m_role[RoleType::A].m_WidgetRegion = m_role[RoleType::B].m_WidgetRegion = Uml::Region::North; } /** * Finds out which region of rectangle 'rect' contains the point 'pos' and returns the region * number: * 1 = Region 1 * 2 = Region 2 * 3 = Region 3 * 4 = Region 4 * 5 = On diagonal 2 between Region 1 and 2 * 6 = On diagonal 1 between Region 2 and 3 * 7 = On diagonal 2 between Region 3 and 4 * 8 = On diagonal 1 between Region 4 and 1 * 9 = On diagonal 1 and On diagonal 2 (the center) */ Uml::Region::Enum AssociationWidget::findPointRegion(const QRectF& rect, const QPointF &pos) { qreal w = rect.width(); qreal h = rect.height(); qreal x = rect.x(); qreal y = rect.y(); qreal slope2 = w / h; qreal slope1 = slope2 *(-1.0); qreal b1 = x + w - (slope1 * y); qreal b2 = x - (slope2 * y); qreal eval1 = slope1 * pos.y() + b1; qreal eval2 = slope2 * pos.y() + b2; Uml::Region::Enum result = Uml::Region::Error; //if inside region 1 if (eval1 > pos.x() && eval2 > pos.x()) { result = Uml::Region::West; } //if inside region 2 else if (eval1 > pos.x() && eval2 < pos.x()) { result = Uml::Region::North; } //if inside region 3 else if (eval1 < pos.x() && eval2 < pos.x()) { result = Uml::Region::East; } //if inside region 4 else if (eval1 < pos.x() && eval2 > pos.x()) { result = Uml::Region::South; } //if inside region 5 else if (eval1 == pos.x() && eval2 < pos.x()) { result = Uml::Region::NorthWest; } //if inside region 6 else if (eval1 < pos.x() && eval2 == pos.x()) { result = Uml::Region::NorthEast; } //if inside region 7 else if (eval1 == pos.x() && eval2 > pos.x()) { result = Uml::Region::SouthEast; } //if inside region 8 else if (eval1 > pos.x() && eval2 == pos.x()) { result = Uml::Region::SouthWest; } //if inside region 9 else if (eval1 == pos.x() && eval2 == pos.x()) { result = Uml::Region::Center; } return result; } /** * Returns a point with interchanged X and Y coordinates. */ QPointF AssociationWidget::swapXY(const QPointF &p) { QPointF swapped( p.y(), p.x() ); return swapped; } #if 0 // not used at the moment /** * Calculates which point of segment P1P2 has a distance equal to * Distance from P1. * Let's say such point is PX, the distance from P1 to PX must be equal * to Distance and if PX is not a point of the segment P1P2 then the * function returns (-1, -1). */ QPointF AssociationWidget::calculatePointAtDistance(const QPointF &P1, const QPointF &P2, float Distance) { /* the distance D between points (x1, y1) and (x3, y3) has the following formula: --- ------------------------------ D = \ / 2 2 \ / (x3 - x1) + (y3 - y1) D, x1 and y1 are known and the point (x3, y3) is inside line (x1, y1)(x2, y2), so if the that line has the formula y = mx + b then y3 = m*x3 + b 2 2 2 D = (x3 - x1) + (y3 - y1) 2 2 2 2 2 D = x3 - 2*x3*x1 + x1 + y3 - 2*y3*y1 + y1 2 2 2 2 2 D - x1 - y1 = x3 - 2*x3*x1 + y3 - 2*y3*y1 2 2 2 2 2 D - x1 - y1 = x3 - 2*x3*x1 + (m*x3 + b) - 2*(m*x3 + b)*y1 2 2 2 2 2 2 D - x1 - y1 + 2*b*y1 - b = (m + 1)*x3 + (-2*x1 + 2*m*b -2*m*y1)*x3 2 2 2 2 C = - D + x1 + y1 - 2*b*y1 + b 2 A = (m + 1) B = (-2*x1 + 2*m*b -2*m*y1) and we have 2 A * x3 + B * x3 - C = 0 --------------- -B + --- / 2 \/ B - 4*A*C sol_1 = -------------------------------- 2*A --------------- -B - --- / 2 \/ B - 4*A*C sol_2 = -------------------------------- 2*A then in the distance formula we have only one variable x3 and that is easy to calculate */ int x1 = P1.y(); int y1 = P1.x(); int x2 = P2.y(); int y2 = P2.x(); if (x2 == x1) { return QPointF(x1, y1 + (int)Distance); } float slope = ((float)y2 - (float)y1) / ((float)x2 - (float)x1); float b = (y1 - slope*x1); float A = (slope * slope) + 1; float B = (2*slope*b) - (2*x1) - (2*slope*y1); float C = (b*b) - (Distance*Distance) + (x1*x1) + (y1*y1) - (2*b*y1); float t = B*B - 4*A*C; if (t < 0) { return QPointF(-1, -1); } float sol_1 = ((-1* B) + sqrt(t)) / (2*A); float sol_2 = ((-1*B) - sqrt(t)) / (2*A); if (sol_1 < 0.0 && sol_2 < 0.0) { return QPointF(-1, -1); } QPointF sol1Point((int)(slope*sol_1 + b), (int)(sol_1)); QPointF sol2Point((int)(slope*sol_2 + b), (int)(sol_2)); if (sol_1 < 0 && sol_2 >=0) { if (x2 > x1) { if (x1 <= sol_2 && sol_2 <= x2) return sol2Point; } else { if (x2 <= sol_2 && sol_2 <= x1) return sol2Point; } } else if (sol_1 >= 0 && sol_2 < 0) { if (x2 > x1) { if (x1 <= sol_1 && sol_1 <= x2) return sol1Point; } else { if (x2 <= sol_1 && sol_1 <= x1) return sol1Point; } } else { if (x2 > x1) { if (x1 <= sol_1 && sol_1 <= x2) return sol1Point; if (x1 <= sol_2 && sol_2 <= x2) return sol2Point; } else { if (x2 <= sol_1 && sol_1 <= x1) return sol1Point; if (x2 <= sol_2 && sol_2 <= x1) return sol2Point; } } return QPointF(-1, -1); } /** * Calculates which point of a perpendicular line to segment P1P2 that contains P2 * has a distance equal to Distance from P2, * Lets say such point is P3, the distance from P2 to P3 must be equal to Distance */ QPointF AssociationWidget::calculatePointAtDistanceOnPerpendicular(const QPointF &P1, const QPointF &P2, float Distance) { /* the distance D between points (x2, y2) and (x3, y3) has the following formula: --- ------------------------------ D = \ / 2 2 \ / (x3 - x2) + (y3 - y2) D, x2 and y2 are known and line P2P3 is perpendicular to line (x1, y1)(x2, y2), so if the line P1P2 has the formula y = m*x + b, then (x1 - x2) m = -----------, because it is perpendicular to line P1P2 (y2 - y1) also y2 = m*x2 + b => b = y2 - m*x2 then P3 = (x3, m*x3 + b) 2 2 2 D = (x3 - x2) + (y3 - y2) 2 2 2 2 2 D = x3 - 2*x3*x2 + x2 + y3 - 2*y3*y2 + y2 2 2 2 2 2 D - x2 - y2 = x3 - 2*x3*x2 + y3 - 2*y3*y2 2 2 2 2 2 D - x2 - y2 = x3 - 2*x3*x2 + (m*x3 + b) - 2*(m*x3 + b)*y2 2 2 2 2 2 2 D - x2 - y2 + 2*b*y2 - b = (m + 1)*x3 + (-2*x2 + 2*m*b -2*m*y2)*x3 2 2 2 2 C = - D + x2 + y2 - 2*b*y2 + b 2 A = (m + 1) B = (-2*x2 + 2*m*b -2*m*y2) and we have 2 A * x3 + B * x3 - C = 0 --------------- --- / 2 -B + \/ B - 4*A*C sol_1 = -------------------------------- 2*A --------------- --- / 2 -B - \/ B - 4*A*C sol_2 = -------------------------------- 2*A then in the distance formula we have only one variable x3 and that is easy to calculate */ if (P1.x() == P2.x()) { return QPointF((int)(P2.x() + Distance), P2.y()); } const int x1 = P1.y(); const int y1 = P1.x(); const int x2 = P2.y(); const int y2 = P2.x(); float slope = ((float)x1 - (float)x2) / ((float)y2 - (float)y1); float b = (y2 - slope*x2); float A = (slope * slope) + 1; float B = (2*slope*b) - (2*x2) - (2*slope*y2); float C = (b*b) - (Distance*Distance) + (x2*x2) + (y2*y2) - (2*b*y2); float t = B*B - 4*A*C; if (t < 0) { return QPointF(-1, -1); } float sol_1 = ((-1* B) + sqrt(t)) / (2*A); float sol_2 = ((-1*B) - sqrt(t)) / (2*A); if (sol_1 < 0 && sol_2 < 0) { return QPointF(-1, -1); } QPointF sol1Point((int)(slope*sol_1 + b), (int)sol_1); QPointF sol2Point((int)(slope*sol_2 + b), (int)sol_2); if (sol_1 < 0 && sol_2 >=0) { return sol2Point; } else if (sol_1 >= 0 && sol_2 < 0) { return sol1Point; } else { // Choose one solution, either will work fine if (slope >= 0) { if (sol_1 <= sol_2) return sol2Point; else return sol1Point; } else { if (sol_1 <= sol_2) return sol1Point; else return sol2Point; } } return QPointF(-1, -1); // never reached, just keep compilers happy } /** * Calculates the intersection (PS) between line P1P2 and a perpendicular line containing * P3, the result is returned in ResultingPoint. and result value represents the distance * between ResultingPoint and P3; if this value is negative an error ocurred. */ float AssociationWidget::perpendicularProjection(const QPointF& P1, const QPointF& P2, const QPointF& P3, QPointF& ResultingPoint) { //line P1P2 is Line 1 = y=slope1*x + b1 //line P3PS is Line 1 = y=slope2*x + b2 float slope2 = 0; float slope1 = 0; float sx = 0, sy = 0; int y2 = P2.x(); int y1 = P1.x(); int x2 = P2.y(); int x1 = P1.y(); int y3 = P3.x(); int x3 = P3.y(); float distance = 0; float b1 = 0; float b2 = 0; if (x2 == x1) { sx = x2; sy = y3; } else if (y2 == y1) { sy = y2; sx = x3; } else { slope1 = (y2 - y1)/ (x2 - x1); slope2 = (x1 - x2)/ (y2 - y1); b1 = y2 - (slope1 * x2); b2 = y3 - (slope2 * x3); sx = (b2 - b1) / (slope1 - slope2); sy = slope1*sx + b1; } distance = (int)(sqrt(((x3 - sx)*(x3 - sx)) + ((y3 - sy)*(y3 - sy)))); ResultingPoint.setX((int)sy); ResultingPoint.setY((int)sx); return distance; } #endif /** * Calculates the position of the text widget depending on the role * that widget is playing. * Returns the point at which to put the widget. */ QPointF AssociationWidget::calculateTextPosition(Uml::TextRole::Enum role) { const int SPACE = 2; QPointF p(-1, -1), q(-1, -1); // used to find out if association end point (p) // is at top or bottom edge of widget. if (role == TextRole::MultiA || role == TextRole::ChangeA || role == TextRole::RoleAName) { p = m_associationLine->point(0); q = m_associationLine->point(1); } else if (role == TextRole::MultiB || role == TextRole::ChangeB || role == TextRole::RoleBName) { const int lastSegment = m_associationLine->count() - 1; p = m_associationLine->point(lastSegment); q = m_associationLine->point(lastSegment - 1); } else if (role != TextRole::Name) { uError() << "called with unsupported TextRole::Enum " << role; return QPointF(-1, -1); } FloatingTextWidget *text = textWidgetByRole(role); int textW = 0, textH = 0; if (text) { textW = text->width(); textH = text->height(); } qreal x = 0.0, y = 0.0; if (role == TextRole::MultiA || role == TextRole::MultiB) { const bool isHorizontal = (p.y() == q.y()); const int atBottom = p.y() + SPACE; const int atTop = p.y() - SPACE - textH; const int atLeft = p.x() - SPACE - textW; const int atRight = p.x() + SPACE; y = (p.y() > q.y()) == isHorizontal ? atBottom : atTop; x = (p.x() < q.x()) == isHorizontal ? atRight : atLeft; } else if (role == TextRole::ChangeA || role == TextRole::ChangeB) { if (p.y() > q.y()) y = p.y() - SPACE - (textH * 2); else y = p.y() + SPACE + textH; if (p.x() < q.x()) x = p.x() + SPACE; else x = p.x() - SPACE - textW; } else if (role == TextRole::RoleAName || role == TextRole::RoleBName) { if (p.y() > q.y()) y = p.y() - SPACE - textH; else y = p.y() + SPACE; if (p.x() < q.x()) x = p.x() + SPACE; else x = p.x() - SPACE - textW; } else if (role == TextRole::Name) { calculateNameTextSegment(); if (m_unNameLineSegment == -1) { uWarning() << "TODO:negative line segment index"; m_unNameLineSegment = 0; } x = ( m_associationLine->point(m_unNameLineSegment).x() + m_associationLine->point(m_unNameLineSegment + 1).x() ) / 2; y = ( m_associationLine->point(m_unNameLineSegment).y() + m_associationLine->point(m_unNameLineSegment + 1).y() ) / 2; } if (text) { constrainTextPos(x, y, textW, textH, role); } p = QPointF( x, y ); return p; } /** * Return the mid point between p0 and p1 */ QPointF AssociationWidget::midPoint(const QPointF& p0, const QPointF& p1) { QPointF midP; if (p0.x() < p1.x()) midP.setX(p0.x() + (p1.x() - p0.x()) / 2); else midP.setX(p1.x() + (p0.x() - p1.x()) / 2); if (p0.y() < p1.y()) midP.setY(p0.y() + (p1.y() - p0.y()) / 2); else midP.setY(p1.y() + (p0.y() - p1.y()) / 2); return midP; } /** * Constrains the FloatingTextWidget X and Y values supplied. * Implements the abstract operation from LinkWidget. * * @param textX Candidate X value (may be modified by the constraint.) * @param textY Candidate Y value (may be modified by the constraint.) * @param textWidth Width of the text. * @param textHeight Height of the text. * @param tr Uml::Text_Role of the text. */ void AssociationWidget::constrainTextPos(qreal &textX, qreal &textY, qreal textWidth, qreal textHeight, Uml::TextRole::Enum tr) { const int textCenterX = textX + textWidth / 2; const int textCenterY = textY + textHeight / 2; const int lastSegment = m_associationLine->count() - 1; QPointF p0, p1; switch (tr) { case TextRole::RoleAName: case TextRole::MultiA: case TextRole::ChangeA: p0 = m_associationLine->point(0); p1 = m_associationLine->point(1); // If we are dealing with a single line then tie the // role label to the proper half of the line, i.e. // the role label must be closer to the "other" // role object. if (lastSegment == 1) p1 = midPoint(p0, p1); break; case TextRole::RoleBName: case TextRole::MultiB: case TextRole::ChangeB: p0 = m_associationLine->point(lastSegment - 1); p1 = m_associationLine->point(lastSegment); if (lastSegment == 1) p0 = midPoint(p0, p1); break; case TextRole::Name: case TextRole::Coll_Message: // CHECK: collab.msg texts seem to be TextRole::Name case TextRole::State: // CHECK: is this used? // Find the linepath segment to which the (textX, textY) is closest // and constrain to the corridor of that segment (see farther below) { int minDistSquare = 100000; // utopian initial value int lpIndex = 0; for (int i = 0; i < lastSegment; ++i) { p0 = m_associationLine->point(i); p1 = m_associationLine->point(i + 1); QPointF midP = midPoint(p0, p1); const int deltaX = textCenterX - midP.x(); const int deltaY = textCenterY - midP.y(); const int cSquare = deltaX * deltaX + deltaY * deltaY; if (cSquare < minDistSquare) { minDistSquare = cSquare; lpIndex = i; } } p0 = m_associationLine->point(lpIndex); p1 = m_associationLine->point(lpIndex + 1); } break; default: uError() << "unexpected TextRole::Enum " << tr; return; break; } /* Constraint: The midpoint between p0 and p1 is taken to be the center of a circle with radius D/2 where D is the distance between p0 and p1. The text center needs to be within this circle else it is constrained to the nearest point on the circle. */ p0 = swapXY(p0); // go to the natural coordinate system p1 = swapXY(p1); // with (0,0) in the lower left corner QPointF midP = midPoint(p0, p1); // If (textX,textY) is not inside the circle around midP then // constrain (textX,textY) to the nearest point on that circle. const int x0 = p0.x(); const int y0 = p0.y(); const int x1 = p1.x(); const int y1 = p1.y(); double r = sqrt((double)((x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0))) / 2; if (textWidth > r) r = textWidth; // swap textCenter{X,Y} to convert from Qt coord.system. const QPointF origTextCenter(textCenterY, textCenterX); const int relX = fabs(origTextCenter.x() - midP.x()); const int relY = fabs(origTextCenter.y() - midP.y()); const double negativeWhenInsideCircle = relX * relX + relY * relY - r * r; if (negativeWhenInsideCircle <= 0.0) { return; } /* The original constraint was to snap the text position to the midpoint but that creates unpleasant visual jitter: textX = midP.y() - textWidth / 2; // go back to Qt coord.sys. textY = midP.x() - textHeight / 2; // go back to Qt coord.sys. Rather, we project the text position onto the closest point on the circle: Circle equation: relX^2 + relY^2 - r^2 = 0, or in other words relY^2 = r^2 - relX^2, or relY = sqrt(r^2 - relX^2) Line equation: relY = a * relX + b We can omit "b" because relX and relY are already relative to the circle origin, therefore we can also write: a = relY / relX To obtain the point of intersection between the circle of radius r and the line connecting the circle origin with the point (relX, relY), we equate the relY: a * x = sqrt(r^2 - x^2), or in other words a^2 * x^2 = r^2 - x^2, or x^2 * (a^2 + 1) = r^2, or x^2 = r^2 / (a^2 + 1), or x = sqrt(r^2 / (a^2 + 1)) and then y = a * x The resulting x and y are relative to the circle origin so we just add the circle origin (X, Y) to obtain the constrained (textX, textY). */ // Handle the special case, relX = 0. if (relX == 0) { if (origTextCenter.y() > midP.y()) textX = midP.y() + (int)r; // go back to Qt coord.sys. else textX = midP.y() - (int)r; // go back to Qt coord.sys. textX -= textWidth / 2; return; } const double a = (double)relY / (double)relX; const double x = sqrt(r*r / (a*a + 1)); const double y = a * x; if (origTextCenter.x() > midP.x()) textY = midP.x() + (int)x; // go back to Qt coord.sys. else textY = midP.x() - (int)x; // go back to Qt coord.sys. textY -= textHeight / 2; if (origTextCenter.y() > midP.y()) textX = midP.y() + (int)y; // go back to Qt coord.sys. else textX = midP.y() - (int)y; // go back to Qt coord.sys. textX -= textWidth / 2; } /** * Puts the text widget with the given role at the given position. * This method calls @ref calculateTextPostion to get the needed position. * I.e. the line segment it is on has moved and it should move the same * amount as the line. */ void AssociationWidget::setTextPosition(Uml::TextRole::Enum role) { bool startMove = false; if (m_role[RoleType::A].multiplicityWidget && m_role[RoleType::A].multiplicityWidget->getStartMove()) startMove = true; else if (m_role[RoleType::B].multiplicityWidget && m_role[RoleType::B].multiplicityWidget->getStartMove()) startMove = true; else if (m_role[RoleType::A].changeabilityWidget && m_role[RoleType::A].changeabilityWidget->getStartMove()) startMove = true; else if (m_role[RoleType::B].changeabilityWidget && m_role[RoleType::B].changeabilityWidget->getStartMove()) startMove = true; else if (m_role[RoleType::A].roleWidget && m_role[RoleType::A].roleWidget->getStartMove()) startMove = true; else if (m_role[RoleType::B].roleWidget && m_role[RoleType::B].roleWidget->getStartMove()) startMove = true; else if (m_nameWidget && m_nameWidget->getStartMove()) startMove = true; if (startMove) { return; } FloatingTextWidget *ft = textWidgetByRole(role); if (ft == 0) return; QPointF pos = calculateTextPosition(role); ft->setX(pos.x()); ft->setY(pos.y()); } /** * Moves the text widget with the given role by the difference between * the two points. */ void AssociationWidget::setTextPositionRelatively(Uml::TextRole::Enum role, const QPointF &oldPosition) { bool startMove = false; if (m_role[RoleType::A].multiplicityWidget && m_role[RoleType::A].multiplicityWidget->getStartMove()) startMove = true; else if (m_role[RoleType::B].multiplicityWidget && m_role[RoleType::B].multiplicityWidget->getStartMove()) startMove = true; else if (m_role[RoleType::A].changeabilityWidget && m_role[RoleType::A].changeabilityWidget->getStartMove()) startMove = true; else if (m_role[RoleType::B].changeabilityWidget && m_role[RoleType::B].changeabilityWidget->getStartMove()) startMove = true; else if (m_role[RoleType::A].roleWidget && m_role[RoleType::A].roleWidget->getStartMove()) startMove = true; else if (m_role[RoleType::B].roleWidget && m_role[RoleType::B].roleWidget->getStartMove()) startMove = true; else if (m_nameWidget && m_nameWidget->getStartMove()) startMove = true; if (startMove) { return; } FloatingTextWidget *ft = textWidgetByRole(role); if (ft == 0) return; qreal ftX = ft->x(); qreal ftY = ft->y(); QPointF pos = calculateTextPosition(role); int relX = pos.x() - oldPosition.x(); int relY = pos.y() - oldPosition.y(); qreal ftNewX = ftX + relX; qreal ftNewY = ftY + relY; bool oldIgnoreSnapToGrid = ft->getIgnoreSnapToGrid(); ft->setIgnoreSnapToGrid(true); ft->setX(ftNewX); ft->setY(ftNewY); ft->setIgnoreSnapToGrid(oldIgnoreSnapToGrid); } /** * Remove dashed connecting line for association class. */ void AssociationWidget::removeAssocClassLine() { delete m_pAssocClassLineSel0; m_pAssocClassLineSel0 = 0; delete m_pAssocClassLineSel1; m_pAssocClassLineSel1 = 0; delete m_pAssocClassLine; m_pAssocClassLine = 0; if (m_associationClass) { m_associationClass->setClassAssociationWidget(0); m_associationClass = 0; } } /** * Creates the association class connecting line. */ void AssociationWidget::createAssocClassLine() { if (m_pAssocClassLine == 0) { m_pAssocClassLine = new QGraphicsLineItem(this); } QPen pen(lineColor(), lineWidth(), Qt::DashLine); m_pAssocClassLine->setPen(pen); // decoration points m_pAssocClassLineSel0 = Widget_Utils::decoratePoint(m_pAssocClassLine->line().p1(), m_pAssocClassLine); m_pAssocClassLineSel1 = Widget_Utils::decoratePoint(m_pAssocClassLine->line().p2(), m_pAssocClassLine); computeAssocClassLine(); selectAssocClassLine(false); } /** * Creates the association class connecting line using the specified * ClassifierWidget. * * @param classifier The ClassifierWidget to use. * @param linePathSegmentIndex The index of the segment where the * association class is created. */ void AssociationWidget::createAssocClassLine(ClassifierWidget* classifier, int linePathSegmentIndex) { m_nLinePathSegmentIndex = linePathSegmentIndex; if (m_nLinePathSegmentIndex < 0) { return; } m_associationClass = classifier; m_associationClass->setClassAssociationWidget(this); m_associationClass->addAssoc(this); // to get widgetMoved(...) for association classes createAssocClassLine(); } /** * Compute the end points of m_pAssocClassLine in case this * association has an attached association class. * TODO: The decoration points make no sense for now, because they are not movable. */ void AssociationWidget::computeAssocClassLine() { if (m_associationClass == 0 || m_pAssocClassLine == 0) { return; } if (m_nLinePathSegmentIndex < 0) { uError() << "m_nLinePathSegmentIndex is not set"; return; } QPointF segStart = m_associationLine->point(m_nLinePathSegmentIndex); QPointF segEnd = m_associationLine->point(m_nLinePathSegmentIndex + 1); const qreal midSegX = segStart.x() + (segEnd.x() - segStart.x()) / 2.0; const qreal midSegY = segStart.y() + (segEnd.y() - segStart.y()) / 2.0; QPointF segmentMidPoint(midSegX, midSegY); QLineF possibleAssocLine = QLineF(segmentMidPoint, m_associationClass->mapRectToScene(m_associationClass->rect()).center()); QPointF intersectionPoint; QLineF::IntersectType type = intersect(m_associationClass->mapRectToScene(m_associationClass->boundingRect()), possibleAssocLine, &intersectionPoint); // DEBUG(DBG_SRC) << "intersect type=" << type << " / point=" << intersectionPoint; if (type == QLineF::BoundedIntersection) { m_pAssocClassLine->setLine(midSegX, midSegY, intersectionPoint.x(), intersectionPoint.y()); if (m_pAssocClassLineSel0 && m_pAssocClassLineSel1) { m_pAssocClassLineSel0->setPos(m_pAssocClassLine->line().p1()); m_pAssocClassLineSel1->setPos(m_pAssocClassLine->line().p2()); } } } /** * Renders the association class connecting line selected. */ void AssociationWidget::selectAssocClassLine(bool sel) { if (m_pAssocClassLineSel0 && m_pAssocClassLineSel1) { m_pAssocClassLineSel0->setVisible(sel); m_pAssocClassLineSel1->setVisible(sel); } } /** * Sets the association to be selected. */ void AssociationWidget::mousePressEvent(QGraphicsSceneMouseEvent * me) { // clear other selected stuff on the screen of ShiftKey if (me->modifiers() != Qt::ShiftModifier) { m_scene->clearSelected(); } if (me->button() == Qt::LeftButton && me->modifiers() == Qt::ControlModifier) { if (checkRemovePoint(me->scenePos())) return; } // make sure we should be here depending on the button if (me->button() != Qt::RightButton && me->button() != Qt::LeftButton) { return; } QPointF mep = me->scenePos(); // see if `mep' is on the connecting line to the association class if (onAssocClassLine(mep)) { setSelected(true); selectAssocClassLine(); return; } setSelected(!isSelected()); associationLine()->mousePressEvent(me); } /** * Displays the right mouse buttom menu if right button is pressed. */ void AssociationWidget::mouseReleaseEvent(QGraphicsSceneMouseEvent * me) { associationLine()->mouseReleaseEvent(me); } /** * Handles the selection from the popup menu. */ void AssociationWidget::slotMenuSelection(QAction* action) { QString oldText, newText; bool ok = false; Uml::AssociationType::Enum atype = associationType(); Uml::RoleType::Enum r = RoleType::B; ListPopupMenu::MenuType sel = ListPopupMenu::typeFromAction(action); DEBUG(DBG_SRC) << "menu selection = " << ListPopupMenu::toString(sel); // if it's a collaboration message we now just use the code in floatingtextwidget // this means there's some redundant code below but that's better than duplicated code if (isCollaboration() && sel != ListPopupMenu::mt_Delete) { m_nameWidget->slotMenuSelection(action); return; } switch(sel) { case ListPopupMenu::mt_Properties: if (atype == AssociationType::Seq_Message || atype == AssociationType::Seq_Message_Self) { // show op dlg for seq. diagram here // don't worry about here, I don't think it can get here as // line is widget on seq. diagram // here just in case - remove later after testing DEBUG(DBG_SRC) << "mt_Properties: assoctype is " << atype; } else { //standard assoc dialog UMLApp::app()->docWindow()->updateDocumentation(false); showPropertiesDialog(); } break; case ListPopupMenu::mt_Add_Point: checkAddPoint(m_eventScenePos); break; case ListPopupMenu::mt_Delete_Point: checkRemovePoint(m_eventScenePos); break; case ListPopupMenu::mt_Delete: if (!Dialog_Utils::askDeleteAssociation()) break; if (m_pAssocClassLineSel0) removeAssocClassLine(); else if (association()) m_scene->removeAssocInViewAndDoc(this); else m_scene->removeWidgetCmd(this); break; case ListPopupMenu::mt_Rename_MultiA: r = RoleType::A; // fall through case ListPopupMenu::mt_Rename_MultiB: if (m_role[r].multiplicityWidget) oldText = m_role[r].multiplicityWidget->text(); else oldText = QString(); newText = oldText; ok = Dialog_Utils::askName(i18n("Multiplicity"), i18n("Enter multiplicity:"), newText); if (ok && newText != oldText) { if (FloatingTextWidget::isTextValid(newText)) { setMultiplicity(newText, r); } else { m_scene->removeWidget(m_role[r].multiplicityWidget); m_role[r].multiplicityWidget = 0; } } break; case ListPopupMenu::mt_Rename_Name: if (m_nameWidget) oldText = m_nameWidget->text(); else oldText = QString(); newText = oldText; ok = Dialog_Utils::askName(i18n("Association Name"), i18n("Enter association name:"), newText); if (ok && newText != oldText) { if (FloatingTextWidget::isTextValid(newText)) { setName(newText); } else if (m_nameWidget) { m_scene->removeWidget(m_nameWidget); m_nameWidget = 0; } } break; case ListPopupMenu::mt_Rename_RoleAName: r = RoleType::A; // fall through case ListPopupMenu::mt_Rename_RoleBName: if (m_role[r].roleWidget) oldText = m_role[r].roleWidget->text(); else oldText = QString(); newText = oldText; ok = Dialog_Utils::askName(i18n("Role Name"), i18n("Enter role name:"), newText); if (ok && newText != oldText) { if (FloatingTextWidget::isTextValid(newText)) { setRoleName(newText, r); } else { m_scene->removeWidget(m_role[r].roleWidget); m_role[r].roleWidget = 0; } } break; case ListPopupMenu::mt_Change_Font: { #if QT_VERSION >= 0x050000 bool ok = false; QFont fnt = QFontDialog::getFont(&ok, font(), m_scene->activeView()); if (ok) #else QFont fnt = font(); if (KFontDialog::getFont(fnt, KFontChooser::NoDisplayFlags, m_scene->activeView())) #endif lwSetFont(fnt); } break; case ListPopupMenu::mt_Line_Color: { #if QT_VERSION >= 0x050000 QColor newColor = QColorDialog::getColor(lineColor()); if (newColor != lineColor()) { #else QColor newColor; if (KColorDialog::getColor(newColor)) { #endif m_scene->selectionSetLineColor(newColor); umlDoc()->setModified(true); } } break; case ListPopupMenu::mt_Cut: m_scene->setStartedCut(); UMLApp::app()->slotEditCut(); break; case ListPopupMenu::mt_Copy: UMLApp::app()->slotEditCopy(); break; case ListPopupMenu::mt_Paste: UMLApp::app()->slotEditPaste(); break; case ListPopupMenu::mt_Reset_Label_Positions: resetTextPositions(); break; case ListPopupMenu::mt_LayoutDirect: m_associationLine->setLayout(AssociationLine::Direct); break; case ListPopupMenu::mt_LayoutSpline: m_associationLine->setLayout(AssociationLine::Spline); break; case ListPopupMenu::mt_LayoutOrthogonal: m_associationLine->setLayout(AssociationLine::Orthogonal); break; case ListPopupMenu::mt_LayoutPolyline: m_associationLine->setLayout(AssociationLine::Polyline); break; default: DEBUG(DBG_SRC) << "MenuType " << ListPopupMenu::toString(sel) << " not implemented"; break; }//end switch } /** * Return the first font found being used by any child widget. (They * could be different fonts, so this is a slightly misleading method.) */ QFont AssociationWidget::font() const { //:TODO: find a general font for the association QFont font; if (m_role[RoleType::A].roleWidget) font = m_role[RoleType::A].roleWidget->font(); else if (m_role[RoleType::B].roleWidget) font = m_role[RoleType::B].roleWidget->font(); else if (m_role[RoleType::A].multiplicityWidget) font = m_role[RoleType::A].multiplicityWidget->font(); else if (m_role[RoleType::B].multiplicityWidget) font = m_role[RoleType::B].multiplicityWidget->font(); else if (m_role[RoleType::A].changeabilityWidget) font = m_role[RoleType::A].changeabilityWidget->font(); else if (m_role[RoleType::B].changeabilityWidget) font = m_role[RoleType::B].changeabilityWidget->font(); else if (m_nameWidget) font = m_nameWidget->font(); else font = m_role[RoleType::A].umlWidget->font(); return font; } /** * Set all 'owned' child widgets to this text color. */ void AssociationWidget::setTextColor(const QColor &color) { WidgetBase::setTextColor(color); if (m_nameWidget) { m_nameWidget->setTextColor(color); } if (m_role[RoleType::A].roleWidget) { m_role[RoleType::A].roleWidget->setTextColor(color); } if (m_role[RoleType::B].roleWidget) { m_role[RoleType::B].roleWidget->setTextColor(color); } if (m_role[RoleType::A].multiplicityWidget) { m_role[RoleType::A].multiplicityWidget->setTextColor(color); } if (m_role[RoleType::B].multiplicityWidget) { m_role[RoleType::B].multiplicityWidget->setTextColor(color); } if (m_role[RoleType::A].changeabilityWidget) m_role[RoleType::A].changeabilityWidget->setTextColor(color); if (m_role[RoleType::B].changeabilityWidget) m_role[RoleType::B].changeabilityWidget->setTextColor(color); } void AssociationWidget::setLineColor(const QColor &color) { WidgetBase::setLineColor(color); QPen pen = m_associationLine->pen(); pen.setColor(color); m_associationLine->setPen(pen); } void AssociationWidget::setLineWidth(uint width) { WidgetBase::setLineWidth(width); QPen pen = m_associationLine->pen(); pen.setWidth(width); m_associationLine->setPen(pen); } bool AssociationWidget::checkAddPoint(const QPointF &scenePos) { if (associationType() == AssociationType::Exception) { return false; } // if there is no point around the mouse pointer, we insert a new one if (m_associationLine->closestPointIndex(scenePos) < 0) { int i = m_associationLine->closestSegmentIndex(scenePos); if (i < 0) { DEBUG(DBG_SRC) << "no closest segment found!"; return false; } m_associationLine->insertPoint(i + 1, scenePos); if (m_nLinePathSegmentIndex == i) { QPointF segStart = m_associationLine->point(i); QPointF segEnd = m_associationLine->point(i + 2); const int midSegX = segStart.x() + (segEnd.x() - segStart.x()) / 2; const int midSegY = segStart.y() + (segEnd.y() - segStart.y()) / 2; /* DEBUG(DBG_SRC) << "segStart=" << segStart << ", segEnd=" << segEnd << ", midSeg=(" << midSegX << "," << midSegY << "), mp=" << mp; */ if (midSegX > scenePos.x() || midSegY < scenePos.y()) { m_nLinePathSegmentIndex++; DEBUG(DBG_SRC) << "setting m_nLinePathSegmentIndex to " << m_nLinePathSegmentIndex; computeAssocClassLine(); } m_associationLine->update(); calculateNameTextSegment(); umlDoc()->setModified(true); setSelected(true); } return true; } else { DEBUG(DBG_SRC) << "found point already close enough!"; return false; } } /** * Remove point close to the given point and redraw the association. * @param scenePos point which should be removed * @return success status of the remove action */ bool AssociationWidget::checkRemovePoint(const QPointF &scenePos) { int i = m_associationLine->closestPointIndex(scenePos); if (i == -1) return false; m_associationLine->setSelected(false); // there was a point so we remove the point m_associationLine->removePoint(i); // Maybe reattach association class connecting line // to different association linepath segment. const int numberOfLines = m_associationLine->count() - 1; if (m_nLinePathSegmentIndex >= numberOfLines) { m_nLinePathSegmentIndex = numberOfLines - 1; } calculateEndingPoints(); // select the line path m_associationLine->setSelected(true); m_associationLine->update(); calculateNameTextSegment(); umlDoc()->setModified(true); return true; } /** * Moves the break point being dragged. */ void AssociationWidget::mouseMoveEvent(QGraphicsSceneMouseEvent* me) { if (me->buttons() != Qt::LeftButton) { return; } setSelected(true); associationLine()->mouseMoveEvent(me); moveEvent(me); m_scene->resizeSceneToItems(); } /** * Returns the Region the widget to line intersection is for the given * widget in this Association. If the given widget is not in the * Association then Region::Error is returned. * Used by @ref calculateEndingPoints to work these positions out for * another Association - since the number of Associations on the same * region for the same widget will mean the lines will need to be * spread out across the region. */ //Uml::Region::Enum AssociationWidget::getWidgetRegion(AssociationWidget * widget) const //{ // if (widget->widgetForRole(RoleType::A) == m_role[RoleType::A].umlWidget) // return m_role[RoleType::A].m_WidgetRegion; // if (widget->widgetForRole(RoleType::B) == m_role[RoleType::B].umlWidget) // return m_role[RoleType::B].m_WidgetRegion; // return Uml::Region::Error; //} /** * Returns the number of lines there are on the given region for * either widget A or B of the association. */ int AssociationWidget::getRegionCount(Uml::Region::Enum region, Uml::RoleType::Enum role) { if ((region == Uml::Region::Error) | (umlScene() == 0)) { return 0; } int widgetCount = 0; AssociationWidgetList list = m_scene->associationList(); foreach (AssociationWidget* assocwidget, list) { //don't count this association if (assocwidget == this) continue; const WidgetRole& otherA = assocwidget->m_role[RoleType::A]; const WidgetRole& otherB = assocwidget->m_role[RoleType::B]; const UMLWidget *a = otherA.umlWidget; const UMLWidget *b = otherB.umlWidget; /* //don't count associations to self if both of their end points are on the same region //they are different and placement won't interfere with them if (a == b && otherA.m_WidgetRegion == otherB.m_WidgetRegion) continue; */ if (m_role[role].umlWidget == a && region == otherA.m_WidgetRegion) widgetCount++; else if (m_role[role].umlWidget == b && region == otherB.m_WidgetRegion) widgetCount++; }//end foreach return widgetCount; } /** * Find the border point of the given rect when a line is drawn from the * given point to the rect. * @param rect rect of a classifier * @param line a line to the rect * @param intersectionPoint the intercept point on the border of the rect * @return the type of the intersection @ref QLineF::IntersectType */ QLineF::IntersectType AssociationWidget::intersect(const QRectF &rect, const QLineF &line, QPointF* intersectionPoint) { QList lines; lines << QLineF(rect.topLeft(), rect.topRight()); lines << QLineF(rect.topRight(), rect.bottomRight()); lines << QLineF(rect.bottomRight(), rect.bottomLeft()); lines << QLineF(rect.bottomLeft(), rect.topLeft()); foreach (const QLineF& rectLine, lines) { QLineF::IntersectType type = rectLine.intersect(line, intersectionPoint); if (type == QLineF::BoundedIntersection) { return type; } } return QLineF::NoIntersection; } /** * Given a rectangle and a point, findInterceptOnEdge computes the * connecting line between the middle point of the rectangle and * the point, and returns the intercept of this line with the * the edge of the rectangle identified by `region'. * When the region is North or South, the X value is returned (Y is * constant.) * When the region is East or West, the Y value is returned (X is * constant.) * @todo This is buggy. Try replacing by intersect() */ qreal AssociationWidget::findInterceptOnEdge(const QRectF &rect, Uml::Region::Enum region, const QPointF &point) { // The Qt coordinate system has (0, 0) in the top left corner. // In order to go to the regular XY coordinate system with (0, 0) // in the bottom left corner, we swap the X and Y axis. // That's why the following assignments look twisted. const qreal rectHalfWidth = rect.height() / 2.0; const qreal rectHalfHeight = rect.width() / 2.0; const qreal rectMidX = rect.y() + rectHalfWidth; const qreal rectMidY = rect.x() + rectHalfHeight; const qreal dX = rectMidX - point.y(); const qreal dY = rectMidY - point.x(); switch (region) { case Uml::Region::West: region = Uml::Region::South; break; case Uml::Region::North: region = Uml::Region::West; break; case Uml::Region::East: region = Uml::Region::North; break; case Uml::Region::South: region = Uml::Region::East; break; default: break; } // Now we have regular coordinates with the point (0, 0) in the // bottom left corner. if (region == Uml::Region::North || region == Uml::Region::South) { if (dX == 0) return rectMidY; // should be rectMidX, but we go back to Qt coord.sys. if (dY == 0) { uError() << "usage error: " << "North/South (dY == 0)"; return -1.0; } const qreal m = dY / dX; qreal relativeX; if (region == Uml::Region::North) relativeX = rectHalfHeight / m; else relativeX = -rectHalfHeight / m; return (rectMidY + relativeX); // should be rectMidX, but we go back to Qt coord.sys. } else { if (dY == 0) return rectMidX; // should be rectMidY, but we go back to Qt coord.sys. if (dX == 0) { uError() << "usage error: " << "East/West (dX == 0)"; return -1.0; } const qreal m = dY / dX; qreal relativeY = m * rectHalfWidth; if (region == Uml::Region::West) relativeY = -relativeY; return (rectMidX + relativeY); // should be rectMidY, but we go back to Qt coord.sys. } } /** * Auxiliary method for updateAssociations(): * Put position into m_positions and assoc into m_ordered at the * correct index. * m_positions and m_ordered move in parallel and are sorted by * ascending position. */ void AssociationWidget::insertIntoLists(qreal position, const AssociationWidget* assoc) { bool did_insertion = false; for (int index = 0; index < m_positions_len; ++index) { if (position < m_positions[index]) { for (int moveback = m_positions_len; moveback > index; moveback--) m_positions[moveback] = m_positions[moveback - 1]; m_positions[index] = position; m_ordered.insert(index, const_cast(assoc)); did_insertion = true; break; } } if (! did_insertion) { m_positions[m_positions_len] = position; m_ordered.append(const_cast(assoc)); } m_positions_len++; } /** * Tells all the other view associations the new count for the * given widget on a certain region. And also what index they should be. */ void AssociationWidget::updateAssociations(int totalCount, Uml::Region::Enum region, Uml::RoleType::Enum role) { if ((region == Uml::Region::Error) | (umlScene() == 0)) { return; } AssociationWidgetList list = m_scene->associationList(); UMLWidget *ownWidget = m_role[role].umlWidget; m_positions_len = 0; m_ordered.clear(); // we order the AssociationWidget list by region and x/y value foreach (AssociationWidget* assocwidget, list) { WidgetRole *roleA = &assocwidget->m_role[RoleType::A]; WidgetRole *roleB = &assocwidget->m_role[RoleType::B]; UMLWidget *wA = roleA->umlWidget; UMLWidget *wB = roleB->umlWidget; // Skip self associations. if (wA == wB) continue; // Now we must find out with which end the assocwidget connects // to the input widget (ownWidget). bool inWidgetARegion = (ownWidget == wA && region == roleA->m_WidgetRegion); bool inWidgetBRegion = (ownWidget == wB && region == roleB->m_WidgetRegion); if (!inWidgetARegion && !inWidgetBRegion) continue; // Determine intercept position on the edge indicated by `region'. UMLWidget * otherWidget = (inWidgetARegion ? wB : wA); AssociationLine *linepath = assocwidget->associationLine(); QPointF refpoint; if (assocwidget->linePathStartsAt(otherWidget)) refpoint = linepath->point(linepath->count() - 2); else refpoint = linepath->point(1); // The point is authoritative if we're called for the second time // (i.e. role==B) or it is a waypoint on the line path. bool pointIsAuthoritative = (role == RoleType::B || linepath->count() > 2); if (! pointIsAuthoritative) { // If the point is not authoritative then we use the other // widget's center. refpoint.setX(otherWidget->scenePos().x() + otherWidget->width() / 2); refpoint.setY(otherWidget->scenePos().y() + otherWidget->height() / 2); } qreal intercept = findInterceptOnEdge(ownWidget->rect(), region, refpoint); if (intercept < 0) { DEBUG(DBG_SRC) << "error from findInterceptOnEdge for" << " assocType=" << assocwidget->associationType() << " ownWidget=" << ownWidget->name() << " otherWidget=" << otherWidget->name(); continue; } insertIntoLists(intercept, assocwidget); } // while ((assocwidget = assoc_it.current())) // we now have an ordered list and we only have to call updateRegionLineCount int index = 1; foreach (AssociationWidget* assocwidget, m_ordered ) { if (ownWidget == assocwidget->widgetForRole(RoleType::A)) { assocwidget->updateRegionLineCount(index++, totalCount, region, RoleType::A); } else if (ownWidget == assocwidget->widgetForRole(RoleType::B)) { assocwidget->updateRegionLineCount(index++, totalCount, region, RoleType::B); } } // for (assocwidget = ordered.first(); ...) } /** * Called to tell the association that another association has added * a line to the region of one of its widgets. The widget is identified * by its role (A or B). * * Called by @ref updateAssociations which is called by * @ref calculateEndingPoints when required. */ void AssociationWidget::updateRegionLineCount(int index, int totalCount, Uml::Region::Enum region, Uml::RoleType::Enum role) { if ((region == Uml::Region::Error) | (umlScene() == 0)) { return; } // If the association is to self and the line ends are on the same region then // use a different calculation. if (isSelf() && m_role[RoleType::A].m_WidgetRegion == m_role[RoleType::B].m_WidgetRegion) { UMLWidget * pWidget = m_role[RoleType::A].umlWidget; qreal x = pWidget->scenePos().x(); qreal y = pWidget->scenePos().y(); qreal wh = pWidget->height(); qreal ww = pWidget->width(); int size = m_associationLine->count(); // See if above widget ok to place assoc. switch( m_role[RoleType::A].m_WidgetRegion ) { case Uml::Region::North: m_associationLine->setPoint( 0, QPointF( x + ( ww / 4 ), y ) ); m_associationLine->setPoint( size - 1, QPointF(x + ( ww * 3 / 4 ), y ) ); break; case Uml::Region::South: m_associationLine->setPoint( 0, QPointF( x + ( ww / 4 ), y + wh ) ); m_associationLine->setPoint( size - 1, QPointF( x + ( ww * 3 / 4 ), y + wh ) ); break; case Uml::Region::East: m_associationLine->setPoint( 0, QPointF( x + ww, y + ( wh / 4 ) ) ); m_associationLine->setPoint( size - 1, QPointF( x + ww, y + ( wh * 3 / 4 ) ) ); break; case Uml::Region::West: m_associationLine->setPoint( 0, QPointF( x, y + ( wh / 4 ) ) ); m_associationLine->setPoint( size - 1, QPointF( x, y + ( wh * 3 / 4 ) ) ); break; default: break; }//end switch return; } WidgetRole& robj = m_role[role]; UMLWidget * pWidget = robj.umlWidget; robj.m_nIndex = index; robj.m_nTotalCount = totalCount; qreal x = pWidget->scenePos().x(); qreal y = pWidget->scenePos().y(); qreal ww = pWidget->width(); qreal wh = pWidget->height(); const bool angular = Settings::optionState().generalState.angularlines; qreal ch = 0; qreal cw = 0; if (angular) { uint nind = (role == RoleType::A ? 1 : m_associationLine->count() - 2); QPointF neighbour = m_associationLine->point(nind); if (neighbour.x() < x) cw = 0; else if (neighbour.x() > x + ww) cw = 0 + ww; else cw = neighbour.x() - x; if (neighbour.y() < y) ch = 0; else if (neighbour.y() > y + wh) ch = 0 + wh; else ch = neighbour.y() - y; } else { ch = wh * index / totalCount; cw = ww * index / totalCount; } qreal newX = x + cw; qreal newY = y + ch; QPointF pt; if (angular) { pt = QPointF(newX, newY); } else { UMLWidget *pWidgetA = m_role[RoleType::A].umlWidget; UMLWidget *pWidgetB = m_role[RoleType::B].umlWidget; QList polyListA = pWidgetA->shape().toSubpathPolygons(); QPolygonF polyA = polyListA.at(0); if (polyListA.size() > 1) { for (int i = 1; i < polyListA.size(); i++) { polyA = polyA.united(polyListA.at(i)); } } polyA = polyA.translated(pWidgetA->pos()); QList polyListB = pWidgetB->shape().toSubpathPolygons(); QPolygonF polyB = polyListB.at(0); if (polyListB.size() > 1) { for (int i = 1; i < polyListB.size(); i++) { polyB = polyB.united(polyListB.at(i)); } } polyB = polyB.translated(pWidgetB->pos()); QLineF nearestPoints = Widget_Utils::closestPoints(polyA, polyB); if (nearestPoints.isNull()) { uError() << "Widget_Utils::closestPoints failed, falling back to simple widget positions"; switch(region) { case Uml::Region::West: pt.setX(x); pt.setY(newY); break; case Uml::Region::North: pt.setX(newX); pt.setY(y); break; case Uml::Region::East: pt.setX(x + ww); pt.setY(newY); break; case Uml::Region::South: pt.setX(newX); pt.setY(y + wh); break; case Uml::Region::Center: pt.setX(x + ww / 2); pt.setY(y + wh / 2); break; default: break; } } else { if (role == RoleType::A) pt = nearestPoints.p1(); else pt = nearestPoints.p2(); } } if (role == RoleType::A) { m_associationLine->setPoint(0, pt); } else { m_associationLine->setPoint(m_associationLine->count() - 1, pt); } } /** * Sets the state of whether the widget is selected. * * @param _select The state of whether the widget is selected. */ void AssociationWidget::setSelected(bool _select /* = true */) { WidgetBase::setSelected(_select); if ( m_nameWidget) m_nameWidget->setSelected( _select ); if ( m_role[RoleType::A].roleWidget ) m_role[RoleType::A].roleWidget->setSelected( _select ); if ( m_role[RoleType::B].roleWidget ) m_role[RoleType::B].roleWidget->setSelected( _select ); if ( m_role[RoleType::A].multiplicityWidget ) m_role[RoleType::A].multiplicityWidget->setSelected( _select ); if ( m_role[RoleType::B].multiplicityWidget ) m_role[RoleType::B].multiplicityWidget->setSelected( _select ); if ( m_role[RoleType::A].changeabilityWidget) m_role[RoleType::A].changeabilityWidget->setSelected( _select ); if ( m_role[RoleType::B].changeabilityWidget) m_role[RoleType::B].changeabilityWidget->setSelected( _select ); // Update the docwindow for this association. // This is done last because each of the above setSelected calls // overwrites the docwindow, but we want the main association doc // to win. if (_select) { UMLApp::app()->docWindow()->showDocumentation(this, false); } else UMLApp::app()->docWindow()->updateDocumentation(true); m_associationLine->setSelected(_select); if (! _select) { // For now, if _select is true we don't make the assoc class line // selected. But that's certainly open for discussion. // At any rate, we need to deselect the assoc class line // if _select is false. selectAssocClassLine(false); } UMLApp::app()->document()->writeToStatusBar(_select ? i18n("Press Ctrl with left mouse click to delete a point") : QString()); } /** * Reimplement method from WidgetBase in order to check owned floating texts. * * @param p Point to be checked. * * @return m_nameWidget if m_nameWidget is non NULL and m_nameWidget->onWidget(p) returns non 0; * m_role[0].(multiplicity|changeability|role)Widget if the resp. widget is non NULL and * its onWidget(p) returns non 0; * m_role[1].(multiplicity|changeability|role)Widget if the resp. widget is non NULL and * its onWidget(p) returns non 0; * else NULL. */ UMLWidget* AssociationWidget::onWidget(const QPointF &p) { if (m_nameWidget && m_nameWidget->onWidget(p)) { return m_nameWidget; } for (int i = 0; i <= 1; i++) { const WidgetRole& r = m_role[i]; if (r.multiplicityWidget && r.multiplicityWidget->onWidget(p)) return r.multiplicityWidget; if (r.changeabilityWidget && r.changeabilityWidget->onWidget(p)) return r.changeabilityWidget; if (r.roleWidget && r.roleWidget->onWidget(p)) return r.roleWidget; } return 0; } /** * Returns true if the given point is on the connecting line to * the association class. Returns false if there is no association * class attached, or if the given point is not on the connecting * line. */ bool AssociationWidget::onAssocClassLine(const QPointF &point) { bool onLine = false; if (m_pAssocClassLine) { //:TODO: // const QPointF mapped = m_pAssocClassLine->mapFromParent(point); // bool onLine = m_pAssocClassLine->contains(mapped); // return onLine; UMLSceneItemList list = m_scene->collisions(point); UMLSceneItemList::iterator end(list.end()); for (UMLSceneItemList::iterator item_it(list.begin()); item_it != end; ++item_it) { if (*item_it == m_pAssocClassLine) { onLine = true; break; } } } DEBUG(DBG_SRC) << onLine; return onLine; } /** * Returns true if the given point is on the association line. * A circle (rectangle) around the point is used to obtain more tolerance. * @param point the point to check * @return flag whether point is on association line */ bool AssociationWidget::onAssociation(const QPointF& point) { // check the path const qreal diameter(4.0); QPainterPath path = m_associationLine->shape(); if (path.contains(point)) { DEBUG(DBG_SRC) << "on path"; return true; } // check also the points if (m_associationLine->layout() == AssociationLine::Spline) { if (m_associationLine->closestPointIndex(point, diameter) > -1) { DEBUG(DBG_SRC) << "on spline point"; return true; } } return onAssocClassLine(point); } /** * Set all association points to x coordinate. */ void AssociationWidget::setXEntireAssoc(qreal x) { for (int i = 0; i < m_associationLine->count(); ++i) { QPointF p = m_associationLine->point(i); p.setX(x); m_associationLine->setPoint(i, p); } } /** * Set all association points to y coordinate. */ void AssociationWidget::setYEntireAssoc(qreal y) { for (int i = 0; i < m_associationLine->count(); ++i) { QPointF p = m_associationLine->point(i); p.setY(y); m_associationLine->setPoint(i, p); } } /** * Moves all the mid points (all expcept start /end) by the given amount. */ void AssociationWidget::moveMidPointsBy(qreal x, qreal y) { int pos = m_associationLine->count() - 1; for (int i = 1; i < (int)pos; ++i) { QPointF p = m_associationLine->point( i ); qreal newX = p.x() + x; qreal newY = p.y() + y; p.setX( newX ); p.setY( newY ); m_associationLine->setPoint( i, p ); } } /** * Moves the entire association by the given offset. */ void AssociationWidget::moveEntireAssoc(qreal x, qreal y) { //TODO: ADD SUPPORT FOR ASSOC. ON SEQ. DIAGRAMS WHEN NOTES BACK IN. moveMidPointsBy(x, y); // multi select if (umlScene()->selectedCount() > 1) { QPointF d(x, y); QPointF s = m_associationLine->startPoint() + d; QPointF e = m_associationLine->endPoint() + d; m_associationLine->setEndPoints(s, e); } calculateEndingPoints(); calculateNameTextSegment(); resetTextPositions(); } /** * Returns the bounding rectangle of all segments of the association. */ QRectF AssociationWidget::boundingRect() const { return m_associationLine->boundingRect(); } /** * Returns the shape of all segments of the association. */ QPainterPath AssociationWidget::shape() const { return m_associationLine->shape(); } /** * Connected to UMLClassifier::attributeRemoved() or UMLEntity::constraintRemoved() * in case this AssociationWidget is linked to a clasifier list item * (an attribute or a foreign key constraint) * * @param obj The UMLClassifierListItem removed. */ void AssociationWidget::slotClassifierListItemRemoved(UMLClassifierListItem* obj) { if (obj != m_umlObject) { DEBUG(DBG_SRC) << "obj=" << obj << ": m_umlObject=" << m_umlObject; return; } m_umlObject = 0; m_scene->removeWidgetCmd(this); } /** * Connected to UMLObject::modified() in case this * AssociationWidget is linked to a classifer's attribute type. */ void AssociationWidget::slotAttributeChanged() { UMLAttribute *attr = attribute(); if (attr == 0) { uError() << "attribute() returns NULL"; return; } setVisibility(attr->visibility(), RoleType::B); setRoleName(attr->name(), RoleType::B); } void AssociationWidget::clipSize() { if (m_nameWidget) m_nameWidget->clipSize(); if (m_role[RoleType::A].multiplicityWidget) m_role[RoleType::A].multiplicityWidget->clipSize(); if (m_role[RoleType::B].multiplicityWidget) m_role[RoleType::B].multiplicityWidget->clipSize(); if (m_role[RoleType::A].roleWidget) m_role[RoleType::A].roleWidget->clipSize(); if (m_role[RoleType::B].roleWidget) m_role[RoleType::B].roleWidget->clipSize(); if (m_role[RoleType::A].changeabilityWidget) m_role[RoleType::A].changeabilityWidget->clipSize(); if (m_role[RoleType::B].changeabilityWidget) m_role[RoleType::B].changeabilityWidget->clipSize(); if (m_associationClass) m_associationClass->clipSize(); } /** * Event handler for context menu events, called from the line segments. */ void AssociationWidget::contextMenuEvent(QGraphicsSceneContextMenuEvent *event) { event->accept(); UMLScene *scene = umlScene(); QWidget *parent = 0; if (scene) { parent = scene->activeView(); } if (!isSelected() && scene && !scene->selectedItems().isEmpty()) { Qt::KeyboardModifiers forSelection = (Qt::ControlModifier | Qt::ShiftModifier); if ((event->modifiers() & forSelection) == 0) { scene->clearSelection(); } } setSelected(true); m_eventScenePos = event->scenePos(); const Uml::AssociationType::Enum type = onAssocClassLine(event->scenePos()) ? Uml::AssociationType::Anchor : associationType(); AssociationWidgetPopupMenu popup(parent, type, this); QAction *triggered = popup.exec(event->screenPos()); slotMenuSelection(triggered); } /** * Reimplemented event handler for hover enter events. */ void AssociationWidget::hoverEnterEvent(QGraphicsSceneHoverEvent *event) { associationLine()->hoverEnterEvent(event); } /** * Reimplemented event handler for hover leave events. */ void AssociationWidget::hoverLeaveEvent(QGraphicsSceneHoverEvent *event) { associationLine()->hoverLeaveEvent(event); } /** * Reimplemented event handler for hover move events. */ void AssociationWidget::hoverMoveEvent(QGraphicsSceneHoverEvent *event) { associationLine()->hoverMoveEvent(event); } /** * Saves this widget to the "assocwidget" XMI element. */ void AssociationWidget::saveToXMI1(QDomDocument &qDoc, QDomElement &qElement) { QDomElement assocElement = qDoc.createElement(QLatin1String("assocwidget")); WidgetBase::saveToXMI1(qDoc, assocElement); LinkWidget::saveToXMI1(qDoc, assocElement); if (m_umlObject) { assocElement.setAttribute(QLatin1String("xmi.id"), Uml::ID::toString(m_umlObject->id())); } assocElement.setAttribute(QLatin1String("type"), associationType()); if (!association()) { assocElement.setAttribute(QLatin1String("visibilityA"), visibility(RoleType::A)); assocElement.setAttribute(QLatin1String("visibilityB"), visibility(RoleType::B)); assocElement.setAttribute(QLatin1String("changeabilityA"), changeability(RoleType::A)); assocElement.setAttribute(QLatin1String("changeabilityB"), changeability(RoleType::B)); if (m_umlObject == 0) { assocElement.setAttribute(QLatin1String("roleAdoc"), roleDocumentation(RoleType::A)); assocElement.setAttribute(QLatin1String("roleBdoc"), roleDocumentation(RoleType::B)); assocElement.setAttribute(QLatin1String("documentation"), documentation()); } } assocElement.setAttribute(QLatin1String("widgetaid"), Uml::ID::toString(widgetIDForRole(RoleType::A))); assocElement.setAttribute(QLatin1String("widgetbid"), Uml::ID::toString(widgetIDForRole(RoleType::B))); assocElement.setAttribute(QLatin1String("indexa"), m_role[RoleType::A].m_nIndex); assocElement.setAttribute(QLatin1String("indexb"), m_role[RoleType::B].m_nIndex); assocElement.setAttribute(QLatin1String("totalcounta"), m_role[RoleType::A].m_nTotalCount); assocElement.setAttribute(QLatin1String("totalcountb"), m_role[RoleType::B].m_nTotalCount); m_associationLine->saveToXMI1(qDoc, assocElement); if (m_nameWidget) { m_nameWidget->saveToXMI1(qDoc, assocElement); } if (multiplicityWidget(RoleType::A)) { multiplicityWidget(RoleType::A)->saveToXMI1(qDoc, assocElement); } if (multiplicityWidget(RoleType::B)) { multiplicityWidget(RoleType::B)->saveToXMI1(qDoc, assocElement); } if (roleWidget(RoleType::A)) { roleWidget(RoleType::A)->saveToXMI1(qDoc, assocElement); } if (roleWidget(RoleType::B)) { roleWidget(RoleType::B)->saveToXMI1(qDoc, assocElement); } if (changeabilityWidget(RoleType::A)) { changeabilityWidget(RoleType::A)->saveToXMI1(qDoc, assocElement); } if (changeabilityWidget(RoleType::B)) { changeabilityWidget(RoleType::B)->saveToXMI1(qDoc, assocElement); } if (m_associationClass) { QString acid = Uml::ID::toString(m_associationClass->id()); assocElement.setAttribute(QLatin1String("assocclass"), acid); assocElement.setAttribute(QLatin1String("aclsegindex"), m_nLinePathSegmentIndex); } qElement.appendChild(assocElement); } /** * Uses the supplied widgetList for resolving * the role A and role B widgets. (The other loadFromXMI1() queries * the UMLView for these widgets.) * Required for clipboard operations. */ bool AssociationWidget::loadFromXMI1(QDomElement& qElement, const UMLWidgetList& widgets, const MessageWidgetList* messages) { if (!WidgetBase::loadFromXMI1(qElement)) { return false; } if (!LinkWidget::loadFromXMI1(qElement)) { return false; } // load child widgets first QString widgetaid = qElement.attribute(QLatin1String("widgetaid"), QLatin1String("-1")); QString widgetbid = qElement.attribute(QLatin1String("widgetbid"), QLatin1String("-1")); Uml::ID::Type aId = Uml::ID::fromString(widgetaid); Uml::ID::Type bId = Uml::ID::fromString(widgetbid); UMLWidget *pWidgetA = Widget_Utils::findWidget(aId, widgets, messages); if (!pWidgetA) { uError() << "cannot find widget for roleA id " << Uml::ID::toString(aId); return false; } UMLWidget *pWidgetB = Widget_Utils::findWidget(bId, widgets, messages); if (!pWidgetB) { uError() << "cannot find widget for roleB id " << Uml::ID::toString(bId); return false; } setWidgetForRole(pWidgetA, RoleType::A); setWidgetForRole(pWidgetB, RoleType::B); QString type = qElement.attribute(QLatin1String("type"), QLatin1String("-1")); Uml::AssociationType::Enum aType = Uml::AssociationType::fromInt(type.toInt()); QString id = qElement.attribute(QLatin1String("xmi.id"), QLatin1String("-1")); bool oldStyleLoad = false; if (id == QLatin1String("-1")) { // xmi.id not present, ergo either a pure widget association, // or old (pre-1.2) style: // Everything is loaded from the AssociationWidget. // UMLAssociation may or may not be saved - if it is, it's a dummy. // Create the UMLAssociation if both roles are UML objects; // else load the info locally. if (Uml::AssociationType::hasUMLRepresentation(aType)) { // lack of an association in our widget AND presence of // both uml objects for each role clearly identifies this // as reading in an old-school file. Note it as such, and // create, and add, the UMLAssociation for this widget. // Remove this special code when backwards compatibility // with older files isn't important anymore. -b.t. UMLObject* umlRoleA = pWidgetA->umlObject(); UMLObject* umlRoleB = pWidgetB->umlObject(); if (!m_umlObject && umlRoleA && umlRoleB) { oldStyleLoad = true; // flag for further special config below if (aType == AssociationType::Aggregation || aType == AssociationType::Composition) { uWarning()<<" Old Style save file? swapping roles on association widget"<umlObject(); umlRoleB = pWidgetB->umlObject(); } setUMLAssociation(umlDoc()->createUMLAssociation(umlRoleA, umlRoleB, aType)); } } setDocumentation(qElement.attribute(QLatin1String("documentation"))); setRoleDocumentation(qElement.attribute(QLatin1String("roleAdoc")), RoleType::A); setRoleDocumentation(qElement.attribute(QLatin1String("roleBdoc")), RoleType::B); // visibility defaults to Public if it cant set it here.. QString visibilityA = qElement.attribute(QLatin1String("visibilityA"), QLatin1String("0")); int vis = visibilityA.toInt(); if (vis >= 200) { // bkwd compat. vis -= 200; } setVisibility((Uml::Visibility::Enum)vis, RoleType::A); QString visibilityB = qElement.attribute(QLatin1String("visibilityB"), QLatin1String("0")); vis = visibilityB.toInt(); if (vis >= 200) { // bkwd compat. vis -= 200; } setVisibility((Uml::Visibility::Enum)vis, RoleType::B); // Changeability defaults to "Changeable" if it cant set it here.. QString changeabilityA = qElement.attribute(QLatin1String("changeabilityA"), QLatin1String("0")); if (changeabilityA.toInt() > 0) setChangeability(Uml::Changeability::fromInt(changeabilityA.toInt()), RoleType::A); QString changeabilityB = qElement.attribute(QLatin1String("changeabilityB"), QLatin1String("0")); if (changeabilityB.toInt() > 0) setChangeability(Uml::Changeability::fromInt(changeabilityB.toInt()), RoleType::B); } else { // we should disconnect any prior association (can this happen??) if (m_umlObject && m_umlObject->baseType() == UMLObject::ot_Association) { UMLAssociation *umla = association(); umla->disconnect(this); umla->nrof_parent_widgets--; } // New style: The xmi.id is a reference to the UMLAssociation. // If the UMLObject is not found right now, we try again later // during the type resolution pass - see activate(). m_nId = Uml::ID::fromString(id); UMLObject *myObj = umlDoc()->findObjectById(m_nId); if (myObj) { const UMLObject::ObjectType ot = myObj->baseType(); if (ot != UMLObject::ot_Association) { setUMLObject(myObj); } else { UMLAssociation * myAssoc = myObj->asUMLAssociation(); setUMLAssociation(myAssoc); if (type == QLatin1String("-1")) aType = myAssoc->getAssocType(); } } } setAssociationType(aType); QString indexa = qElement.attribute(QLatin1String("indexa"), QLatin1String("0")); QString indexb = qElement.attribute(QLatin1String("indexb"), QLatin1String("0")); QString totalcounta = qElement.attribute(QLatin1String("totalcounta"), QLatin1String("0")); QString totalcountb = qElement.attribute(QLatin1String("totalcountb"), QLatin1String("0")); m_role[RoleType::A].m_nIndex = indexa.toInt(); m_role[RoleType::B].m_nIndex = indexb.toInt(); m_role[RoleType::A].m_nTotalCount = totalcounta.toInt(); m_role[RoleType::B].m_nTotalCount = totalcountb.toInt(); QString assocclassid = qElement.attribute(QLatin1String("assocclass")); if (! assocclassid.isEmpty()) { Uml::ID::Type acid = Uml::ID::fromString(assocclassid); UMLWidget *w = Widget_Utils::findWidget(acid, widgets); if (w) { ClassifierWidget* aclWidget = static_cast(w); QString aclSegIndex = qElement.attribute(QLatin1String("aclsegindex"), QLatin1String("0")); createAssocClassLine(aclWidget, aclSegIndex.toInt()); } else { uError() << "cannot find assocclass " << assocclassid; } } //now load child elements QDomNode node = qElement.firstChild(); QDomElement element = node.toElement(); while (!element.isNull()) { QString tag = element.tagName(); if (tag == QLatin1String("linepath")) { if (!m_associationLine->loadFromXMI1(element)) { return false; } } else if (tag == QLatin1String("floatingtext") || tag == QLatin1String("UML:FloatingTextWidget")) { // for bkwd compatibility QString r = element.attribute(QLatin1String("role"), QLatin1String("-1")); if (r == QLatin1String("-1")) return false; Uml::TextRole::Enum role = Uml::TextRole::fromInt(r.toInt()); FloatingTextWidget *ft = new FloatingTextWidget(m_scene, role, QString(), Uml::ID::Reserved); if (! ft->loadFromXMI1(element)) { // Most likely cause: The FloatingTextWidget is empty. delete ft; node = element.nextSibling(); element = node.toElement(); continue; } // always need this ft->setParentItem(this); ft->setLink(this); ft->setSequenceNumber(m_SequenceNumber); ft->setFontCmd(ft->font()); switch(role) { case Uml::TextRole::MultiA: m_role[RoleType::A].multiplicityWidget = ft; if (oldStyleLoad) setMultiplicity(m_role[RoleType::A].multiplicityWidget->text(), RoleType::A); break; case Uml::TextRole::MultiB: m_role[RoleType::B].multiplicityWidget = ft; if (oldStyleLoad) setMultiplicity(m_role[RoleType::B].multiplicityWidget->text(), RoleType::B); break; case Uml::TextRole::ChangeA: m_role[RoleType::A].changeabilityWidget = ft; break; case Uml::TextRole::ChangeB: m_role[RoleType::B].changeabilityWidget = ft; break; case Uml::TextRole::Name: m_nameWidget = ft; if (oldStyleLoad) setName(m_nameWidget->text()); break; case Uml::TextRole::Coll_Message: case Uml::TextRole::Coll_Message_Self: m_nameWidget = ft; ft->setLink(this); ft->setActivated(); if (FloatingTextWidget::isTextValid(ft->text())) ft->show(); else ft->hide(); break; case Uml::TextRole::RoleAName: m_role[RoleType::A].roleWidget = ft; setRoleName(ft->text(), RoleType::A); break; case Uml::TextRole::RoleBName: m_role[RoleType::B].roleWidget = ft; setRoleName(ft->text(), RoleType::B); break; default: DEBUG(DBG_SRC) << "unexpected FloatingTextWidget (TextRole::Enum " << role << ")"; delete ft; break; } } node = element.nextSibling(); element = node.toElement(); } return true; } /** * Queries the UMLView for resolving the role A and role B widgets. * .... */ bool AssociationWidget::loadFromXMI1(QDomElement& qElement) { UMLScene *scene = umlScene(); if (scene) { const UMLWidgetList& widgetList = scene->widgetList(); const MessageWidgetList& messageList = scene->messageList(); return loadFromXMI1(qElement, widgetList, &messageList); } else { DEBUG(DBG_SRC) << "This isn't on UMLScene yet, so can neither fetch" "messages nor widgets on umlscene"; return false; } } diff --git a/umbrello/umlwidgets/classifierwidget.cpp b/umbrello/umlwidgets/classifierwidget.cpp index e2c43af7d..d9aae4da4 100644 --- a/umbrello/umlwidgets/classifierwidget.cpp +++ b/umbrello/umlwidgets/classifierwidget.cpp @@ -1,1496 +1,1497 @@ /*************************************************************************** * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * copyright (C) 2004-2014 * * Umbrello UML Modeller Authors * ***************************************************************************/ // own header #include "classifierwidget.h" // app includes #include "floatingtextwidget.h" #include "associationwidget.h" #include "associationline.h" #include "classifier.h" #include "cmds.h" #include "debug_utils.h" #include "instance.h" #include "listpopupmenu.h" #include "object_factory.h" #include "operation.h" #include "template.h" #include "uml.h" #include "umldoc.h" #include "umlview.h" // qt includes #include DEBUG_REGISTER_DISABLED(ClassifierWidget) const int ClassifierWidget::CIRCLE_SIZE = 30; const int ClassifierWidget::SOCKET_INCREMENT = 10; /** * Constructs a ClassifierWidget. * * @param scene The parent of this ClassifierWidget. * @param c The UMLClassifier to represent. */ ClassifierWidget::ClassifierWidget(UMLScene * scene, UMLClassifier *c) : UMLWidget(scene, WidgetBase::wt_Class, c), m_pAssocWidget(0), m_pInterfaceName(0) { const Settings::OptionState& ops = m_scene->optionState(); setVisualPropertyCmd(ShowVisibility, ops.classState.showVisibility); setVisualPropertyCmd(ShowOperations, ops.classState.showOps); setVisualPropertyCmd(ShowPublicOnly, ops.classState.showPublicOnly); setVisualPropertyCmd(ShowPackage, ops.classState.showPackage); m_attributeSignature = Uml::SignatureType::ShowSig; /*:TODO: setVisualProperty(ShowOperationSignature, ops.classState.showOpSig); Cannot do that because we get "pure virtual method called". Open code: */ if(!ops.classState.showOpSig) { if (visualProperty(ShowVisibility)) m_operationSignature = Uml::SignatureType::NoSig; else m_operationSignature = Uml::SignatureType::NoSigNoVis; } else if (visualProperty(ShowVisibility)) m_operationSignature = Uml::SignatureType::ShowSig; else m_operationSignature = Uml::SignatureType::SigNoVis; setVisualPropertyCmd(ShowAttributes, ops.classState.showAtts); setVisualPropertyCmd(ShowStereotype, ops.classState.showStereoType); setVisualPropertyCmd(DrawAsCircle, false); setShowAttSigs(ops.classState.showAttSig); if (c && c->isInterface()) { setBaseType(WidgetBase::wt_Interface); m_visualProperties = ShowOperations | ShowVisibility | ShowStereotype; setShowStereotype(true); updateSignatureTypes(); } if (c && scene->type() == Uml::DiagramType::Object) { setBaseType(WidgetBase::wt_Instance); m_visualProperties = ShowAttributes; updateSignatureTypes(); } } /** * Constructs a ClassifierWidget. * * @param scene The parent of this ClassifierWidget. * @param o The UMLPackage to represent. */ ClassifierWidget::ClassifierWidget(UMLScene * scene, UMLPackage *o) : UMLWidget(scene, WidgetBase::wt_Package, o), m_pAssocWidget(0), m_pInterfaceName(0) { const Settings::OptionState& ops = m_scene->optionState(); setVisualPropertyCmd(ShowVisibility, ops.classState.showVisibility); setVisualPropertyCmd(ShowOperations, ops.classState.showOps); setVisualPropertyCmd(ShowPublicOnly, ops.classState.showPublicOnly); setVisualPropertyCmd(ShowPackage, ops.classState.showPackage); m_attributeSignature = Uml::SignatureType::ShowSig; if(!ops.classState.showOpSig) { if (visualProperty(ShowVisibility)) m_operationSignature = Uml::SignatureType::NoSig; else m_operationSignature = Uml::SignatureType::NoSigNoVis; } else if (visualProperty(ShowVisibility)) m_operationSignature = Uml::SignatureType::ShowSig; else m_operationSignature = Uml::SignatureType::SigNoVis; setVisualPropertyCmd(ShowAttributes, ops.classState.showAtts); setVisualPropertyCmd(ShowStereotype, ops.classState.showStereoType); setVisualPropertyCmd(DrawAsPackage, true); setShowAttSigs(ops.classState.showAttSig); } /** * Destructor. */ ClassifierWidget::~ClassifierWidget() { if (m_pAssocWidget) m_pAssocWidget->removeAssocClassLine(); if (m_pInterfaceName) { delete m_pInterfaceName; m_pInterfaceName = 0; } } /** * Return the UMLClassifier which this ClassifierWidget * represents. */ UMLClassifier *ClassifierWidget::classifier() const { return m_umlObject->asUMLClassifier(); } /** * @return the visual properties */ ClassifierWidget::VisualProperties ClassifierWidget::visualProperties() const { return m_visualProperties; } /** * Set an OR combination of properties stored in \a properties on this * widget. */ void ClassifierWidget::setVisualProperties(VisualProperties properties) { // Don't do anything if the argument is equal to current status. if (quint32(m_visualProperties) == quint32(properties)) { return; } m_visualProperties = properties; updateSignatureTypes(); } /** * @return The status of the property passed in. * * @note Use @ref attributeSignature() and @ref * operationSignature() to get signature status. This * method only indicates whether signature is visible or not. */ bool ClassifierWidget::visualProperty(VisualProperty property) const { if (property == ShowAttributeSignature) { return (m_attributeSignature == Uml::SignatureType::ShowSig || m_attributeSignature == Uml::SignatureType::SigNoVis); } else if(property == ShowOperationSignature) { return (m_operationSignature == Uml::SignatureType::ShowSig || m_operationSignature == Uml::SignatureType::SigNoVis); } return m_visualProperties.testFlag(property); } /** * A convenient method to set and reset individual VisualProperty * * Undo command. * * @param property The property to be set/reset. * @param enable True/false to set/reset. (default = true) * * @note This method handles ShowAttributeSignature and * ShowOperationSignature specially. */ void ClassifierWidget::setVisualProperty(VisualProperty property, bool enable) { if (visualProperty(property) != enable) { UMLApp::app()->executeCommand(new Uml::CmdChangeVisualProperty(this, property, enable)); } } /** * A convenient method to set and reset individual VisualProperty * * @param property The property to be set/reset. * @param enable True/false to set/reset. (default = true) * * @note This method handles ShowAttributeSignature and * ShowOperationSignature specially. */ void ClassifierWidget::setVisualPropertyCmd(VisualProperty property, bool enable) { // Handle ShowAttributeSignature and ShowOperationSignature // specially. if (property == ShowAttributeSignature) { if (!enable) { m_attributeSignature = visualProperty(ShowVisibility) ? Uml::SignatureType::NoSig : Uml::SignatureType::NoSigNoVis; } else { m_attributeSignature = visualProperty(ShowVisibility) ? Uml::SignatureType::ShowSig : Uml::SignatureType::SigNoVis; } //:TODO: updateTextItemGroups(); updateSignatureTypes(); } else if (property == ShowOperationSignature) { if (!enable) { m_operationSignature = visualProperty(ShowVisibility) ? Uml::SignatureType::NoSig : Uml::SignatureType::NoSigNoVis; } else { m_operationSignature = visualProperty(ShowVisibility) ? Uml::SignatureType::ShowSig : Uml::SignatureType::SigNoVis; } //:TODO: updateTextItemGroups(); updateSignatureTypes(); } else if (property == ShowStereotype) { // Now just update flag and use base method for actual work. if (enable) { m_visualProperties |= property; } else { m_visualProperties &= ~property; } setShowStereotype(enable); } else if (property == DrawAsCircle) { // Don't do anything if the flag status is same. if (visualProperty(property) == enable) return; if (enable) { m_visualProperties |= property; } else { m_visualProperties &= ~property; } setDrawAsCircle(enable); } // Some other flag. else { // Don't do anything if the flag status is same. if (visualProperty(property) == enable) { return; } // Call setVisualProperties appropriately based on enbable. if (enable) { setVisualProperties(visualProperties() | property); } else { setVisualProperties(visualProperties() & ~property); } } } /** * A convenient method to toggle individual VisualProperty of this * widget. * * @param property The property to be toggled. * * @note This method handles ShowAttributeSignature and * ShowOperationSignature specially. */ void ClassifierWidget::toggleVisualProperty(VisualProperty property) { bool oppositeStatus; if (property == ShowOperationSignature) { oppositeStatus = !(m_operationSignature == Uml::SignatureType::ShowSig || m_operationSignature == Uml::SignatureType::SigNoVis); } else if (property == ShowAttributeSignature) { oppositeStatus = !(m_attributeSignature == Uml::SignatureType::ShowSig || m_attributeSignature == Uml::SignatureType::SigNoVis); } else { oppositeStatus = !visualProperty(property); } DEBUG(DBG_SRC) << "VisualProperty: " << property << " to opposite status " << oppositeStatus; setVisualProperty(property, oppositeStatus); } /** * Updates m_operationSignature to match m_showVisibility. */ void ClassifierWidget::updateSignatureTypes() { //turn on scope if (visualProperty(ShowVisibility)) { if (m_operationSignature == Uml::SignatureType::NoSigNoVis) { m_operationSignature = Uml::SignatureType::NoSig; } else if (m_operationSignature == Uml::SignatureType::SigNoVis) { m_operationSignature = Uml::SignatureType::ShowSig; } } //turn off scope else { if (m_operationSignature == Uml::SignatureType::ShowSig) { m_operationSignature = Uml::SignatureType::SigNoVis; } else if (m_operationSignature == Uml::SignatureType::NoSig) { m_operationSignature = Uml::SignatureType::NoSigNoVis; } } if (visualProperty(ShowVisibility)) { if (m_attributeSignature == Uml::SignatureType::NoSigNoVis) m_attributeSignature = Uml::SignatureType::NoSig; else if (m_attributeSignature == Uml::SignatureType::SigNoVis) m_attributeSignature = Uml::SignatureType::ShowSig; } else { if (m_attributeSignature == Uml::SignatureType::ShowSig) m_attributeSignature = Uml::SignatureType::SigNoVis; else if(m_attributeSignature == Uml::SignatureType::NoSig) m_attributeSignature = Uml::SignatureType::NoSigNoVis; } updateGeometry(); update(); } /** * Returns whether to show attribute signatures. * Only applies when m_umlObject->getBaseType() is ot_Class. * * @return Status of how attribute signatures are shown. */ Uml::SignatureType::Enum ClassifierWidget::attributeSignature() const { return m_attributeSignature; } /** * Sets the type of signature to display for an attribute. * Only applies when m_umlObject->getBaseType() is ot_Class. * * @param sig Type of signature to display for an attribute. */ void ClassifierWidget::setAttributeSignature(Uml::SignatureType::Enum sig) { m_attributeSignature = sig; updateSignatureTypes(); updateGeometry(); update(); } /** * @return The Uml::SignatureType::Enum value for the operations. */ Uml::SignatureType::Enum ClassifierWidget::operationSignature() const { return m_operationSignature; } /** * Set the type of signature to display for an Operation * * @param sig Type of signature to display for an operation. */ void ClassifierWidget::setOperationSignature(Uml::SignatureType::Enum sig) { m_operationSignature = sig; updateSignatureTypes(); updateGeometry(); update(); } /** * Sets whether to show attribute signature * Only applies when m_umlObject->getBaseType() is ot_Class. * * @param _status True if attribute signatures shall be shown. */ void ClassifierWidget::setShowAttSigs(bool _status) { if(!_status) { if (visualProperty(ShowVisibility)) m_attributeSignature = Uml::SignatureType::NoSig; else m_attributeSignature = Uml::SignatureType::NoSigNoVis; } else if (visualProperty(ShowVisibility)) m_attributeSignature = Uml::SignatureType::ShowSig; else m_attributeSignature = Uml::SignatureType::SigNoVis; if (UMLApp::app()->document()->loading()) return; updateGeometry(); update(); } /** * Toggles whether to show attribute signatures. * Only applies when m_umlObject->getBaseType() is ot_Class. */ void ClassifierWidget::toggleShowAttSigs() { if (m_attributeSignature == Uml::SignatureType::ShowSig || m_attributeSignature == Uml::SignatureType::SigNoVis) { if (visualProperty(ShowVisibility)) { m_attributeSignature = Uml::SignatureType::NoSig; } else { m_attributeSignature = Uml::SignatureType::NoSigNoVis; } } else if (visualProperty(ShowVisibility)) { m_attributeSignature = Uml::SignatureType::ShowSig; } else { m_attributeSignature = Uml::SignatureType::SigNoVis; } updateGeometry(); update(); } /** * Return the number of displayed members of the given ObjectType. * Takes into consideration m_showPublicOnly but not other settings. */ int ClassifierWidget::displayedMembers(UMLObject::ObjectType ot) const { int count = 0; UMLClassifier *umlc = this->classifier(); if (!umlc) return count; UMLClassifierListItemList list = umlc->getFilteredList(ot); foreach (UMLClassifierListItem *m, list) { if (!(visualProperty(ShowPublicOnly) && m->visibility() != Uml::Visibility::Public)) count++; } return count; } /** * Overrides method from UMLWidget. */ QSizeF ClassifierWidget::minimumSize() const { return calculateSize(); } /** * Calculate content related size of widget. * Overrides method from UMLWidget. */ QSizeF ClassifierWidget::calculateSize(bool withExtensions /* = true */) const { if (!m_umlObject) { return UMLWidget::minimumSize(); } if (m_umlObject->baseType() == UMLObject::ot_Package) { return calculateAsPackageSize(); } UMLClassifier *umlc = this->classifier(); if (!umlc) { uError() << "Internal error - classifier() returns NULL"; return UMLWidget::minimumSize(); } if (umlc->isInterface() && visualProperty(DrawAsCircle)) { return calculateAsCircleSize(); } const bool showNameOnly = !visualProperty(ShowAttributes) && !visualProperty(ShowOperations) && !visualProperty(ShowDocumentation); const QFontMetrics &fm = getFontMetrics(UMLWidget::FT_NORMAL); const int fontHeight = fm.lineSpacing(); // width is the width of the longest 'word' int width = 0, height = 0; // consider stereotype if (visualProperty(ShowStereotype) && !m_umlObject->stereotype().isEmpty()) { height += fontHeight; // ... width const QFontMetrics &bfm = UMLWidget::getFontMetrics(UMLWidget::FT_BOLD); const int stereoWidth = bfm.size(0, m_umlObject->stereotype(true)).width(); if (stereoWidth > width) width = stereoWidth; } else if (showNameOnly) { height += defaultMargin; } // consider name height += fontHeight; // ... width QString name; UMLObject *o; if (m_umlObject && m_umlObject->isUMLInstance() && m_umlObject->asUMLInstance()->classifier()) o = m_umlObject->asUMLInstance()->classifier(); else o = m_umlObject; if (!o) name = m_Text; else if (visualProperty(ShowPackage)) name = o->fullyQualifiedName(); else name = o->name(); QString displayedName; if (m_umlObject->isUMLInstance()) displayedName = m_umlObject->name() + QLatin1String(" : ") + name; else displayedName = name; const UMLWidget::FontType nft = (m_umlObject->isAbstract() ? FT_BOLD_ITALIC : FT_BOLD); const int nameWidth = UMLWidget::getFontMetrics(nft).size(0, displayedName).width(); if (nameWidth > width) width = nameWidth; #ifdef ENABLE_WIDGET_SHOW_DOC // consider documentation if (visualProperty(ShowDocumentation)) { if (!documentation().isEmpty()) { QRect brect = fm.boundingRect(QRect(0, 0, this->width()-2*defaultMargin, this->height()-height), Qt::AlignLeft | Qt::AlignTop | Qt::TextWordWrap, documentation()); height += brect.height(); if (!visualProperty(ShowOperations) && !visualProperty(ShowAttributes)) { if (brect.width() >= width) width = brect.width(); } } else height += fontHeight / 2; } #endif // consider attributes if (visualProperty(ShowAttributes)) { const int numAtts = displayedAttributes(); if (numAtts > 0) { height += fontHeight * numAtts; // calculate width of the attributes UMLClassifierListItemList list = umlc->getFilteredList( m_umlObject->isUMLInstance() ? UMLObject::ot_InstanceAttribute : UMLObject::ot_Attribute); foreach (UMLClassifierListItem *a, list) { if (visualProperty(ShowPublicOnly) && a->visibility() != Uml::Visibility::Public) continue; const int attWidth = fm.size(0, a->toString(m_attributeSignature, visualProperty(ShowStereotype))).width(); if (attWidth > width) width = attWidth; } } else height += fontHeight / 2; } // consider operations if (visualProperty(ShowOperations)) { const int numOps = displayedOperations(); if (numOps > 0) { height += numOps * fontHeight; // ... width UMLOperationList list(umlc->getOpList()); foreach (UMLOperation* op, list) { if (visualProperty(ShowPublicOnly) && op->visibility() != Uml::Visibility::Public) continue; const QString displayedOp = op->toString(m_operationSignature, visualProperty(ShowStereotype)); UMLWidget::FontType oft; oft = (op->isAbstract() ? UMLWidget::FT_ITALIC : UMLWidget::FT_NORMAL); const int w = UMLWidget::getFontMetrics(oft).size(0, displayedOp).width(); if (w > width) width = w; } } else height += fontHeight / 2; } if (withExtensions) { // consider template box _as last_ ! QSize templatesBoxSize = calculateTemplatesBoxSize(); if (templatesBoxSize.width() != 0) { // add width to largest 'word' width += templatesBoxSize.width() / 2; } if (templatesBoxSize.height() != 0) { height += templatesBoxSize.height() - defaultMargin; } } // allow for height margin if (showNameOnly) { height += defaultMargin; } // allow for width margin width += defaultMargin * 2; return QSizeF(width, height); } /** * Calculcates the size of the templates box in the top left * if it exists, returns QSize(0, 0) if it doesn't. * * @return QSize of the templates flap. */ QSize ClassifierWidget::calculateTemplatesBoxSize() const { if (!classifier()) return QSize(0, 0); UMLTemplateList list = classifier()->getTemplateList(); int count = list.count(); if (count == 0) { return QSize(0, 0); } QFont font = UMLWidget::font(); font.setItalic(false); font.setUnderline(false); font.setBold(false); const QFontMetrics fm(font); int width = 0; int height = count * fm.lineSpacing() + (defaultMargin*2); foreach (UMLTemplate *t, list) { int textWidth = fm.size(0, t->toString(Uml::SignatureType::NoSig, visualProperty(ShowStereotype))).width(); if (textWidth > width) width = textWidth; } width += (defaultMargin*2); return QSize(width, height); } /** * Return the number of displayed attributes. */ int ClassifierWidget::displayedAttributes() const { if (!visualProperty(ShowAttributes)) return 0; if(baseType() == WidgetBase::wt_Instance) return displayedMembers(UMLObject::ot_InstanceAttribute); else return displayedMembers(UMLObject::ot_Attribute); } /** * Return the number of displayed operations. */ int ClassifierWidget::displayedOperations() const { if (!visualProperty(ShowOperations)) return 0; return displayedMembers(UMLObject::ot_Operation); } /** * Set the AssociationWidget when this ClassWidget acts as * an association class. */ void ClassifierWidget::setClassAssociationWidget(AssociationWidget *assocwidget) { if (!classifier()) { uError() << "Class association cannot be applied to package"; return; } m_pAssocWidget = assocwidget; UMLAssociation *umlassoc = 0; if (assocwidget) umlassoc = assocwidget->association(); classifier()->setClassAssoc(umlassoc); } /** * Return the AssociationWidget when this classifier acts as * an association class (else return NULL.) */ AssociationWidget *ClassifierWidget::classAssociationWidget() const { return m_pAssocWidget; } /** * Overrides standard method. * Auxiliary to reimplementations in the derived classes. * @note keep fetching attributes in sync with calculateSize() */ void ClassifierWidget::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { Q_UNUSED(option); Q_UNUSED(widget); setPenFromSettings(painter); if (UMLWidget::useFillColor()) painter->setBrush(UMLWidget::fillColor()); else { painter->setBrush(m_scene->backgroundColor()); } if (m_umlObject->baseType() == UMLObject::ot_Package) { drawAsPackage(painter, option); UMLWidget::paint(painter, option, widget); return; } UMLClassifier *umlc = this->classifier(); if (!umlc) { uError() << "Internal error - classifier() returns NULL"; return; } if (umlc->isInterface() && visualProperty(DrawAsCircle)) { drawAsCircle(painter, option); UMLWidget::paint(painter, option, widget); return; } // Draw the bounding rectangle QSize templatesBoxSize = calculateTemplatesBoxSize(); int bodyOffsetY = 0; if (templatesBoxSize.height() > 0) bodyOffsetY += templatesBoxSize.height() - defaultMargin; int w = width(); if (templatesBoxSize.width() > 0) w -= templatesBoxSize.width() / 2; int h = height(); if (templatesBoxSize.height() > 0) h -= templatesBoxSize.height() - defaultMargin; painter->drawRect(0, bodyOffsetY, w, h); QFont font = UMLWidget::font(); font.setUnderline(false); font.setItalic(false); const QFontMetrics &fm = UMLWidget::getFontMetrics(UMLWidget::FT_NORMAL); const int fontHeight = fm.lineSpacing(); //If there are any templates then draw them UMLTemplateList tlist = umlc->getTemplateList(); if (tlist.count() > 0) { setPenFromSettings(painter); QPen pen = painter->pen(); pen.setStyle(Qt::DotLine); painter->setPen(pen); painter->drawRect(width() - templatesBoxSize.width(), 0, templatesBoxSize.width(), templatesBoxSize.height()); painter->setPen(QPen(textColor())); font.setBold(false); painter->setFont(font); const int x = width() - templatesBoxSize.width() + defaultMargin; int y = defaultMargin; foreach (UMLTemplate *t, tlist) { QString text = t->toString(Uml::SignatureType::NoSig, visualProperty(ShowStereotype)); painter->drawText(x, y, fm.size(0, text).width(), fontHeight, Qt::AlignVCenter, text); y += fontHeight; } } const int textX = defaultMargin; const int textWidth = w - defaultMargin * 2; painter->setPen(QPen(textColor())); // draw stereotype font.setBold(true); const bool showNameOnly = !visualProperty(ShowAttributes) && !visualProperty(ShowOperations) && !visualProperty(ShowDocumentation); int nameHeight = fontHeight; if (visualProperty(ShowStereotype) && !m_umlObject->stereotype().isEmpty()) { painter->setFont(font); painter->drawText(textX, bodyOffsetY, textWidth, fontHeight, Qt::AlignCenter, m_umlObject->stereotype(true)); bodyOffsetY += fontHeight; } else if (showNameOnly) { nameHeight = h; } // draw name QString name; UMLObject *o; if (m_umlObject && m_umlObject->isUMLInstance() && m_umlObject->asUMLInstance()->classifier()) o = m_umlObject->asUMLInstance()->classifier(); else o = m_umlObject; if (!o) name = m_Text; else if (visualProperty(ShowPackage)) name = o->fullyQualifiedName(); else name = o->name(); QString displayedName; if (m_umlObject->isUMLInstance()) displayedName = m_umlObject->name() + QLatin1String(" : ") + name; else displayedName = name; font.setItalic(m_umlObject->isAbstract()); painter->setFont(font); painter->drawText(textX, bodyOffsetY, textWidth, nameHeight, Qt::AlignCenter, displayedName); bodyOffsetY += fontHeight; font.setBold(false); font.setItalic(false); painter->setFont(font); #ifdef ENABLE_WIDGET_SHOW_DOC // draw documentation if (visualProperty(ShowDocumentation)) { setPenFromSettings(painter); painter->drawLine(0, bodyOffsetY, w, bodyOffsetY); painter->setPen(textColor()); if (!documentation().isEmpty()) { QRect brect = fm.boundingRect(QRect(0, 0, w-2*defaultMargin, h-bodyOffsetY), Qt::AlignLeft | Qt::AlignTop | Qt::TextWordWrap, documentation()); if (brect.width() > width() + 2*defaultMargin) brect.setWidth(width()-2*defaultMargin); brect.adjust(textX, bodyOffsetY, textX, bodyOffsetY); painter->drawText(brect, Qt::AlignCenter | Qt::TextWordWrap, documentation()); bodyOffsetY += brect.height(); } else bodyOffsetY += fontHeight / 2; } #endif // draw attributes if (visualProperty(ShowAttributes)) { // draw dividing line between doc/name and attributes setPenFromSettings(painter); painter->drawLine(0, bodyOffsetY, w, bodyOffsetY); painter->setPen(textColor()); const int numAtts = displayedAttributes(); if (numAtts > 0) { if (baseType() == WidgetBase::wt_Instance) { drawMembers(painter, UMLObject::ot_InstanceAttribute, m_attributeSignature, textX, bodyOffsetY, fontHeight); } else { drawMembers(painter, UMLObject::ot_Attribute, m_attributeSignature, textX, bodyOffsetY, fontHeight); } bodyOffsetY += fontHeight * numAtts; } else bodyOffsetY += fontHeight / 2; } // draw operations if (visualProperty(ShowOperations)) { // draw dividing line between attributes and operations setPenFromSettings(painter); painter->drawLine(0, bodyOffsetY, w, bodyOffsetY); painter->setPen(QPen(textColor())); const int numOps = displayedOperations(); if (numOps >= 0) { drawMembers(painter, UMLObject::ot_Operation, m_operationSignature, textX, bodyOffsetY, fontHeight); } } UMLWidget::paint(painter, option, widget); } /** * @return The shape of the ClassifierWidget. */ QPainterPath ClassifierWidget::shape() const { QPainterPath path; if (classifier() && classifier()->isInterface() && visualProperty(DrawAsCircle)) { - path.addEllipse(rect()); + path.addEllipse(QRectF(QPointF(), calculateAsCircleSize())); return path; } QSizeF mainSize = rect().size(); QSize templatesBoxSize = calculateTemplatesBoxSize(); qreal mainY = 0.0; if (templatesBoxSize.height() > 0) { mainY += templatesBoxSize.height() - defaultMargin; path.addRect(QRectF(mainSize.width() - templatesBoxSize.width() / 2, 0.0, templatesBoxSize.width(), templatesBoxSize.height())); } path.addRect(QRectF(0.0, mainY, mainSize.width(), mainSize.height())); return path; } /** * Draws the interface as a circle. * Only applies when m_umlObject->getBaseType() is ot_Interface. */ void ClassifierWidget::drawAsCircle(QPainter *painter, const QStyleOptionGraphicsItem *option) { Q_UNUSED(option); const int w = width(); if (associationWidgetList().size() > 1) { painter->drawEllipse(w/2 - CIRCLE_SIZE/2, SOCKET_INCREMENT / 2, CIRCLE_SIZE, CIRCLE_SIZE); // Draw socket for required interface. const qreal angleSpan = 180; // 360.0 / (m_Assocs.size() + 1.0); const int arcDiameter = CIRCLE_SIZE + SOCKET_INCREMENT; QRect requireArc(w/2 - arcDiameter/2, 0, arcDiameter, arcDiameter); const QPointF center(x() + w/2, y() + arcDiameter/2); const qreal cX = center.x(); const qreal cY = center.y(); foreach (AssociationWidget *aw, associationWidgetList()) { const Uml::AssociationType::Enum aType = aw->associationType(); if (aType == Uml::AssociationType::UniAssociation || aType == Uml::AssociationType::Association) // provider continue; UMLWidget *otherEnd = aw->widgetForRole(Uml::RoleType::A); const WidgetBase::WidgetType oType = otherEnd->baseType(); if (oType != WidgetBase::wt_Component && oType != WidgetBase::wt_Port) continue; AssociationLine *assocLine = aw->associationLine(); const QPointF p(assocLine->endPoint()); const qreal tolerance = 18.0; bool drawArc = true; qreal midAngle; if (p.x() < cX - tolerance) { if (p.y() < cY - tolerance) midAngle = 135; else if (p.y() > cY + tolerance) midAngle = 225; else midAngle = 180; } else if (p.x() > cX + tolerance) { if (p.y() < cY - tolerance) midAngle = 45; else if (p.y() > cY + tolerance) midAngle = 315; else midAngle = 0; } else { if (p.y() < cY - tolerance) midAngle = 90; else if (p.y() > cY + tolerance) midAngle = 270; else drawArc = false; } if (drawArc) { // uDebug() << "number of assocs: " << m_Assocs.size() // << ", p: " << p << ", center: " << center // << ", midAngle: " << midAngle << ", angleSpan: " << angleSpan; painter->drawArc(requireArc, 16 * (midAngle - angleSpan/2), 16 * angleSpan); } else { uError() << "socket: assocLine endPoint " << p << " too close to own center" << center; } } } else painter->drawEllipse(w/2 - CIRCLE_SIZE/2, 0, CIRCLE_SIZE, CIRCLE_SIZE); } /** * Calculates the size of the object when drawn as a circle. * Only applies when m_umlObject->getBaseType() is ot_Interface. */ QSize ClassifierWidget::calculateAsCircleSize() const { int circleSize = CIRCLE_SIZE; if (associationWidgetList().size() > 1) circleSize += SOCKET_INCREMENT; return QSize(circleSize, circleSize); } void ClassifierWidget::drawAsPackage(QPainter *painter, const QStyleOptionGraphicsItem *option) { Q_UNUSED(option); int w = width(); int h = height(); QFont font = UMLWidget::font(); font.setBold(true); //FIXME italic is true when a package is first created until you click elsewhere, not sure why font.setItalic(false); const QFontMetrics &fm = getFontMetrics(FT_BOLD); const int fontHeight = fm.lineSpacing(); painter->drawRect(0, 0, 50, fontHeight); if (m_umlObject->stereotype() == QLatin1String("subsystem")) { const int fHalf = fontHeight / 2; const int symY = fHalf; const int symX = 38; painter->drawLine(symX, symY, symX, symY + fHalf - 2); // left leg painter->drawLine(symX + 8, symY, symX + 8, symY + fHalf - 2); // right leg painter->drawLine(symX, symY, symX + 8, symY); // waist painter->drawLine(symX + 4, symY, symX + 4, symY - fHalf + 2); // head } painter->drawRect(0, fontHeight - 1, w, h - fontHeight); painter->setPen(textColor()); painter->setFont(font); int lines = 1; QString stereotype = m_umlObject->stereotype(); if (!stereotype.isEmpty()) { painter->drawText(0, fontHeight + defaultMargin, w, fontHeight, Qt::AlignCenter, m_umlObject->stereotype(true)); lines = 2; } painter->drawText(0, (fontHeight*lines) + defaultMargin, w, fontHeight, Qt::AlignCenter, name()); } QSize ClassifierWidget::calculateAsPackageSize() const { const QFontMetrics &fm = getFontMetrics(FT_BOLD_ITALIC); const int fontHeight = fm.lineSpacing(); int lines = 1; int width = fm.width(m_umlObject->name()); int tempWidth = 0; if (!m_umlObject->stereotype().isEmpty()) { tempWidth = fm.width(m_umlObject->stereotype(true)); lines = 2; } if (tempWidth > width) width = tempWidth; width += defaultMargin * 2; if (width < 70) width = 70; // minumin width of 70 int height = (lines*fontHeight) + fontHeight + (defaultMargin * 2); return QSize(width, height); } /** * Auxiliary method for draw() of child classes: * Draw the attributes or operations. * * @param p QPainter to paint to. * @param ot Object type to draw, either ot_Attribute or ot_Operation. * @param sigType Governs details of the member display. * @param x X coordinate at which to draw the texts. * @param y Y coordinate at which text drawing commences. * @param fontHeight The font height. */ void ClassifierWidget::drawMembers(QPainter * painter, UMLObject::ObjectType ot, Uml::SignatureType::Enum sigType, int x, int y, int fontHeight) { UMLClassifier *umlc = classifier(); if (!umlc) { return; } QFont f = UMLWidget::font(); f.setBold(false); UMLClassifierListItemList list = umlc->getFilteredList(ot); painter->setClipping(true); painter->setClipRect(rect()); foreach (UMLClassifierListItem *obj, list) { if (visualProperty(ShowPublicOnly) && obj->visibility() != Uml::Visibility::Public) continue; QString text = obj->toString(sigType, visualProperty(ShowStereotype)); f.setItalic(obj->isAbstract()); f.setUnderline(obj->isStatic()); painter->setFont(f); QFontMetrics fontMetrics(f); painter->drawText(x, y, fontMetrics.size(0, text).width(), fontHeight, Qt::AlignVCenter, text); f.setItalic(false); f.setUnderline(false); painter->setFont(f); y += fontHeight; } painter->setClipping(false); } /** * Override method from UMLWidget in order to additionally check m_pInterfaceName. * * @param p Point to be checked. * * @return 'this' if UMLWidget::onWidget(p) returns non 0; * m_pInterfaceName if m_pName is non NULL and * m_pInterfaceName->onWidget(p) returns non 0; else NULL. */ UMLWidget* ClassifierWidget::onWidget(const QPointF &p) { if (UMLWidget::onWidget(p) != 0) return this; if (getDrawAsCircle() && m_pInterfaceName) { uDebug() << "floatingtext: " << m_pInterfaceName->text(); return m_pInterfaceName->onWidget(p); } return 0; } /** * Reimplement function from UMLWidget. */ UMLWidget* ClassifierWidget::widgetWithID(Uml::ID::Type id) { if (UMLWidget::widgetWithID(id)) return this; if (getDrawAsCircle() && m_pInterfaceName && m_pInterfaceName->widgetWithID(id)) return m_pInterfaceName; return 0; } void ClassifierWidget::setDocumentation(const QString &doc) { WidgetBase::setDocumentation(doc); updateGeometry(); } /** * Sets whether to draw as circle. * Only applies when m_umlObject->getBaseType() is ot_Interface. * * @param drawAsCircle True if widget shall be drawn as circle. */ void ClassifierWidget::setDrawAsCircle(bool drawAsCircle) { setVisualPropertyCmd(DrawAsCircle, drawAsCircle); const int circleSize = CIRCLE_SIZE + SOCKET_INCREMENT; if (drawAsCircle) { setX(x() + (width()/2 - circleSize/2)); setY(y() + (height()/2 - circleSize/2)); setSize(circleSize, circleSize); if (m_pInterfaceName) { m_pInterfaceName->show(); } else { m_pInterfaceName = new FloatingTextWidget(m_scene, Uml::TextRole::Floating, name()); m_pInterfaceName->setParentItem(this); m_pInterfaceName->setText(name()); // to get geometry update m_pInterfaceName->setX(circleSize/2 - m_pInterfaceName->width() / 2); m_pInterfaceName->setY(circleSize + SOCKET_INCREMENT); } m_resizable = false; } else { setSize(ClassifierWidget::minimumSize()); setX(x() - (width()/2 - circleSize/2)); setY(y() - (height()/2 - circleSize/2)); if (m_pInterfaceName) m_pInterfaceName->hide(); m_resizable = true; } + setChangesShape(drawAsCircle); updateGeometry(); update(); } /** * Returns whether to draw as circle. * Only applies when m_umlObject->getBaseType() is ot_Interface. * * @return True if widget is drawn as circle. */ bool ClassifierWidget::getDrawAsCircle() const { return visualProperty(DrawAsCircle); } /** * Toggles whether to draw as circle. * Only applies when m_umlObject->getBaseType() is ot_Interface. */ void ClassifierWidget::toggleDrawAsCircle() { toggleVisualProperty(DrawAsCircle); updateSignatureTypes(); updateGeometry(); update(); } /** * Changes this classifier from an interface to a class. * Attributes and stereotype visibility is got from the view OptionState. * This widget is also updated. */ void ClassifierWidget::changeToClass() { setBaseType(WidgetBase::wt_Class); m_umlObject->setBaseType(UMLObject::ot_Class); setVisualPropertyCmd(DrawAsCircle, false); const Settings::OptionState& ops = m_scene->optionState(); setVisualProperty(ShowAttributes, ops.classState.showAtts); setVisualProperty(ShowStereotype, ops.classState.showStereoType); updateGeometry(); update(); } /** * Changes this classifier from a class to an interface. * Attributes are hidden and stereotype is shown. * This widget is also updated. */ void ClassifierWidget::changeToInterface() { setBaseType(WidgetBase::wt_Interface); m_umlObject->setBaseType(UMLObject::ot_Interface); setVisualProperty(ShowAttributes, false); setVisualProperty(ShowStereotype, true); updateGeometry(); update(); } /** * Changes this classifier from an "class-or-package" to a package. * This widget is also updated. */ void ClassifierWidget::changeToPackage() { setBaseType(WidgetBase::wt_Package); m_umlObject->setBaseType(UMLObject::ot_Package); setVisualProperty(ShowAttributes, false); setVisualProperty(ShowStereotype, true); updateGeometry(); update(); } /** * Extends base method to adjust also the association of a class * association. * Executes the base method and then, if file isn't loading and the * classifier acts as a class association, the association position is * updated. * TODO: This is never called. * * param x The x-coordinate. * param y The y-coordinate. */ //void ClassifierWidget::adjustAssociations(int x, int y) //{ // DEBUG(DBG_SRC) << "x=" << x << " / y=" << y; // UMLWidget::adjustAssocs(x, y); // if (m_doc->loading() || m_pAssocWidget == 0) { // return; // } // //:TODO: the following is also called from UMLWidgetr::ajdustAssocs(...) // // and then AssociationWidget::widgetMoved(...) // //m_pAssocWidget->computeAssocClassLine(); //} /** * Loads the "classwidget" or "interfacewidget" XML element. */ bool ClassifierWidget::loadFromXMI1(QDomElement & qElement) { if (!UMLWidget::loadFromXMI1(qElement)) { return false; } bool loadShowAttributes = true; if (umlObject() && (umlObject()->isUMLPackage() || umlObject()->isUMLInstance())) { loadShowAttributes = false; } if (loadShowAttributes) { QString showatts = qElement.attribute(QLatin1String("showattributes"), QLatin1String("0")); QString showops = qElement.attribute(QLatin1String("showoperations"), QLatin1String("1")); QString showpubliconly = qElement.attribute(QLatin1String("showpubliconly"), QLatin1String("0")); QString showattsigs = qElement.attribute(QLatin1String("showattsigs"), QLatin1String("600")); QString showopsigs = qElement.attribute(QLatin1String("showopsigs"), QLatin1String("600")); QString showpackage = qElement.attribute(QLatin1String("showpackage"), QLatin1String("0")); QString showscope = qElement.attribute(QLatin1String("showscope"), QLatin1String("0")); QString drawascircle = qElement.attribute(QLatin1String("drawascircle"), QLatin1String("0")); QString showstereotype = qElement.attribute(QLatin1String("showstereotype"), QLatin1String("1")); setVisualPropertyCmd(ShowAttributes, (bool)showatts.toInt()); setVisualPropertyCmd(ShowOperations, (bool)showops.toInt()); setVisualPropertyCmd(ShowPublicOnly, (bool)showpubliconly.toInt()); setVisualPropertyCmd(ShowPackage, (bool)showpackage.toInt()); setVisualPropertyCmd(ShowVisibility, (bool)showscope.toInt()); setVisualPropertyCmd(DrawAsCircle, (bool)drawascircle.toInt()); setVisualPropertyCmd(ShowStereotype, (bool)showstereotype.toInt()); m_attributeSignature = Uml::SignatureType::fromInt(showattsigs.toInt()); m_operationSignature = Uml::SignatureType::fromInt(showopsigs.toInt()); } #ifdef ENABLE_WIDGET_SHOW_DOC QString showDocumentation = qElement.attribute(QLatin1String("showdocumentation"), QLatin1String("0")); setVisualPropertyCmd(ShowDocumentation, (bool)showDocumentation.toInt()); #endif if (!getDrawAsCircle()) return true; // Optional child element: floatingtext QDomNode node = qElement.firstChild(); QDomElement element = node.toElement(); if (!element.isNull()) { QString tag = element.tagName(); if (tag == QLatin1String("floatingtext")) { if (m_pInterfaceName == 0) { m_pInterfaceName = new FloatingTextWidget(m_scene, Uml::TextRole::Floating, name(), Uml::ID::Reserved); m_pInterfaceName->setParentItem(this); } if (!m_pInterfaceName->loadFromXMI1(element)) { // Most likely cause: The FloatingTextWidget is empty. delete m_pInterfaceName; m_pInterfaceName = 0; } else { m_pInterfaceName->activate(); m_pInterfaceName->update(); } } else { uError() << "unknown tag " << tag; } } return true; } /** * Creates the "classwidget" or "interfacewidget" XML element. */ void ClassifierWidget::saveToXMI1(QDomDocument & qDoc, QDomElement & qElement) { QDomElement conceptElement; bool saveShowAttributes = true; UMLClassifier *umlc = classifier(); if (umlObject() && umlObject()->baseType() == UMLObject::ot_Package) { conceptElement = qDoc.createElement(QLatin1String("packagewidget")); saveShowAttributes = false; } else if(umlObject()->baseType() == UMLObject::ot_Instance) { conceptElement = qDoc.createElement(QLatin1String("instancewidget")); saveShowAttributes = false; } else if (umlc && umlc->isInterface()) { conceptElement = qDoc.createElement(QLatin1String("interfacewidget")); } else { conceptElement = qDoc.createElement(QLatin1String("classwidget")); } UMLWidget::saveToXMI1(qDoc, conceptElement); if (saveShowAttributes) { conceptElement.setAttribute(QLatin1String("showoperations"), visualProperty(ShowOperations)); conceptElement.setAttribute(QLatin1String("showpubliconly"), visualProperty(ShowPublicOnly)); conceptElement.setAttribute(QLatin1String("showopsigs"), m_operationSignature); conceptElement.setAttribute(QLatin1String("showpackage"), visualProperty(ShowPackage)); conceptElement.setAttribute(QLatin1String("showscope"), visualProperty(ShowVisibility)); conceptElement.setAttribute(QLatin1String("showattributes"), visualProperty(ShowAttributes)); conceptElement.setAttribute(QLatin1String("showattsigs"), m_attributeSignature); conceptElement.setAttribute(QLatin1String("showstereotype"), visualProperty(ShowStereotype)); } #ifdef ENABLE_WIDGET_SHOW_DOC conceptElement.setAttribute(QLatin1String("showdocumentation"),visualProperty(ShowDocumentation)); #endif if (umlc && (umlc->isInterface() || umlc->isAbstract())) { conceptElement.setAttribute(QLatin1String("drawascircle"), visualProperty(DrawAsCircle)); if (visualProperty(DrawAsCircle) && m_pInterfaceName) { m_pInterfaceName->saveToXMI1(qDoc, conceptElement); } } qElement.appendChild(conceptElement); } /** * Will be called when a menu selection has been made from the * popup menu. * * @param action The action that has been selected. */ void ClassifierWidget::slotMenuSelection(QAction* action) { ListPopupMenu::MenuType sel = ListPopupMenu::typeFromAction(action); switch (sel) { case ListPopupMenu::mt_Attribute: case ListPopupMenu::mt_Operation: case ListPopupMenu::mt_Template: case ListPopupMenu::mt_InstanceAttribute: { UMLObject::ObjectType ot = ListPopupMenu::convert_MT_OT(sel); UMLClassifier *umlc = classifier(); if (!umlc) { uError() << "Internal error - classifier() returns NULL"; return; } if (Object_Factory::createChildObject(umlc, ot)) { updateGeometry(); update(); UMLApp::app()->document()->setModified(); } break; } case ListPopupMenu::mt_Class: case ListPopupMenu::mt_Datatype: case ListPopupMenu::mt_Enum: case ListPopupMenu::mt_Interface: { UMLObject::ObjectType ot = ListPopupMenu::convert_MT_OT(sel); UMLClassifier *umlc = classifier(); if (!umlc) { uError() << "Internal error - classifier() returns NULL"; return; } umlScene()->setCreateObject(true); if (Object_Factory::createUMLObject(ot, QString(), umlc)) { updateGeometry(); update(); UMLApp::app()->document()->setModified(); } break; } case ListPopupMenu::mt_Show_Operations: toggleVisualProperty(ShowOperations); break; case ListPopupMenu::mt_Show_Attributes: toggleVisualProperty(ShowAttributes); break; case ListPopupMenu::mt_Show_Documentation: toggleVisualProperty(ShowDocumentation); break; case ListPopupMenu::mt_Show_Public_Only: toggleVisualProperty(ShowPublicOnly); break; case ListPopupMenu::mt_Show_Operation_Signature: toggleVisualProperty(ShowOperationSignature); break; case ListPopupMenu::mt_Show_Attribute_Signature: toggleVisualProperty(ShowAttributeSignature); break; case ListPopupMenu::mt_Visibility: toggleVisualProperty(ShowVisibility); break; case ListPopupMenu::mt_Show_Packages: toggleVisualProperty(ShowPackage); break; case ListPopupMenu::mt_Show_Stereotypes: toggleVisualProperty(ShowStereotype); break; case ListPopupMenu::mt_DrawAsCircle: toggleVisualProperty(DrawAsCircle); break; case ListPopupMenu::mt_ChangeToClass: changeToClass(); break; case ListPopupMenu::mt_ChangeToInterface: changeToInterface(); break; case ListPopupMenu::mt_ChangeToPackage: changeToPackage(); break; default: UMLWidget::slotMenuSelection(action); break; } } /** * Slot to show/hide attributes based on \a state. */ void ClassifierWidget::slotShowAttributes(bool state) { setVisualProperty(ShowAttributes, state); } /** * Slot to show/hide operations based on \a state. */ void ClassifierWidget::slotShowOperations(bool state) { setVisualProperty(ShowOperations, state); } diff --git a/umbrello/umlwidgets/umlwidget.cpp b/umbrello/umlwidgets/umlwidget.cpp index b01a8f8d5..0e018adf6 100644 --- a/umbrello/umlwidgets/umlwidget.cpp +++ b/umbrello/umlwidgets/umlwidget.cpp @@ -1,2017 +1,2018 @@ /*************************************************************************** * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * copyright (C) 2002-2014 * * Umbrello UML Modeller Authors * ***************************************************************************/ #include "umlwidget.h" // local includes #include "actor.h" #include "actorwidget.h" #include "associationwidget.h" #include "classifier.h" #include "classpropertiesdialog.h" #include "cmds.h" #include "debug_utils.h" #include "dialog_utils.h" #include "docwindow.h" #include "associationwidget.h" #include "floatingtextwidget.h" #include "notewidget.h" #include "object_factory.h" #include "idchangelog.h" #include "menus/listpopupmenu.h" #include "port.h" #include "portwidget.h" #include "settingsdialog.h" #include "uml.h" #include "umldoc.h" #include "umllistview.h" #include "umlobject.h" #include "umlscene.h" #include "umlview.h" #include "usecase.h" #include "usecasewidget.h" #include "uniqueid.h" #include "widget_factory.h" // kde includes #include #include // qt includes #include #include #include #include using namespace Uml; DEBUG_REGISTER_DISABLED(UMLWidget) const QSizeF UMLWidget::DefaultMinimumSize(50, 20); const QSizeF UMLWidget::DefaultMaximumSize(1000, 5000); const int UMLWidget::defaultMargin = 5; const int UMLWidget::selectionMarkerSize = 4; const int UMLWidget::resizeMarkerLineCount = 3; /** * Creates a UMLWidget object. * * @param scene The view to be displayed on. * @param type The WidgetType to construct. * This must be set to the appropriate value by the constructors of inheriting classes. * @param o The UMLObject to represent. */ UMLWidget::UMLWidget(UMLScene * scene, WidgetType type, UMLObject * o) : WidgetBase(scene, type) { init(); m_umlObject = o; if (m_umlObject) { connect(m_umlObject, SIGNAL(modified()), this, SLOT(updateWidget())); m_nId = m_umlObject->id(); } } /** * Creates a UMLWidget object. * * @param scene The view to be displayed on. * @param type The WidgetType to construct. * This must be set to the appropriate value by the constructors of inheriting classes. * @param id The id of the widget. * The default value (id_None) will prompt generation of a new ID. */ UMLWidget::UMLWidget(UMLScene *scene, WidgetType type, Uml::ID::Type id) : WidgetBase(scene, type) { init(); if (id == Uml::ID::None) m_nId = UniqueID::gen(); else m_nId = id; } /** * Destructor. */ UMLWidget::~UMLWidget() { cleanup(); } /** * Assignment operator */ UMLWidget& UMLWidget::operator=(const UMLWidget & other) { if (this == &other) return *this; WidgetBase::operator=(other); // assign members loaded/saved m_useFillColor = other.m_useFillColor; m_usesDiagramFillColor = other.m_usesDiagramFillColor; m_usesDiagramUseFillColor = other.m_usesDiagramUseFillColor; m_fillColor = other.m_fillColor; m_Assocs = other.m_Assocs; m_isInstance = other.m_isInstance; m_instanceName = other.m_instanceName; m_instanceName = other.m_instanceName; m_showStereotype = other.m_showStereotype; setX(other.x()); setY(other.y()); setRect(rect().x(), rect().y(), other.width(), other.height()); // assign volatile (non-saved) members m_startMove = other.m_startMove; m_nPosX = other.m_nPosX; m_doc = other.m_doc; //new m_resizable = other.m_resizable; for (unsigned i = 0; i < FT_INVALID; ++i) m_pFontMetrics[i] = other.m_pFontMetrics[i]; m_activated = other.m_activated; m_ignoreSnapToGrid = other.m_ignoreSnapToGrid; m_ignoreSnapComponentSizeToGrid = other.m_ignoreSnapComponentSizeToGrid; return *this; } /** * Overload '==' operator */ bool UMLWidget::operator==(const UMLWidget& other) const { if (this == &other) return true; if (baseType() != other.baseType()) { return false; } if (id() != other.id()) return false; /* Testing the associations is already an exaggeration, no? The type and ID should uniquely identify an UMLWidget. */ if (m_Assocs.count() != other.m_Assocs.count()) { return false; } // if(getBaseType() != wt_Text) // DON'T do this for floatingtext widgets, an infinite loop will result // { AssociationWidgetListIt assoc_it(m_Assocs); AssociationWidgetListIt assoc_it2(other.m_Assocs); AssociationWidget * assoc = 0, *assoc2 = 0; while (assoc_it.hasNext() && assoc_it2.hasNext()) { assoc = assoc_it.next(); assoc2 = assoc_it2.next(); if (!(*assoc == *assoc2)) { return false; } } // } return true; // NOTE: In the comparison tests we are going to do, we don't need these values. // They will actually stop things functioning correctly so if you change these, be aware of that. /* if(m_useFillColor != other.m_useFillColor) return false; if(m_nId != other.m_nId) return false; if(m_nX != other.m_nX) return false; if(m_nY != other.m_nY) return false; */ } /** * Sets the local id of the object. * * @param id The local id of the object. */ void UMLWidget::setLocalID(Uml::ID::Type id) { m_nLocalID = id; } /** * Returns the local ID for this object. This ID is used so that * many objects of the same @ref UMLObject instance can be on the * same diagram. * * @return The local ID. */ Uml::ID::Type UMLWidget::localID() const { return m_nLocalID; } /** * Returns the widget with the given ID. * The default implementation tests the following IDs: * - m_nLocalID * - if m_umlObject is non NULL: m_umlObject->id() * - m_nID * Composite widgets override this function to test further owned widgets. * * @param id The ID to test this widget against. * @return 'this' if id is either of m_nLocalID, m_umlObject->id(), or m_nId; * else NULL. */ UMLWidget* UMLWidget::widgetWithID(Uml::ID::Type id) { if (id == m_nLocalID || (m_umlObject != 0 && id == m_umlObject->id()) || id == m_nId) return this; return 0; } /** * Compute the minimum possible width and height. * * @return QSizeF(mininum_width, minimum_height) */ QSizeF UMLWidget::minimumSize() const { return m_minimumSize; } /** * This method is used to set the minimum size variable for this * widget. * * @param newSize The size being set as minimum. */ void UMLWidget::setMinimumSize(const QSizeF& newSize) { m_minimumSize = newSize; } /** * Compute the maximum possible width and height. * * @return maximum size */ QSizeF UMLWidget::maximumSize() { return m_maximumSize; } /** * This method is used to set the maximum size variable for this * widget. * * @param newSize The size being set as maximum. */ void UMLWidget::setMaximumSize(const QSizeF& newSize) { m_maximumSize = newSize; } /** * Event handler for context menu events. */ void UMLWidget::contextMenuEvent(QGraphicsSceneContextMenuEvent *event) { WidgetBase::contextMenuEvent(event); } /** * Moves the widget to a new position using the difference between the * current position and the new position. * This method doesn't adjust associations. It only moves the widget. * * It can be overridden to constrain movement only in one axis even when * the user isn't constraining the movement with shift or control buttons, for example. * The movement policy set here is applied whenever the widget is moved, being it * moving it explicitly, or as a part of a selection but not receiving directly the * mouse events. * * Default behaviour is move the widget to the new position using the diffs. * @see constrainMovementForAllWidgets * * @param diffX The difference between current X position and new X position. * @param diffY The difference between current Y position and new Y position. */ void UMLWidget::moveWidgetBy(qreal diffX, qreal diffY) { setX(x() + diffX); setY(y() + diffY); } /** * Modifies the value of the diffX and diffY variables used to move the widgets. * * It can be overridden to constrain movement of all the selected widgets only in one * axis even when the user isn't constraining the movement with shift or control * buttons, for example. * The difference with moveWidgetBy is that the diff positions used here are * applied to all the selected widgets instead of only to m_widget, and that * moveWidgetBy, in fact, moves the widget, and here simply the diff positions * are modified. * * Default behaviour is do nothing. * @see moveWidgetBy * * @param diffX The difference between current X position and new X position. * @param diffY The difference between current Y position and new Y position. */ void UMLWidget::constrainMovementForAllWidgets(qreal &diffX, qreal &diffY) { Q_UNUSED(diffX) Q_UNUSED(diffY) } /** * Bring the widget at the pressed position to the foreground. */ void UMLWidget::toForeground() { QRectF rect = QRectF(scenePos(), QSizeF(width(), height())); QList items = scene()->items(rect, Qt::IntersectsItemShape, Qt::DescendingOrder); DEBUG(DBG_SRC) << "items at " << rect << " = " << items.count(); if (items.count() > 1) { foreach(QGraphicsItem* i, items) { UMLWidget* w = dynamic_cast(i); if (w) { DEBUG(DBG_SRC) << "item=" << w->name() << " with zValue=" << w->zValue(); if (w->name() != name()) { if (w->zValue() >= zValue()) { setZValue(w->zValue() + 1.0); DEBUG(DBG_SRC) << "bring to foreground with zValue: " << zValue(); } } } } } else { setZValue(0.0); } DEBUG(DBG_SRC) << "zValue is " << zValue(); } /** * Handles a mouse press event. * It'll select the widget (or mark it to be deselected) and prepare it to * be moved or resized. Go on reading for more info about this. * * Widget values and message bar status are saved. * * If shift or control buttons are pressed, we're in move area no matter * where the button was pressed in the widget. Moreover, if the widget * wasn't already selected, it's added to the selection. If already selected, * it's marked to be deselected when releasing the button (provided it isn't * moved). * Also, if the widget is already selected with other widgets but shift nor * control buttons are pressed, we're in move area. If finally we don't move * the widget, it's selected and the other widgets deselected when releasing * the left button. * * If shift nor control buttons are pressed, we're facing a single selection. * Depending on the position of the cursor, we're in move or in resize area. * If the widget wasn't selected (both when there are no widgets selected, or * when there're other widgets selected but not the one receiving the press * event) it's selected and the others deselected, if any. If already selected, * it's marked to be deselected when releasing the button (provided it wasn't * moved or resized). * * @param event The QGraphicsSceneMouseEvent event. */ void UMLWidget::mousePressEvent(QGraphicsSceneMouseEvent *event) { if (event->button() != Qt::LeftButton) { event->ignore(); return; } event->accept(); DEBUG(DBG_SRC) << "widget = " << name() << " / type = " << baseTypeStr(); toForeground(); m_startMovePostion = pos(); m_startResizeSize = QSizeF(width(), height()); // saving the values of the widget m_pressOffset = event->scenePos() - pos(); DEBUG(DBG_SRC) << "press offset=" << m_pressOffset; m_oldStatusBarMsg = UMLApp::app()->statusBarMsg(); if (event->modifiers() == Qt::ShiftModifier || event->modifiers() == Qt::ControlModifier) { m_shiftPressed = true; if (event->button() == Qt::LeftButton) { m_inMoveArea = true; } if (!isSelected()) { selectMultiple(event); } return; } m_shiftPressed = false; int count = m_scene->selectedCount(true); if (event->button() == Qt::LeftButton) { if (isSelected() && count > 1) { // single selection is made in release event if the widget wasn't moved m_inMoveArea = true; m_oldPos = pos(); return; } if (isInResizeArea(event)) { m_inResizeArea = true; m_oldW = width(); m_oldH = height(); } else { m_inMoveArea = true; } } // if widget wasn't selected, or it was selected but with other widgets also selected if (!isSelected() || count > 1) { selectSingle(event); } } /** * Handles a mouse move event. * It resizes or moves the widget, depending on where the cursor is pressed * on the widget. Go on reading for more info about this. * * If resizing, the widget is resized using UMLWidget::resizeWidget (where specific * widget resize constraint can be applied), and then the associations are * adjusted. * The resizing can be constrained also to a specific axis using control * and shift buttons. If on or another is pressed, it's constrained to X axis. * If both are pressed, it's constrained to Y axis. * * If not resizing, the widget is being moved. If the move is being started, * the selection bounds are set (which includes updating the list of selected * widgets). * The difference between the previous position of the selection and the new * one is got (taking in account the selection bounds so widgets don't go * beyond the scene limits). Then, it's constrained to X or Y axis depending * on shift and control buttons. * A further constraint is made using constrainMovementForAllWidgets (for example, * if the widget that receives the event can only be moved in Y axis, with this * method the movement of all the widgets in the selection can be constrained to * be moved only in Y axis). * Then, all the selected widgets are moved using moveWidgetBy (where specific * widget movement constraint can be applied) and, if a certain amount of time * passed from the last move event, the associations are also updated (they're * not updated always to be easy on the CPU). Finally, the scene is resized, * and selection bounds updated. * * @param event The QGraphicsSceneMouseEvent event. */ void UMLWidget::mouseMoveEvent(QGraphicsSceneMouseEvent* event) { if (m_inResizeArea) { resize(event); return; } if (!m_moved) { UMLApp::app()->document()->writeToStatusBar(i18n("Hold shift or ctrl to move in X axis. Hold shift and control to move in Y axis. Right button click to cancel move.")); m_moved = true; //Maybe needed by AssociationWidget m_startMove = true; setSelectionBounds(); } QPointF position = event->scenePos() - m_pressOffset; qreal diffX = position.x() - x(); qreal diffY = position.y() - y(); if ((event->modifiers() & Qt::ShiftModifier) && (event->modifiers() & Qt::ControlModifier)) { // move only in Y axis diffX = 0; } else if ((event->modifiers() & Qt::ShiftModifier) || (event->modifiers() & Qt::ControlModifier)) { // move only in X axis diffY = 0; } constrainMovementForAllWidgets(diffX, diffY); // nothing to move if (diffX == 0 && diffY == 0) { return; } QPointF delta = event->scenePos() - event->lastScenePos(); DEBUG(DBG_SRC) << "diffX=" << diffX << " / diffY=" << diffY; foreach(UMLWidget* widget, umlScene()->selectedWidgets()) { if ((widget->parentItem() == 0) || (!widget->parentItem()->isSelected())) { widget->moveWidgetBy(diffX, diffY); widget->adjustUnselectedAssocs(delta.x(), delta.y()); widget->slotSnapToGrid(); } } // Move any selected associations. foreach(AssociationWidget* aw, m_scene->selectedAssocs()) { if (aw->isSelected()) { aw->moveEntireAssoc(diffX, diffY); } } umlScene()->resizeSceneToItems(); } /** * Handles a mouse release event. * It selects or deselects the widget and cancels or confirms the move or * resize. Go on reading for more info about this. * No matter which tool is selected, Z position of widget is updated. * * Middle button release resets the selection. * Left button release, if it wasn't moved nor resized, selects the widget * and deselect the others if it wasn't selected and there were other widgets * selected. If the widget was marked to be deselected, deselects it. * If it was moved or resized, the document is set to modified if position * or size changed. Also, if moved, all the associations are adjusted because * the timer could have prevented the adjustment in the last move event before * the release. * If mouse was pressed in resize area, cursor is set again to normal cursor * Right button release if right button was pressed shows the pop up menu for * the widget. * If left button was pressed, it cancels the move or resize with a mouse move * event at the same position than the cursor was when pressed. Another left * button release is also sent. * * @param event The QGraphicsSceneMouseEvent event. */ void UMLWidget::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { if (!m_moved && !m_resized) { if (!m_shiftPressed && (m_scene->selectedCount(true) > 1)) { selectSingle(event); } else if (!isSelected()) { deselect(event); } } else { // Commands if (m_moved) { int selectionCount = umlScene()->selectedWidgets().count(); if (selectionCount > 1) { UMLApp::app()->beginMacro(i18n("Move widgets")); } foreach(UMLWidget* widget, umlScene()->selectedWidgets()) { UMLApp::app()->executeCommand(new Uml::CmdMoveWidget(widget)); } if (selectionCount > 1) { UMLApp::app()->endMacro(); } m_moved = false; } else { UMLApp::app()->executeCommand(new Uml::CmdResizeWidget(this)); m_autoResize = false; m_resized = false; } if ((m_inMoveArea && wasPositionChanged()) || (m_inResizeArea && wasSizeChanged())) { umlDoc()->setModified(true); } UMLApp::app()->document()->writeToStatusBar(m_oldStatusBarMsg); } if (m_inResizeArea) { m_inResizeArea = false; m_scene->activeView()->setCursor(Qt::ArrowCursor); } else { m_inMoveArea = false; } m_startMove = false; } /** * Event handler for mouse double click events. * @param event the QGraphicsSceneMouseEvent event. */ void UMLWidget::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) { if (event->button() == Qt::LeftButton) { DEBUG(DBG_SRC) << "widget = " << name() << " / type = " << baseTypeStr(); showPropertiesDialog(); event->accept(); } } /** * Return the start position of the move action. * @return point where the move began */ QPointF UMLWidget::startMovePosition() const { return m_startMovePostion; } /** * Set the start position of the move action. * @param position point where the move began */ void UMLWidget::setStartMovePosition(const QPointF &position) { m_startMovePostion = position; } /** * Return the start size of the resize action. * @return size where the resize began */ QSizeF UMLWidget::startResizeSize() const { return m_startResizeSize; } /** * Resizes the widget. * It's called from resize, after the values are constrained and before * the associations are adjusted. * * Default behaviour is resize the widget using the new size values. * @see resize * * @param newW The new width for the widget. * @param newH The new height for the widget. */ void UMLWidget::resizeWidget(qreal newW, qreal newH) { setSize(newW, newH); } /** * Notify child widget about parent resizes. * Child widgets can override this function to move when their parent is resized. */ void UMLWidget::notifyParentResize() { } /** * When a widget changes this slot captures that signal. */ void UMLWidget::updateWidget() { updateGeometry(); switch (baseType()) { case WidgetBase::wt_Class: m_scene->createAutoAttributeAssociations(this); break; case WidgetBase::wt_Entity: m_scene->createAutoConstraintAssociations(this); break; default: break; } if (isVisible()) update(); } /** * Apply possible constraints to the given candidate width and height. * The default implementation limits input values to the bounds returned * by minimumSize()/maximumSize(). * * @param width input value, may be modified by the constraint * @param height input value, may be modified by the constraint */ void UMLWidget::constrain(qreal& width, qreal& height) { QSizeF minSize = minimumSize(); if (width < minSize.width()) width = minSize.width(); if (height < minSize.height()) height = minSize.height(); QSizeF maxSize = maximumSize(); if (width > maxSize.width()) width = maxSize.width(); if (height > maxSize.height()) height = maxSize.height(); if (fixedAspectRatio()) { QSizeF size = rect().size(); float aspectRatio = size.width() > 0 ? (float)size.height()/size.width() : 1; height = width * aspectRatio; } } /** * Initializes key attributes of the class. */ void UMLWidget::init() { m_nId = Uml::ID::None; m_nLocalID = UniqueID::gen(); m_isInstance = false; setMinimumSize(DefaultMinimumSize); setMaximumSize(DefaultMaximumSize); m_font = QApplication::font(); for (int i = (int)FT_INVALID - 1; i >= 0; --i) { FontType fontType = (FontType)i; setupFontType(m_font, fontType); m_pFontMetrics[fontType] = new QFontMetrics(m_font); } if (m_scene) { m_useFillColor = true; m_usesDiagramFillColor = true; m_usesDiagramUseFillColor = true; const Settings::OptionState& optionState = m_scene->optionState(); m_fillColor = optionState.uiState.fillColor; m_showStereotype = optionState.classState.showStereoType; } else { uError() << "SERIOUS PROBLEM - m_scene is NULL"; m_useFillColor = false; m_usesDiagramFillColor = false; m_usesDiagramUseFillColor = false; m_showStereotype = false; } m_resizable = true; m_fixedAspectRatio = false; m_startMove = false; m_activated = false; m_ignoreSnapToGrid = false; m_ignoreSnapComponentSizeToGrid = false; m_doc = UMLApp::app()->document(); m_nPosX = 0; connect(m_scene, SIGNAL(sigFillColorChanged(Uml::ID::Type)), this, SLOT(slotFillColorChanged(Uml::ID::Type))); connect(m_scene, SIGNAL(sigLineColorChanged(Uml::ID::Type)), this, SLOT(slotLineColorChanged(Uml::ID::Type))); connect(m_scene, SIGNAL(sigTextColorChanged(Uml::ID::Type)), this, SLOT(slotTextColorChanged(Uml::ID::Type))); connect(m_scene, SIGNAL(sigLineWidthChanged(Uml::ID::Type)), this, SLOT(slotLineWidthChanged(Uml::ID::Type))); m_umlObject = 0; m_oldPos = QPointF(); m_pressOffset = QPointF(); m_oldW = 0; m_oldH = 0; m_shiftPressed = false; m_inMoveArea = false; m_inResizeArea = false; m_moved = false; m_resized = false; // propagate line color set by base class constructor // which does not call the virtual methods from this class. setLineColor(lineColor()); setZValue(2.0); // default for most widgets } /** * This is usually called synchronously after menu.exec() and \a * trigger's parent is always the ListPopupMenu which can be used to * get the type of action of \a trigger. * * @note Subclasses can reimplement to handle specific actions and * leave the rest to WidgetBase::slotMenuSelection. */ void UMLWidget::slotMenuSelection(QAction *trigger) { if (!trigger) { return; } ListPopupMenu::MenuType sel = ListPopupMenu::typeFromAction(trigger); switch (sel) { case ListPopupMenu::mt_Resize: umlScene()->resizeSelection(); break; case ListPopupMenu::mt_AutoResize: setAutoResize(trigger->isChecked()); updateGeometry(); break; case ListPopupMenu::mt_Rename_Object: { QString name = m_instanceName; bool ok = Dialog_Utils::askName(i18n("Rename Object"), i18n("Enter object name:"), name); if (ok) { m_instanceName = name; updateGeometry(); moveEvent(0); update(); UMLApp::app()->document()->setModified(true); } break; } case ListPopupMenu::mt_FloatText: { FloatingTextWidget* ft = new FloatingTextWidget(umlScene()); ft->showChangeTextDialog(); //if no text entered delete if (!FloatingTextWidget::isTextValid(ft->text())) { delete ft; } else { ft->setID(UniqueID::gen()); addWidget(ft, false); } break; } case ListPopupMenu::mt_Actor: { UMLActor *actor = new UMLActor; UMLWidget *widget = new ActorWidget(umlScene(), actor); addConnectedWidget(widget, Uml::AssociationType::Association); break; } case ListPopupMenu::mt_Note: { NoteWidget *widget = new NoteWidget(umlScene()); addConnectedWidget(widget, Uml::AssociationType::Anchor); break; } case ListPopupMenu::mt_Port: { // TODO: merge with ToolbarStateOneWidget::setWidget() UMLPackage* component = umlObject()->asUMLPackage(); QString name = Model_Utils::uniqObjectName(UMLObject::ot_Port, component); if (Dialog_Utils::askName(i18n("Enter Port Name"), i18n("Enter the port"), name)) { UMLPort *port = Object_Factory::createUMLObject(UMLObject::ot_Port, name, component)->asUMLPort(); UMLWidget *umlWidget = Widget_Factory::createWidget(umlScene(), port); umlScene()->setupNewWidget(umlWidget); } break; } case ListPopupMenu::mt_UseCase: { UMLUseCase *useCase = new UMLUseCase; UMLWidget *widget = new UseCaseWidget(umlScene(), useCase); addConnectedWidget(widget, Uml::AssociationType::Association); break; } default: WidgetBase::slotMenuSelection(trigger); break; } } /** * Captures when another widget moves if this widget is linked to it. * @see sigWidgetMoved * * @param id The id of object behind the widget. */ void UMLWidget::slotWidgetMoved(Uml::ID::Type /*id*/) { } /** * Captures a color change signal. * * @param viewID The id of the UMLScene behind the widget. */ void UMLWidget::slotFillColorChanged(Uml::ID::Type viewID) { //only change if on the diagram concerned if (m_scene->ID() != viewID) { return; } if (m_usesDiagramFillColor) { WidgetBase::setFillColor(m_scene->fillColor()); } if (m_usesDiagramUseFillColor) { WidgetBase::setUseFillColor(m_scene->useFillColor()); } update(); } /** * Captures a text color change signal. * * @param viewID The id of the UMLScene behind the widget. */ void UMLWidget::slotTextColorChanged(Uml::ID::Type viewID) { //only change if on the diagram concerned if (m_scene->ID() != viewID) return; WidgetBase::setTextColor(m_scene->textColor()); update(); } /** * Captures a line color change signal. * * @param viewID The id of the UMLScene behind the widget. */ void UMLWidget::slotLineColorChanged(Uml::ID::Type viewID) { //only change if on the diagram concerned if (m_scene->ID() != viewID) return; if (m_usesDiagramLineColor) { WidgetBase::setLineColor(m_scene->lineColor()); } update(); } /** * Captures a linewidth change signal. * * @param viewID The id of the UMLScene behind the widget. */ void UMLWidget::slotLineWidthChanged(Uml::ID::Type viewID) { //only change if on the diagram concerned if (m_scene->ID() != viewID) { return; } if (m_usesDiagramLineWidth) { WidgetBase::setLineWidth(m_scene->lineWidth()); } update(); } /** * Set the status of using fill color (undo action) * * @param fc the status of using fill color. */ void UMLWidget::setUseFillColor(bool fc) { if (useFillColor() != fc) { UMLApp::app()->executeCommand(new CmdChangeUseFillColor(this, fc)); } } /** * Set the status of using fill color. * * @param fc the status of using fill color. */ void UMLWidget::setUseFillColorCmd(bool fc) { WidgetBase::setUseFillColor(fc); update(); } /** * Overrides the method from WidgetBase. */ void UMLWidget::setTextColorCmd(const QColor &color) { WidgetBase::setTextColor(color); update(); } /** * Overrides the method from WidgetBase. */ void UMLWidget::setTextColor(const QColor &color) { if (textColor() != color) { UMLApp::app()->executeCommand(new CmdChangeTextColor(this, color)); update(); } } /** * Overrides the method from WidgetBase. */ void UMLWidget::setLineColorCmd(const QColor &color) { WidgetBase::setLineColor(color); update(); } /** * Overrides the method from WidgetBase. */ void UMLWidget::setLineColor(const QColor &color) { if (lineColor() != color) { UMLApp::app()->executeCommand(new CmdChangeLineColor(this, color)); } } /** * Overrides the method from WidgetBase, execute CmdChangeLineWidth */ void UMLWidget::setLineWidth(uint width) { if (lineWidth() != width) { UMLApp::app()->executeCommand(new CmdChangeLineWidth(this, width)); } } /** * Overrides the method from WidgetBase. */ void UMLWidget::setLineWidthCmd(uint width) { WidgetBase::setLineWidth(width); update(); } /** * Sets the background fill color * * @param color the new fill color */ void UMLWidget::setFillColor(const QColor &color) { if (fillColor() != color) { UMLApp::app()->executeCommand(new CmdChangeFillColor(this, color)); } } /** * Sets the background fill color * * @param color the new fill color */ void UMLWidget::setFillColorCmd(const QColor &color) { WidgetBase::setFillColor(color); update(); } /** * Activate the object after serializing it from a QDataStream * * @param ChangeLog * @return true for success */ bool UMLWidget::activate(IDChangeLog* /*ChangeLog = 0 */) { if (widgetHasUMLObject(baseType()) && m_umlObject == 0) { m_umlObject = m_doc->findObjectById(m_nId); if (m_umlObject == 0) { uError() << "cannot find UMLObject with id=" << Uml::ID::toString(m_nId); return false; } } setFontCmd(m_font); setSize(width(), height()); m_activated = true; updateGeometry(); if (m_scene->getPaste()) { FloatingTextWidget * ft = 0; QPointF point = m_scene->getPastePoint(); int x = point.x() + this->x(); int y = point.y() + this->y(); if (m_scene->type() == Uml::DiagramType::Sequence) { switch (baseType()) { case WidgetBase::wt_Object: case WidgetBase::wt_Precondition : setY(this->y()); setX(x); break; case WidgetBase::wt_Message: setY(this->y()); setX(x); break; case WidgetBase::wt_Text: ft = static_cast(this); if (ft->textRole() == Uml::TextRole::Seq_Message) { setX(x); setY(this->y()); } else { setX(this->x()); setY(this->y()); } break; default: setY(y); break; }//end switch base type }//end if sequence else { setX(x); setY(y); } }//end if pastepoint else { setX(this->x()); setY(this->y()); } if (m_scene->getPaste()) m_scene->createAutoAssociations(this); updateGeometry(); return true; } /** * Returns true if the Activate method has been called for this instance * * @return The activate status. */ bool UMLWidget::isActivated() const { return m_activated; } /** * Set the m_activated flag of a widget but does not perform the Activate method * * @param active Status of activation is to be set. */ void UMLWidget::setActivated(bool active /*=true*/) { m_activated = active; } /** * Adds an already created association to the list of * associations that include this UMLWidget */ void UMLWidget::addAssoc(AssociationWidget* pAssoc) { if (pAssoc && !associationWidgetList().contains(pAssoc)) { associationWidgetList().append(pAssoc); } } /** * Returns the list of associations connected to this widget. */ AssociationWidgetList &UMLWidget::associationWidgetList() const { m_Assocs.removeAll(0); return m_Assocs; } /** * Removes an already created association from the list of * associations that include this UMLWidget */ void UMLWidget::removeAssoc(AssociationWidget* pAssoc) { if (pAssoc) { associationWidgetList().removeAll(pAssoc); } } /** * Adjusts associations with the given co-ordinates * * @param dx The amount by which the widget moved in X direction. * @param dy The amount by which the widget moved in Y direction. */ void UMLWidget::adjustAssocs(qreal dx, qreal dy) { // don't adjust Assocs on file load, as // the original positions, which are stored in XMI // should be reproduced exactly // (don't try to reposition assocs as long // as file is only partly loaded -> reposition // could be misguided) /// @todo avoid trigger of this event during load if (m_doc->loading()) { // don't recalculate the assocs during load of XMI // -> return immediately without action return; } foreach(AssociationWidget* assocwidget, associationWidgetList()) { assocwidget->saveIdealTextPositions(); } foreach(AssociationWidget* assocwidget, associationWidgetList()) { assocwidget->widgetMoved(this, dx, dy); } } /** * Adjusts all unselected associations with the given co-ordinates * * @param dx The amount by which the widget moved in X direction. * @param dy The amount by which the widget moved in Y direction. */ void UMLWidget::adjustUnselectedAssocs(qreal dx, qreal dy) { foreach(AssociationWidget* assocwidget, associationWidgetList()) { if (!assocwidget->isSelected()) assocwidget->saveIdealTextPositions(); } foreach(AssociationWidget* assocwidget, associationWidgetList()) { if (!assocwidget->isSelected()) { assocwidget->widgetMoved(this, dx, dy); } } } /** * Show a properties dialog for a UMLWidget. */ bool UMLWidget::showPropertiesDialog() { bool result = false; // will already be selected so make sure docWindow updates the doc // back it the widget UMLApp::app()->docWindow()->updateDocumentation(false); QPointer dlg = new ClassPropertiesDialog((QWidget*)UMLApp::app(), this); if (dlg->exec()) { UMLApp::app()->docWindow()->showDocumentation(umlObject(), true); m_doc->setModified(true); result = true; } dlg->close(); //wipe from memory delete dlg; return result; } /** * Move the widget by an X and Y offset relative to * the current position. */ void UMLWidget::moveByLocal(qreal dx, qreal dy) { qreal newX = x() + dx; qreal newY = y() + dy; setX(newX); setY(newY); adjustAssocs(dx, dy); } /** * Set the pen. */ void UMLWidget::setPenFromSettings(QPainter & p) { p.setPen(QPen(m_lineColor, m_lineWidth)); } /** * Set the pen. */ void UMLWidget::setPenFromSettings(QPainter *p) { p->setPen(QPen(m_lineColor, m_lineWidth)); } /** * Returns the cursor to be shown when resizing the widget. * Default cursor is KCursor::sizeFDiagCursor(). * * @return The cursor to be shown when resizing the widget. */ QCursor UMLWidget::resizeCursor() const { return Qt::SizeFDiagCursor; } /** * Checks if the mouse is in resize area (right bottom corner), and sets * the cursor depending on that. * The cursor used when resizing is gotten from resizeCursor(). * * @param me The QMouseEVent to check. * @return true if the mouse is in resize area, false otherwise. */ bool UMLWidget::isInResizeArea(QGraphicsSceneMouseEvent *me) { qreal m = 10.0; const qreal w = width(); const qreal h = height(); // If the widget itself is very small then make the resize area small, too. // Reason: Else it becomes impossible to do a move instead of resize. if (w - m < m || h - m < m) { m = 2.0; } if (m_resizable && me->scenePos().x() >= (x() + w - m) && me->scenePos().y() >= (y() + h - m)) { m_scene->activeView()->setCursor(resizeCursor()); return true; } else { m_scene->activeView()->setCursor(Qt::ArrowCursor); return false; } } /** * calculate content related size of widget. * * @return calculated widget size */ QSizeF UMLWidget::calculateSize(bool withExtensions /* = true */) const { Q_UNUSED(withExtensions) const QFontMetrics &fm = getFontMetrics(UMLWidget::FT_NORMAL); const int fontHeight = fm.lineSpacing(); if (m_umlObject) { qreal width = 0, height = defaultMargin; if (!m_umlObject->stereotype().isEmpty()) { height += fontHeight; const QFontMetrics &bfm = UMLWidget::getFontMetrics(UMLWidget::FT_BOLD); const int stereoWidth = bfm.size(0, m_umlObject->stereotype(true)).width(); if (stereoWidth > width) width = stereoWidth; } height += fontHeight; const QFontMetrics &bfm = UMLWidget::getFontMetrics(UMLWidget::FT_BOLD); const int nameWidth = bfm.size(0, m_umlObject->name()).width(); if (nameWidth > width) width = nameWidth; return QSizeF(width + 2*defaultMargin, height); } else return QSizeF(width(), height()); } /** * Resize widget to minimum size. */ void UMLWidget::resize() { qreal oldW = width(); qreal oldH = height(); // @TODO minimumSize() do not work in all cases, we need a dedicated autoResize() method QSizeF size = minimumSize(); setSize(size.width(), size.height()); DEBUG(DBG_SRC) << "size=" << size; adjustAssocs(size.width()-oldW, size.height()-oldH); } /** * Resizes the widget and adjusts the associations. * It's called when a mouse move event happens and the cursor was * in resize area when pressed. * Resizing can be constrained to an specific axis using control and shift buttons. * * @param me The QGraphicsSceneMouseEvent to get the values from. */ void UMLWidget::resize(QGraphicsSceneMouseEvent *me) { // TODO the status message lies for at least MessageWidget which could only be resized vertical UMLApp::app()->document()->writeToStatusBar(i18n("Hold shift or ctrl to move in X axis. Hold shift and control to move in Y axis. Right button click to cancel resize.")); m_resized = true; qreal newW = m_oldW + me->scenePos().x() - x() - m_pressOffset.x(); qreal newH = m_oldH + me->scenePos().y() - y() - m_pressOffset.y(); if ((me->modifiers() & Qt::ShiftModifier) && (me->modifiers() & Qt::ControlModifier)) { //Move in Y axis newW = m_oldW; } else if ((me->modifiers() & Qt::ShiftModifier) || (me->modifiers() & Qt::ControlModifier)) { //Move in X axis newH = m_oldH; } constrain(newW, newH); resizeWidget(newW, newH); DEBUG(DBG_SRC) << "event=" << me->scenePos() << "/ pos=" << pos() << " / newW=" << newW << " / newH=" << newH; QPointF delta = me->scenePos() - me->lastScenePos(); adjustAssocs(delta.x(), delta.y()); m_scene->resizeSceneToItems(); } /** * Checks if the size of the widget changed respect to the size that * it had when press event was fired. * * @return true if was resized, false otherwise. */ bool UMLWidget::wasSizeChanged() { return m_oldW != width() || m_oldH != height(); } /** * Checks if the position of the widget changed respect to the position that * it had when press event was fired. * * @return true if was moved, false otherwise. */ bool UMLWidget::wasPositionChanged() { return m_oldPos != pos(); } /** * Fills m_selectedWidgetsList and sets the selection bounds ((m_min/m_max)X/Y attributes). */ void UMLWidget::setSelectionBounds() { } void UMLWidget::setSelectedFlag(bool _select) { WidgetBase::setSelected(_select); } /** * Sets the state of whether the widget is selected. * * @param _select The state of whether the widget is selected. */ void UMLWidget::setSelected(bool _select) { WidgetBase::setSelected(_select); const WidgetBase::WidgetType wt = baseType(); if (_select) { if (m_scene->selectedCount() == 0) { if (widgetHasUMLObject(wt)) { UMLApp::app()->docWindow()->showDocumentation(m_umlObject, false); } else { UMLApp::app()->docWindow()->showDocumentation(this, false); } }//end if /* if (wt != wt_Text && wt != wt_Box) { setZ(9);//keep text on top and boxes behind so don't touch Z value } */ } else { /* if (wt != wt_Text && wt != wt_Box) { setZ(m_origZ); } */ if (isSelected()) UMLApp::app()->docWindow()->updateDocumentation(true); } update(); // selection changed, we have to make sure the copy and paste items // are correctly enabled/disabled UMLApp::app()->slotCopyChanged(); // select in tree view as done for diagrams if (_select) { UMLListViewItem * item = UMLApp::app()->listView()->findItem(id()); if (item) UMLApp::app()->listView()->setCurrentItem(item); else UMLApp::app()->listView()->clearSelection(); } } /** * Selects the widget and clears the other selected widgets, if any. * * @param me The QGraphicsSceneMouseEvent which made the selection. */ void UMLWidget::selectSingle(QGraphicsSceneMouseEvent *me) { m_scene->clearSelected(); // Adds the widget to the selected widgets list, but as it has been cleared // only the current widget is selected. selectMultiple(me); } /** * Selects the widget and adds it to the list of selected widgets. * * @param me The QGraphicsSceneMouseEvent which made the selection. */ void UMLWidget::selectMultiple(QGraphicsSceneMouseEvent *me) { Q_UNUSED(me); setSelected(true); } /** * Deselects the widget and removes it from the list of selected widgets. * * @param me The QGraphicsSceneMouseEvent which made the selection. */ void UMLWidget::deselect(QGraphicsSceneMouseEvent *me) { Q_UNUSED(me); setSelected(false); } /** * Clears the selection, resets the toolbar and deselects the widget. */ //void UMLWidget::resetSelection() //{ // m_scene->clearSelected(); // m_scene->resetToolbar(); // setSelected(false); //} /** * Sets the view the widget is on. * * @param scene The UMLScene the widget is on. */ void UMLWidget::setScene(UMLScene *scene) { //remove signals from old view - was probably 0 anyway disconnect(m_scene, SIGNAL(sigFillColorChanged(Uml::ID::Type)), this, SLOT(slotFillColorChanged(Uml::ID::Type))); disconnect(m_scene, SIGNAL(sigTextColorChanged(Uml::ID::Type)), this, SLOT(slotTextColorChanged(Uml::ID::Type))); disconnect(m_scene, SIGNAL(sigLineWidthChanged(Uml::ID::Type)), this, SLOT(slotLineWidthChanged(Uml::ID::Type))); m_scene = scene; connect(m_scene, SIGNAL(sigFillColorChanged(Uml::ID::Type)), this, SLOT(slotFillColorChanged(Uml::ID::Type))); connect(m_scene, SIGNAL(sigTextColorChanged(Uml::ID::Type)), this, SLOT(slotTextColorChanged(Uml::ID::Type))); connect(m_scene, SIGNAL(sigLineWidthChanged(Uml::ID::Type)), this, SLOT(slotLineWidthChanged(Uml::ID::Type))); } /** * Sets the x-coordinate. * Currently, the only class that reimplements this method is * ObjectWidget. * * @param x The x-coordinate to be set. */ void UMLWidget::setX(qreal x) { QGraphicsObject::setX(x); } /** * Sets the y-coordinate. * Currently, the only class that reimplements this method is * ObjectWidget. * * @param y The y-coordinate to be set. */ void UMLWidget::setY(qreal y) { QGraphicsObject::setY(y); } /** * Used to cleanup any other widget it may need to delete. * Used by child classes. This should be called before deleting a widget of a diagram. */ void UMLWidget::cleanup() { } /** * Tells the widget to snap to grid. * Will use the grid settings of the @ref UMLView it belongs to. */ void UMLWidget::slotSnapToGrid() { if (!m_ignoreSnapToGrid) { qreal newX = m_scene->snappedX(x()); setX(newX); qreal newY = m_scene->snappedY(y()); setY(newY); } } /** * Returns whether the widget type has an associated UMLObject */ bool UMLWidget::widgetHasUMLObject(WidgetBase::WidgetType type) { if (type == WidgetBase::wt_Actor || type == WidgetBase::wt_UseCase || type == WidgetBase::wt_Class || type == WidgetBase::wt_Interface || type == WidgetBase::wt_Enum || type == WidgetBase::wt_Datatype || type == WidgetBase::wt_Package || type == WidgetBase::wt_Component || type == WidgetBase::wt_Port || type == WidgetBase::wt_Node || type == WidgetBase::wt_Artifact || type == WidgetBase::wt_Object) { return true; } else { return false; } } /** * Set m_ignoreSnapToGrid. */ void UMLWidget::setIgnoreSnapToGrid(bool to) { m_ignoreSnapToGrid = to; } /** * Return the value of m_ignoreSnapToGrid. */ bool UMLWidget::getIgnoreSnapToGrid() const { return m_ignoreSnapToGrid; } /** * Sets the size. * If m_scene->snapComponentSizeToGrid() is true, then * set the next larger size that snaps to the grid. */ void UMLWidget::setSize(qreal width, qreal height) { // snap to the next larger size that is a multiple of the grid if (!m_ignoreSnapComponentSizeToGrid && m_scene->snapComponentSizeToGrid()) { // integer divisions int numX = width / m_scene->snapX(); int numY = height / m_scene->snapY(); // snap to the next larger valid value if (width > numX * m_scene->snapX()) width = (numX + 1) * m_scene->snapX(); if (height > numY * m_scene->snapY()) height = (numY + 1) * m_scene->snapY(); } const QRectF newRect(rect().x(), rect().y(), width, height); setRect(newRect); foreach(QGraphicsItem* child, childItems()) { UMLWidget* umlChild = static_cast(child); umlChild->notifyParentResize(); } } /** * Sets the size with another size. */ void UMLWidget::setSize(const QSizeF& size) { setSize(size.width(), size.height()); } /** * Update the size of this widget. */ void UMLWidget::updateGeometry() { if (m_doc->loading()) { return; } if (!m_autoResize) return; qreal oldW = width(); qreal oldH = height(); QSizeF size = calculateSize(); qreal clipWidth = size.width(); qreal clipHeight = size.height(); constrain(clipWidth, clipHeight); setSize(clipWidth, clipHeight); slotSnapToGrid(); adjustAssocs(size.width()-oldW, size.height()-oldH); + update(); } /** * clip the size of this widget against the * minimal and maximal limits. */ void UMLWidget::clipSize() { qreal clipWidth = width(); qreal clipHeight = height(); constrain(clipWidth, clipHeight); setSize(clipWidth, clipHeight); } /** * Template Method, override this to set the default font metric. */ void UMLWidget::setDefaultFontMetrics(QFont &font, UMLWidget::FontType fontType) { setupFontType(font, fontType); setFontMetrics(fontType, QFontMetrics(font)); } void UMLWidget::setupFontType(QFont &font, UMLWidget::FontType fontType) { switch (fontType) { case FT_NORMAL: font.setBold(false); font.setItalic(false); font.setUnderline(false); break; case FT_BOLD: font.setBold(true); font.setItalic(false); font.setUnderline(false); break; case FT_ITALIC: font.setBold(false); font.setItalic(true); font.setUnderline(false); break; case FT_UNDERLINE: font.setBold(false); font.setItalic(false); font.setUnderline(true); break; case FT_BOLD_ITALIC: font.setBold(true); font.setItalic(true); font.setUnderline(false); break; case FT_BOLD_UNDERLINE: font.setBold(true); font.setItalic(false); font.setUnderline(true); break; case FT_ITALIC_UNDERLINE: font.setBold(false); font.setItalic(true); font.setUnderline(true); break; case FT_BOLD_ITALIC_UNDERLINE: font.setBold(true); font.setItalic(true); font.setUnderline(true); break; default: return; } } void UMLWidget::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { Q_UNUSED(option); Q_UNUSED(widget); if (option->state & QStyle::State_Selected) { const qreal w = width(); const qreal h = height(); const qreal s = selectionMarkerSize; QBrush brush(Qt::blue); painter->fillRect(0, 0, s, s, brush); painter->fillRect(0, 0 + h - s, s, s, brush); painter->fillRect(0 + w - s, 0, s, s, brush); // Draw the resize anchor in the lower right corner. // Don't draw it if the widget is so small that the // resize anchor would cover up most of the widget. if (m_resizable && w >= s+8 && h >= s+8) { brush.setColor(Qt::red); const int right = 0 + w; const int bottom = 0 + h; painter->drawLine(right - s, 0 + h - 1, 0 + w - 1, 0 + h - s); painter->drawLine(right - (s*2), bottom - 1, right - 1, bottom - (s*2)); painter->drawLine(right - (s*3), bottom - 1, right - 1, bottom - (s*3)); } else { painter->fillRect(0 + w - s, 0 + h - s, s, s, brush); } // debug info if (Tracer::instance()->isEnabled(QLatin1String(metaObject()->className()))) { painter->setPen(Qt::green); painter->setBrush(Qt::NoBrush); painter->drawPath(shape()); painter->setPen(Qt::blue); painter->drawRect(boundingRect()); // origin painter->drawLine(-10, 0, 10, 0); painter->drawLine(0, -10, 0, 10); } } if (umlScene()->isShowDocumentationIndicator() && hasDocumentation()) { const qreal h = height(); const qreal d = 8; QPolygonF p; p << QPointF(0, h - d) << QPointF(d, h) << QPointF(0, h); painter->setPen(Qt::blue); painter->setBrush(Qt::red); painter->drawPolygon(p); } } /** * Template Method, override this to set the default font metric. */ void UMLWidget::setDefaultFontMetrics(QFont &font, UMLWidget::FontType fontType, QPainter &painter) { setupFontType(font, fontType); painter.setFont(font); setFontMetrics(fontType, painter.fontMetrics()); } /** * Returns the font metric used by this object for Text * which uses bold/italic fonts. */ QFontMetrics &UMLWidget::getFontMetrics(UMLWidget::FontType fontType) const { return *m_pFontMetrics[fontType]; } /** * Set the font metric to use. */ void UMLWidget::setFontMetrics(UMLWidget::FontType fontType, QFontMetrics fm) { delete m_pFontMetrics[fontType]; m_pFontMetrics[fontType] = new QFontMetrics(fm); } /** * Sets the font the widget is to use. * * @param font Font to be set. */ void UMLWidget::setFont(const QFont &font) { QFont newFont = font; forceUpdateFontMetrics(newFont, 0); if (m_font != newFont) { UMLApp::app()->executeCommand(new CmdChangeFont(this, font)); } } /** * Sets the font the widget is to use. * * @param font Font to be set. */ void UMLWidget::setFontCmd(const QFont &font) { WidgetBase::setFont(font); forceUpdateFontMetrics(0); if (m_doc->loading()) return; update(); } /** * Updates font metrics for widgets current m_font */ void UMLWidget::forceUpdateFontMetrics(QPainter *painter) { forceUpdateFontMetrics(m_font, painter); } /** * @note For performance Reasons, only FontMetrics for already used * font types are updated. Not yet used font types will not get a font metric * and will get the same font metric as if painter was zero. * This behaviour is acceptable, because diagrams will always be shown on Display * first before a special painter like a printer device is used. */ void UMLWidget::forceUpdateFontMetrics(QFont& font, QPainter *painter) { if (painter == 0) { for (int i = (int)FT_INVALID - 1; i >= 0; --i) { if (m_pFontMetrics[(UMLWidget::FontType)i] != 0) setDefaultFontMetrics(font, (UMLWidget::FontType)i); } } else { for (int i2 = (int)FT_INVALID - 1; i2 >= 0; --i2) { if (m_pFontMetrics[(UMLWidget::FontType)i2] != 0) setDefaultFontMetrics(font, (UMLWidget::FontType)i2, *painter); } } if (m_doc->loading()) return; // calculate the size, based on the new font metric updateGeometry(); } /** * Set the status of whether to show Stereotype. * * @param flag True if stereotype shall be shown. */ void UMLWidget::setShowStereotype(bool flag) { m_showStereotype = flag; updateGeometry(); update(); } /** * Returns the status of whether to show Stereotype. * * @return True if stereotype is shown. */ bool UMLWidget::showStereotype() const { return m_showStereotype; } /** * Overrides the standard operation. * * @param me The move event. */ void UMLWidget::moveEvent(QGraphicsSceneMouseEvent* me) { Q_UNUSED(me) } void UMLWidget::saveToXMI1(QDomDocument & qDoc, QDomElement & qElement) { /* Call after required actions in child class. Type must be set in the child class. */ WidgetBase::saveToXMI1(qDoc, qElement); qElement.setAttribute(QLatin1String("xmi.id"), Uml::ID::toString(id())); qreal dpiScale = UMLApp::app()->document()->dpiScale(); qElement.setAttribute(QLatin1String("x"), QString::number(x() / dpiScale)); qElement.setAttribute(QLatin1String("y"), QString::number(y() / dpiScale)); qElement.setAttribute(QLatin1String("width"), QString::number(width() / dpiScale)); qElement.setAttribute(QLatin1String("height"), QString::number(height() / dpiScale)); qElement.setAttribute(QLatin1String("isinstance"), m_isInstance); if (!m_instanceName.isEmpty()) qElement.setAttribute(QLatin1String("instancename"), m_instanceName); if (m_showStereotype) qElement.setAttribute(QLatin1String("showstereotype"), m_showStereotype); // Unique identifier for widget (todo: id() should be unique, new attribute // should indicate the UMLObject's ID it belongs to) qElement.setAttribute(QLatin1String("localid"), Uml::ID::toString(m_nLocalID)); } bool UMLWidget::loadFromXMI1(QDomElement & qElement) { QString id = qElement.attribute(QLatin1String("xmi.id"), QLatin1String("-1")); m_nId = Uml::ID::fromString(id); WidgetBase::loadFromXMI1(qElement); QString x = qElement.attribute(QLatin1String("x"), QLatin1String("0")); QString y = qElement.attribute(QLatin1String("y"), QLatin1String("0")); QString h = qElement.attribute(QLatin1String("height"), QLatin1String("0")); QString w = qElement.attribute(QLatin1String("width"), QLatin1String("0")); qreal dpiScale = UMLApp::app()->document()->dpiScale(); setSize(toDoubleFromAnyLocale(w) * dpiScale, toDoubleFromAnyLocale(h) * dpiScale); setX(toDoubleFromAnyLocale(x) * dpiScale); setY(toDoubleFromAnyLocale(y) * dpiScale); QString isinstance = qElement.attribute(QLatin1String("isinstance"), QLatin1String("0")); m_isInstance = (bool)isinstance.toInt(); m_instanceName = qElement.attribute(QLatin1String("instancename")); QString showstereo = qElement.attribute(QLatin1String("showstereotype"), QLatin1String("0")); m_showStereotype = (bool)showstereo.toInt(); QString localid = qElement.attribute(QLatin1String("localid"), QLatin1String("0")); if (localid != QLatin1String("0")) { m_nLocalID = Uml::ID::fromString(localid); } return true; } /** * Adds a widget to the diagram, which is connected to the current widget * @param widget widget instance to add to diagram * @param type association type */ void UMLWidget::addConnectedWidget(UMLWidget *widget, Uml::AssociationType::Enum type) { umlScene()->addItem(widget); widget->setX(x() + rect().width() + 100); widget->setY(y()); widget->setSize(100, 40); AssociationWidget* assoc = AssociationWidget::create(umlScene(), this, type, widget); umlScene()->addAssociation(assoc); widget->showPropertiesDialog(); QSizeF size = widget->minimumSize(); widget->setSize(size); } /** * Adds a widget to the diagram, which is connected to the current widget * @param widget widget instance to add to diagram * @param showProperties whether to show properties of the widget */ void UMLWidget::addWidget(UMLWidget *widget, bool showProperties) { umlScene()->addItem(widget); widget->setX(x() + rect().width() + 100); widget->setY(y()); widget->setSize(100, 40); if (showProperties) widget->showPropertiesDialog(); QSizeF size = widget->minimumSize(); widget->setSize(size); } diff --git a/umbrello/umlwidgets/widgetbase.cpp b/umbrello/umlwidgets/widgetbase.cpp index 34fb21899..5d18bdd46 100644 --- a/umbrello/umlwidgets/widgetbase.cpp +++ b/umbrello/umlwidgets/widgetbase.cpp @@ -1,1330 +1,1349 @@ /*************************************************************************** * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * copyright (C) 2004-2014 * * Umbrello UML Modeller Authors * ***************************************************************************/ #include "widgetbase.h" #include "classifier.h" #include "debug_utils.h" #include "dialog_utils.h" #include "floatingtextwidget.h" #include "uml.h" #include "umldoc.h" #include "umllistview.h" #include "umlobject.h" #include "umlscene.h" #include "widgetbasepopupmenu.h" #if QT_VERSION < 0x050000 #include #include #endif #include #include #if QT_VERSION >= 0x050000 #include #include #endif #include /** * Creates a WidgetBase object. * * @param scene The view to be displayed on. * @param type The WidgetType to construct. This must be set to the appropriate * value by the constructors of inheriting classes. */ WidgetBase::WidgetBase(UMLScene *scene, WidgetType type) : QGraphicsObject(), m_baseType(type), m_scene(scene), m_umlObject(0), m_textColor(QColor("black")), m_fillColor(QColor("yellow")), m_brush(m_fillColor), m_lineWidth(0), // initialize with 0 to have valid start condition m_useFillColor(true), m_usesDiagramFillColor(true), m_usesDiagramLineColor(true), m_usesDiagramLineWidth(true), m_usesDiagramTextColor(true), m_usesDiagramUseFillColor(true), - m_autoResize(true) + m_autoResize(true), + m_changesShape(false) { Q_ASSERT(m_baseType > wt_Min && m_baseType < wt_Max); // Note: no virtual methods from derived classes available, // this operation need to be finished in derived class constructor. setLineColor(QColor("black")); setSelected(false); // TODO 310283 setFlags(ItemIsSelectable); //setFlags(ItemIsSelectable | ItemIsMovable |ItemSendsGeometryChanges); if (m_scene) { m_usesDiagramLineColor = true; m_usesDiagramLineWidth = true; m_usesDiagramTextColor = true; const Settings::OptionState& optionState = m_scene->optionState(); m_textColor = optionState.uiState.textColor; setLineColor(optionState.uiState.lineColor); setLineWidth(optionState.uiState.lineWidth); m_font = optionState.uiState.font; } else { uError() << "WidgetBase constructor: SERIOUS PROBLEM - m_scene is NULL"; } } /** * Destructor. */ WidgetBase::~WidgetBase() { } /** * Read property of m_baseType. */ WidgetBase::WidgetType WidgetBase::baseType() const { Q_ASSERT(m_baseType > wt_Min && m_baseType < wt_Max); return m_baseType; } /** * Set property m_baseType. Used for types changing their types during runtime. */ void WidgetBase::setBaseType(const WidgetType& baseType) { Q_ASSERT(baseType > wt_Min && baseType < wt_Max); m_baseType = baseType; } /** * @return The type used for rtti as string. */ QLatin1String WidgetBase::baseTypeStr() const { Q_ASSERT(m_baseType > wt_Min && m_baseType < wt_Max); return QLatin1String(ENUM_NAME(WidgetBase, WidgetType, m_baseType)); } /* * Sets the state of whether the widget is selected. * * @param select The state of whether the widget is selected. */ void WidgetBase::setSelected(bool select) { QGraphicsObject::setSelected(select); } /** * Deliver a pointer to the connected UMLView * (needed esp. by event handling of AssociationLine). */ UMLScene* WidgetBase::umlScene() const { return m_scene; } /** * This is shortcut method for UMLApp::app()->document(). * * @return Pointer to the UMLDoc object. */ UMLDoc* WidgetBase::umlDoc() const { return UMLApp::app()->document(); } /** * Returns the @ref UMLObject set to represent. * * @return the UMLObject to represent. */ UMLObject* WidgetBase::umlObject() const { return m_umlObject; } /** * Sets the @ref UMLObject to represent. * * @param obj The object to represent. */ void WidgetBase::setUMLObject(UMLObject *obj) { m_umlObject = obj; } /** * Write property of m_nId. */ void WidgetBase::setID(Uml::ID::Type id) { if (m_umlObject) { if (m_umlObject->id() != Uml::ID::None) uWarning() << "changing old UMLObject " << Uml::ID::toString(m_umlObject->id()) << " to " << Uml::ID::toString(id); m_umlObject->setID(id); } m_nId = id; } /** * Read property of m_nId. */ Uml::ID::Type WidgetBase::id() const { if (m_umlObject) return m_umlObject->id(); return m_nId; } /** * Used by some child classes to get documentation. * * @return The documentation from the UMLObject (if m_umlObject is set.) */ QString WidgetBase::documentation() const { if (m_umlObject) return m_umlObject->doc(); return m_Doc; } /** * Returns state of documentation for the widget. * * @return false if documentation is empty */ bool WidgetBase::hasDocumentation() { if (m_umlObject) return m_umlObject->hasDoc(); return !m_Doc.isEmpty(); } /** * Used by some child classes to set documentation. * * @param doc The documentation to be set in the UMLObject * (if m_umlObject is set.) */ void WidgetBase::setDocumentation(const QString& doc) { if (m_umlObject) m_umlObject->setDoc(doc); else m_Doc = doc; } /** * Gets the name from the corresponding UMLObject if this widget has an * underlying UMLObject; if it does not, then it returns the local * m_Text (notably the case for FloatingTextWidget.) * * @return the currently set name */ QString WidgetBase::name() const { if (m_umlObject) return m_umlObject->name(); return m_Text; } /** * Sets the name in the corresponding UMLObject. * Sets the local m_Text if m_umlObject is NULL. * * @param strName The name to be set. */ void WidgetBase::setName(const QString &strName) { if (m_umlObject) m_umlObject->setName(strName); else m_Text = strName; } /** * Returns text color * * @return currently used text color */ QColor WidgetBase::textColor() const { return m_textColor; } /** * Sets the text color * * @param color the new text color */ void WidgetBase::setTextColor(const QColor &color) { m_textColor = color; m_usesDiagramTextColor = false; } /** * Returns line color * * @return currently used line color */ QColor WidgetBase::lineColor() const { return m_lineColor; } /** * Sets the line color * * @param color The new line color */ void WidgetBase::setLineColor(const QColor &color) { m_lineColor = color; m_usesDiagramLineColor = false; } /** * Returns fill color * * @return currently used fill color */ QColor WidgetBase::fillColor() const { return m_fillColor; } /** * Sets the fill color * * @param color The new fill color */ void WidgetBase::setFillColor(const QColor &color) { m_fillColor = color; m_usesDiagramFillColor = false; } /** * Returns line width * * @return currently used line with */ uint WidgetBase::lineWidth() const { return m_lineWidth; } /** * Sets the line width * * @param width The new line width */ void WidgetBase::setLineWidth(uint width) { m_lineWidth = width; m_usesDiagramLineWidth = false; } /** * Return state of fill color usage * * @return True if fill color is used */ bool WidgetBase::useFillColor() { return m_useFillColor; } /** * Set state if fill color is used * * @param state The state to set */ void WidgetBase::setUseFillColor(bool state) { m_useFillColor = state; m_usesDiagramUseFillColor = false; } /** * Returns state if diagram text color is used * * @return True means diagram text color is used */ bool WidgetBase::usesDiagramTextColor() const { return m_usesDiagramTextColor; } /** * Set state if diagram text color is used * * @param state The state to set */ void WidgetBase::setUsesDiagramTextColor(bool state) { if (m_usesDiagramTextColor == state) { return; } m_usesDiagramTextColor = state; setTextColor(m_textColor); } /** * Returns state of diagram line color is used * * @return True means diagrams line color is used */ bool WidgetBase::usesDiagramLineColor() const { return m_usesDiagramLineColor; } /** * Set state of diagram line color is used * * @param state The state to set */ void WidgetBase::setUsesDiagramLineColor(bool state) { m_usesDiagramLineColor = state; } /** * Returns state of diagram fill color is used * * @return True means diagrams fill color is used */ bool WidgetBase::usesDiagramFillColor() const { return m_usesDiagramFillColor; } /** * Set state if diagram fill color is used * * @param state The state to set */ void WidgetBase::setUsesDiagramFillColor(bool state) { m_usesDiagramFillColor = state; } /** * Returns state of diagram use fill color is used * * @return True means diagrams fill color is used */ bool WidgetBase::usesDiagramUseFillColor() const { return m_usesDiagramUseFillColor; } /** * Set state of diagram use fill color is used * * @param state The state to set */ void WidgetBase::setUsesDiagramUseFillColor(bool state) { m_usesDiagramUseFillColor = state; } /** * Returns state of diagram line width is used * * @return True means diagrams line width is used */ bool WidgetBase::usesDiagramLineWidth() const { return m_usesDiagramLineWidth; } /** * Set state of diagram line width is used * * @param state The state to set */ void WidgetBase::setUsesDiagramLineWidth(bool state) { m_usesDiagramLineWidth = state; } /** * Returns the font used for diaplaying any text. * @return the font */ QFont WidgetBase::font() const { return m_font; } /** * Set the font used to display text inside this widget. */ void WidgetBase::setFont(const QFont& font) { m_font = font; } /** * Return state of auto resize property * @return the auto resize state */ bool WidgetBase::autoResize() { return m_autoResize; } /** * set auto resize state * @param state */ void WidgetBase::setAutoResize(bool state) { m_autoResize = state; } +/** + * Return changes state property + * @return the changes shape state + */ +bool WidgetBase::changesShape() +{ + return m_changesShape; +} + +/** + * set changes shape property + * @param state + */ +void WidgetBase::setChangesShape(bool state) +{ + m_changesShape = state; +} + /** * A virtual method for the widget to display a property dialog box. * Subclasses should reimplment this appropriately. * In case the user cancels the dialog or there are some requirements * not fulfilled the method returns false; true otherwise. * * @return true - properties has been applyed * @return false - properties has not been applied * */ bool WidgetBase::showPropertiesDialog() { return false; } /** * A virtual method to save the properties of this widget into a * QDomElement i.e xml. * * Subclasses should first create a new dedicated element as the child * of \a qElement parameter passed. Then this base method should be * called to save basic widget properties. * * @param qDoc A QDomDocument object representing the xml document. * @param qElement A QDomElement representing xml element data. */ void WidgetBase::saveToXMI1(QDomDocument& qDoc, QDomElement& qElement) { Q_UNUSED(qDoc) qElement.setAttribute(QLatin1String("textcolor"), m_usesDiagramTextColor ? QLatin1String("none") : m_textColor.name()); if (m_usesDiagramLineColor) { qElement.setAttribute(QLatin1String("linecolor"), QLatin1String("none")); } else { qElement.setAttribute(QLatin1String("linecolor"), m_lineColor.name()); } if (m_usesDiagramLineWidth) { qElement.setAttribute(QLatin1String("linewidth"), QLatin1String("none")); } else { qElement.setAttribute(QLatin1String("linewidth"), m_lineWidth); } qElement.setAttribute(QLatin1String("usefillcolor"), m_useFillColor); // for consistency the following attributes now use american spelling for "color" qElement.setAttribute(QLatin1String("usesdiagramfillcolor"), m_usesDiagramFillColor); qElement.setAttribute(QLatin1String("usesdiagramusefillcolor"), m_usesDiagramUseFillColor); if (m_usesDiagramFillColor) { qElement.setAttribute(QLatin1String("fillcolor"), QLatin1String("none")); } else { qElement.setAttribute(QLatin1String("fillcolor"), m_fillColor.name()); } qElement.setAttribute(QLatin1String("font"), m_font.toString()); qElement.setAttribute(QLatin1String("autoresize"), m_autoResize ? 1 : 0); } /** * A virtual method to load the properties of this widget from a * QDomElement into this widget. * * Subclasses should reimplement this to load addtional properties * required, calling this base method to load the basic properties of * the widget. * * @param qElement A QDomElement which contains xml info for this widget. * * @todo Add support to load older version. */ bool WidgetBase::loadFromXMI1(QDomElement& qElement) { // first load from "linecolour" and then overwrite with the "linecolor" // attribute if that one is present. The "linecolour" name was a "typo" in // earlier versions of Umbrello QString lineColor = qElement.attribute(QLatin1String("linecolour"), QLatin1String("none")); lineColor = qElement.attribute(QLatin1String("linecolor"), lineColor); if (lineColor != QLatin1String("none")) { setLineColor(QColor(lineColor)); m_usesDiagramLineColor = false; } else if (m_baseType != WidgetBase::wt_Box && m_scene != 0) { setLineColor(m_scene->lineColor()); m_usesDiagramLineColor = true; } QString lineWidth = qElement.attribute(QLatin1String("linewidth"), QLatin1String("none")); if (lineWidth != QLatin1String("none")) { setLineWidth(lineWidth.toInt()); m_usesDiagramLineWidth = false; } else if (m_scene) { setLineWidth(m_scene->lineWidth()); m_usesDiagramLineWidth = true; } QString textColor = qElement.attribute(QLatin1String("textcolor"), QLatin1String("none")); if (textColor != QLatin1String("none")) { m_textColor = QColor(textColor); m_usesDiagramTextColor = false; } else if (m_scene) { m_textColor = m_scene->textColor(); m_usesDiagramTextColor = true; } QString usefillcolor = qElement.attribute(QLatin1String("usefillcolor"), QLatin1String("1")); m_useFillColor = (bool)usefillcolor.toInt(); /* For the next three *color attributes, there was a mixup of american and english spelling for "color". So first we need to keep backward compatibility and try to retrieve the *colour attribute. Next we overwrite this value if we find a *color, otherwise the former *colour is kept. */ QString fillColor = qElement.attribute(QLatin1String("fillcolour"), QLatin1String("none")); fillColor = qElement.attribute(QLatin1String("fillcolor"), fillColor); if (fillColor != QLatin1String("none")) { m_fillColor = QColor(fillColor); } QString usesDiagramFillColor = qElement.attribute(QLatin1String("usesdiagramfillcolour"), QLatin1String("1")); usesDiagramFillColor = qElement.attribute(QLatin1String("usesdiagramfillcolor"), usesDiagramFillColor); m_usesDiagramFillColor = (bool)usesDiagramFillColor.toInt(); QString usesDiagramUseFillColor = qElement.attribute(QLatin1String("usesdiagramusefillcolour"), QLatin1String("1")); usesDiagramUseFillColor = qElement.attribute(QLatin1String("usesdiagramusefillcolor"), usesDiagramUseFillColor); m_usesDiagramUseFillColor = (bool)usesDiagramUseFillColor.toInt(); QString font = qElement.attribute(QLatin1String("font")); if (!font.isEmpty()) { QFont newFont; newFont.fromString(font); m_font = newFont; } else { uWarning() << "Using default font " << m_font.toString() << " for widget with xmi.id " << Uml::ID::toString(m_nId); } QString autoResize = qElement.attribute(QLatin1String("autoresize"), QLatin1String("1")); m_autoResize = (bool)autoResize.toInt(); return true; } /** * Assignment operator */ WidgetBase& WidgetBase::operator=(const WidgetBase& other) { m_baseType = other.m_baseType; m_scene = other.m_scene; m_umlObject = other.m_umlObject; m_Doc = other.m_Doc; m_Text = other.m_Text; m_nId = other.m_nId; m_textColor = other.m_textColor; setLineColor(other.lineColor()); m_fillColor = other.m_fillColor; m_brush = other.m_brush; m_font = other.m_font; m_lineWidth = other.m_lineWidth; m_useFillColor = other.m_useFillColor; m_usesDiagramTextColor = other.m_usesDiagramTextColor; m_usesDiagramLineColor = other.m_usesDiagramLineColor; m_usesDiagramFillColor = other.m_usesDiagramFillColor; m_usesDiagramLineWidth = other.m_usesDiagramLineWidth; setSelected(other.isSelected()); return *this; } /** * return drawing rectangle of widget in local coordinates */ QRectF WidgetBase::rect() const { return m_rect; } /** * set widget rectangle in item coordinates */ void WidgetBase::setRect(const QRectF& rect) { if (m_rect == rect) return; prepareGeometryChange(); m_rect = rect; update(); } /** * set widget rectangle in item coordinates */ void WidgetBase::setRect(qreal x, qreal y, qreal width, qreal height) { setRect(QRectF(x, y, width, height)); } /** * @return The bounding rectangle for this widget. * @see setRect */ QRectF WidgetBase::boundingRect() const { int halfWidth = lineWidth()/2; return m_rect.adjusted(-halfWidth, -halfWidth, halfWidth, halfWidth); } /** * Test if point is inside the bounding rectangle of the widget. * Inheriting classes may reimplement this to test possible child widgets. * * @param p Point to be checked. * * @return 'this' if the given point is in the boundaries of the widget; * else NULL. */ UMLWidget* WidgetBase::onWidget(const QPointF &p) { UMLWidget *uw = this->asUMLWidget(); if (uw == 0) return 0; const qreal w = m_rect.width(); const qreal h = m_rect.height(); const qreal left = x(); // don't use m_rect.x() for this, it is always 0 const qreal right = left + w; const qreal top = y(); // don't use m_rect.y() for this, it is always 0 const qreal bottom = top + h; // uDebug() << "p=(" << p.x() << "," << p.y() // << "), x=" << left << ", y=" << top << ", w=" << w << ", h=" << h // << "; right=" << right << ", bottom=" << bottom; if (p.x() < left || p.x() > right || p.y() < top || p.y() > bottom) { // Qt coord.sys. origin in top left corner // uDebug() << "returning NULL"; return 0; } // uDebug() << "returning this"; return uw; } /** * Draws the UMLWidget on the given paint device * * @param painter The painter for the drawing device * @param option Painting related options * @param widget Background widget on which to paint (optional) * */ void WidgetBase::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { Q_UNUSED(painter); Q_UNUSED(option); Q_UNUSED(widget); } /** * Reimplemented to show appropriate context menu. */ void WidgetBase::contextMenuEvent(QGraphicsSceneContextMenuEvent *event) { event->accept(); uDebug() << "widget = " << name() << " / type = " << baseTypeStr(); UMLScene *scene = umlScene(); // If right-click was done on a widget that was not selected, clear the // current selection and select the new widget. The context menu is shown // with actions for that single widget. // If a keyboard modifier was used, add the widget to the current selection // and show the menu with actions for the whole selection. if (!isSelected()) { Qt::KeyboardModifiers forSelection = (Qt::ControlModifier | Qt::ShiftModifier); if ((event->modifiers() & forSelection) == 0) { scene->clearSelected(); } if (umlObject() != 0) { scene->selectWidget(this->asUMLWidget()); } else { setSelected(true); } } int count = scene->selectedCount(true); // Determine multi state bool multi = (isSelected() && count > 1); WidgetBasePopupMenu popup(0, this, multi, scene->getUniqueSelectionType()); // Disable the "view code" menu for simple code generators if (UMLApp::app()->isSimpleCodeGeneratorActive()) { popup.setActionEnabled(ListPopupMenu::mt_ViewCode, false); } QAction *triggered = popup.exec(event->screenPos()); slotMenuSelection(triggered); } /** * This is usually called synchronously after menu.exec() and \a * trigger's parent is always the ListPopupMenu which can be used to * get the type of action of \a trigger. * * @note Subclasses can reimplement to handle specific actions and * leave the rest to WidgetBase::slotMenuSelection. */ void WidgetBase::slotMenuSelection(QAction *trigger) { if (!trigger) { return; } QColor newColor; const WidgetType wt = m_baseType; // short hand name ListPopupMenu::MenuType sel = ListPopupMenu::typeFromAction(trigger); switch (sel) { case ListPopupMenu::mt_Rename: umlDoc()->renameUMLObject(umlObject()); break; case ListPopupMenu::mt_Properties: if (wt == WidgetBase::wt_Actor || wt == WidgetBase::wt_UseCase || wt == WidgetBase::wt_Package || wt == WidgetBase::wt_Interface || wt == WidgetBase::wt_Datatype || wt == WidgetBase::wt_Node || wt == WidgetBase::wt_Component || wt == WidgetBase::wt_Artifact || wt == WidgetBase::wt_Enum || wt == WidgetBase::wt_Entity || wt == WidgetBase::wt_Port || wt == WidgetBase::wt_Instance || (wt == WidgetBase::wt_Class && umlScene()->type() == Uml::DiagramType::Class)) { showPropertiesDialog(); } else if (wt == WidgetBase::wt_Object) { m_umlObject->showPropertiesDialog(); } else { uWarning() << "making properties dialog for unknown widget type"; } break; case ListPopupMenu::mt_Line_Color: case ListPopupMenu::mt_Line_Color_Selection: #if QT_VERSION >= 0x050000 newColor = QColorDialog::getColor(lineColor()); if (newColor != lineColor()) { #else newColor = lineColor(); if (KColorDialog::getColor(newColor)) { #endif if (sel == ListPopupMenu::mt_Line_Color_Selection) { umlScene()->selectionSetLineColor(newColor); } else { setLineColor(newColor); } setUsesDiagramLineColor(false); umlDoc()->setModified(true); } break; case ListPopupMenu::mt_Fill_Color: case ListPopupMenu::mt_Fill_Color_Selection: #if QT_VERSION >= 0x050000 newColor = QColorDialog::getColor(fillColor()); if (newColor != fillColor()) { #else newColor = fillColor(); if (KColorDialog::getColor(newColor)) { #endif if (sel == ListPopupMenu::mt_Fill_Color_Selection) { umlScene()->selectionSetFillColor(newColor); } else { setFillColor(newColor); } umlDoc()->setModified(true); } break; case ListPopupMenu::mt_Use_Fill_Color: setUseFillColor(!m_useFillColor); break; case ListPopupMenu::mt_Set_Use_Fill_Color_Selection: umlScene()->selectionUseFillColor(true); break; case ListPopupMenu::mt_Unset_Use_Fill_Color_Selection: umlScene()->selectionUseFillColor(false); break; case ListPopupMenu::mt_Show_Attributes_Selection: case ListPopupMenu::mt_Hide_Attributes_Selection: umlScene()->selectionSetVisualProperty( ClassifierWidget::ShowAttributes, sel != ListPopupMenu::mt_Hide_Attributes_Selection ); break; case ListPopupMenu::mt_Show_Operations_Selection: case ListPopupMenu::mt_Hide_Operations_Selection: umlScene()->selectionSetVisualProperty( ClassifierWidget::ShowOperations, sel != ListPopupMenu::mt_Hide_Operations_Selection ); break; case ListPopupMenu::mt_Show_Visibility_Selection: case ListPopupMenu::mt_Hide_Visibility_Selection: umlScene()->selectionSetVisualProperty( ClassifierWidget::ShowVisibility, sel != ListPopupMenu::mt_Hide_Visibility_Selection ); break; case ListPopupMenu::mt_Show_Operation_Signature_Selection: case ListPopupMenu::mt_Hide_Operation_Signature_Selection: umlScene()->selectionSetVisualProperty( ClassifierWidget::ShowOperationSignature, sel != ListPopupMenu::mt_Hide_Operation_Signature_Selection ); break; case ListPopupMenu::mt_Show_Attribute_Signature_Selection: case ListPopupMenu::mt_Hide_Attribute_Signature_Selection: umlScene()->selectionSetVisualProperty( ClassifierWidget::ShowAttributeSignature, sel != ListPopupMenu::mt_Hide_Attribute_Signature_Selection ); break; case ListPopupMenu::mt_Show_Packages_Selection: case ListPopupMenu::mt_Hide_Packages_Selection: umlScene()->selectionSetVisualProperty( ClassifierWidget::ShowPackage, sel != ListPopupMenu::mt_Hide_Packages_Selection ); break; case ListPopupMenu::mt_Show_Stereotypes_Selection: case ListPopupMenu::mt_Hide_Stereotypes_Selection: umlScene()->selectionSetVisualProperty( ClassifierWidget::ShowStereotype, sel != ListPopupMenu::mt_Hide_Stereotypes_Selection ); break; case ListPopupMenu::mt_Hide_NonPublic_Selection: case ListPopupMenu::mt_Show_NonPublic_Selection: umlScene()->selectionSetVisualProperty( ClassifierWidget::ShowPublicOnly, sel != ListPopupMenu::mt_Show_NonPublic_Selection ); break; case ListPopupMenu::mt_ViewCode: { UMLClassifier *c = umlObject()->asUMLClassifier(); if (c) { UMLApp::app()->viewCodeDocument(c); } break; } case ListPopupMenu::mt_Remove: umlScene()->deleteSelection(); break; case ListPopupMenu::mt_Delete: if (!Dialog_Utils::askDeleteAssociation()) break; umlScene()->deleteSelection(); break; case ListPopupMenu::mt_Change_Font: case ListPopupMenu::mt_Change_Font_Selection: { #if QT_VERSION >= 0x050000 bool ok = false; QFont newFont = QFontDialog::getFont(&ok, font()); if (ok) { #else QFont newFont = font(); if (KFontDialog::getFont(newFont, KFontChooser::NoDisplayFlags, 0) == KFontDialog::Accepted) { #endif if (sel == ListPopupMenu::mt_Change_Font_Selection) { m_scene->selectionSetFont(newFont); } else { setFont(newFont); } } } break; case ListPopupMenu::mt_Cut: umlScene()->setStartedCut(); UMLApp::app()->slotEditCut(); break; case ListPopupMenu::mt_Copy: UMLApp::app()->slotEditCopy(); break; case ListPopupMenu::mt_Paste: UMLApp::app()->slotEditPaste(); break; case ListPopupMenu::mt_Refactoring: //check if we are operating on a classifier, or some other kind of UMLObject if (umlObject()->asUMLClassifier()) { UMLApp::app()->refactor(umlObject()->asUMLClassifier()); } break; case ListPopupMenu::mt_Clone: { foreach (UMLWidget* widget, umlScene()->selectedWidgets()) { if (Model_Utils::isCloneable(widget->baseType())) { UMLObject *clone = widget->umlObject()->clone(); umlScene()->addObject(clone); } } } break; case ListPopupMenu::mt_Rename_MultiA: case ListPopupMenu::mt_Rename_MultiB: case ListPopupMenu::mt_Rename_Name: case ListPopupMenu::mt_Rename_RoleAName: case ListPopupMenu::mt_Rename_RoleBName: { FloatingTextWidget *ft = static_cast(this); ft->handleRename(); break; } case ListPopupMenu::mt_Align_Right: umlScene()->alignRight(); break; case ListPopupMenu::mt_Align_Left: umlScene()->alignLeft(); break; case ListPopupMenu::mt_Align_Top: umlScene()->alignTop(); break; case ListPopupMenu::mt_Align_Bottom: umlScene()->alignBottom(); break; case ListPopupMenu::mt_Align_VerticalMiddle: umlScene()->alignVerticalMiddle(); break; case ListPopupMenu::mt_Align_HorizontalMiddle: umlScene()->alignHorizontalMiddle(); break; case ListPopupMenu::mt_Align_VerticalDistribute: umlScene()->alignVerticalDistribute(); break; case ListPopupMenu::mt_Align_HorizontalDistribute: umlScene()->alignHorizontalDistribute(); break; default: uDebug() << "MenuType " << ListPopupMenu::toString(sel) << " not implemented"; break; } } /** * Helper function for debug output. * Returns the given enum value as string. * @param wt WidgetType of which a string representation is wanted * @return the WidgetType as string */ QString WidgetBase::toString(WidgetType wt) { return QLatin1String(ENUM_NAME(WidgetBase, WidgetType, wt)); } /** * Returns the given enum value as localized string. * @param wt WidgetType of which a string representation is wanted * @return the WidgetType as localized string */ QString WidgetBase::toI18nString(WidgetType wt) { QString name; switch (wt) { case wt_Activity: name = i18n("Activity"); break; case wt_Actor: name = i18n("Actor"); break; case wt_Artifact: name = i18n("Artifact"); break; case wt_Association: name = i18n("Association"); break; case wt_Box: name = i18n("Box"); break; case wt_Category: name = i18n("Category"); break; case wt_CombinedFragment: name = i18n("CombinedFragment"); break; case wt_Component: name = i18n("Component"); break; case wt_Class: name = i18n("Class"); break; case wt_Datatype: name = i18n("Datatype"); break; case wt_Entity: name = i18n("Entity"); break; case wt_Enum: name = i18n("Enum"); break; case wt_FloatingDashLine: name = i18n("FloatingDashLine"); break; case wt_ForkJoin: name = i18n("ForkJoin"); break; case wt_Interface: name = i18n("Interface"); break; case wt_Message: name = i18n("Message"); break; case wt_Node: name = i18n("Node"); break; case wt_Note: name = i18n("Note"); break; case wt_Object: name = i18n("Object"); break; case wt_ObjectNode: name = i18n("ObjectNode"); break; case wt_Package: name = i18n("Package"); break; case wt_Pin: name = i18n("Pin"); break; case wt_Port: name = i18n("Port"); break; case wt_Precondition: name = i18n("Precondition"); break; case wt_Region: name = i18n("Region"); break; case wt_Signal: name = i18n("Signal"); break; case wt_State: name = i18n("State"); break; case wt_Text: name = i18n("Text"); break; case wt_UseCase: name = i18n("UseCase"); break; case wt_Instance: name = i18n("Instance"); break; default: name = QLatin1String(" &name:"); uWarning() << "unknown widget type"; break; } return name; } /** * Returns the given enum value as icon type. * @param wt WidgetType of which an icon type representation is wanted * @return the WidgetType as icon type */ Icon_Utils::IconType WidgetBase::toIcon(WidgetBase::WidgetType wt) { Icon_Utils::IconType icon; switch (wt) { case wt_Activity: icon = Icon_Utils::it_Activity; break; case wt_Actor: icon = Icon_Utils::it_Actor; break; case wt_Artifact: icon = Icon_Utils::it_Artifact; break; case wt_Association: icon = Icon_Utils::it_Association; break; case wt_Box: icon = Icon_Utils::it_Box; break; case wt_Category: icon = Icon_Utils::it_Category; break; case wt_CombinedFragment: icon = Icon_Utils::it_Combined_Fragment; break; case wt_Component: icon = Icon_Utils::it_Component; break; case wt_Class: icon = Icon_Utils::it_Class; break; case wt_Datatype: icon = Icon_Utils::it_Datatype; break; case wt_Entity: icon = Icon_Utils::it_Entity; break; case wt_Enum: icon = Icon_Utils::it_Enum; break; case wt_FloatingDashLine: icon = Icon_Utils::it_Association; break; case wt_ForkJoin: icon = Icon_Utils::it_Fork_Join; break; case wt_Instance: icon = Icon_Utils::it_Instance; break; case wt_Interface: icon = Icon_Utils::it_Interface; break; case wt_Message: icon = Icon_Utils::it_Message_Synchronous; break; case wt_Node: icon = Icon_Utils::it_Node; break; case wt_Note: icon = Icon_Utils::it_Note; break; case wt_Object: icon = Icon_Utils::it_Object; break; case wt_ObjectNode: icon = Icon_Utils::it_Object_Node; break; case wt_Package: icon = Icon_Utils::it_Package; break; case wt_Pin: icon = Icon_Utils::it_Pin; break; case wt_Port: icon = Icon_Utils::it_Port; break; case wt_Precondition: icon = Icon_Utils::it_Precondition; break; case wt_Region: icon = Icon_Utils::it_Region; break; case wt_Signal: icon = Icon_Utils::it_Send_Signal; break; case wt_State: icon = Icon_Utils::it_State; break; case wt_Text: icon = Icon_Utils::it_Text; break; case wt_UseCase: icon = Icon_Utils::it_UseCase; break; default: icon = Icon_Utils::it_Home; uWarning() << "unknown widget type"; break; } return icon; } #include "activitywidget.h" #include "actorwidget.h" #include "artifactwidget.h" #include "associationwidget.h" #include "boxwidget.h" #include "categorywidget.h" //#include "classwidget.h" #include "combinedfragmentwidget.h" #include "componentwidget.h" #include "datatypewidget.h" #include "entitywidget.h" #include "enumwidget.h" #include "floatingdashlinewidget.h" #include "forkjoinwidget.h" //#include "interfacewidget.h" #include "messagewidget.h" #include "nodewidget.h" #include "notewidget.h" #include "objectnodewidget.h" #include "objectwidget.h" #include "packagewidget.h" #include "pinwidget.h" #include "portwidget.h" #include "preconditionwidget.h" #include "regionwidget.h" #include "signalwidget.h" #include "statewidget.h" #include "floatingtextwidget.h" #include "usecasewidget.h" ActivityWidget* WidgetBase::asActivityWidget() { return dynamic_cast(this); } ActorWidget* WidgetBase::asActorWidget() { return dynamic_cast(this); } ArtifactWidget* WidgetBase::asArtifactWidget() { return dynamic_cast(this); } AssociationWidget* WidgetBase::asAssociationWidget() { return dynamic_cast(this); } BoxWidget* WidgetBase::asBoxWidget() { return dynamic_cast(this); } CategoryWidget* WidgetBase::asCategoryWidget() { return dynamic_cast(this); } ClassifierWidget* WidgetBase::asClassifierWidget() { return dynamic_cast(this); } CombinedFragmentWidget* WidgetBase::asCombinedFragmentWidget() { return dynamic_cast(this); } ComponentWidget* WidgetBase::asComponentWidget() { return dynamic_cast(this); } DatatypeWidget* WidgetBase::asDatatypeWidget() { return dynamic_cast(this); } EntityWidget* WidgetBase::asEntityWidget() { return dynamic_cast(this); } EnumWidget* WidgetBase::asEnumWidget() { return dynamic_cast(this); } FloatingDashLineWidget* WidgetBase::asFloatingDashLineWidget() { return dynamic_cast(this); } ForkJoinWidget* WidgetBase::asForkJoinWidget() { return dynamic_cast(this); } //InterfaceWidget* WidgetBase::asInterfaceWidget() { return dynamic_cast(this); } MessageWidget* WidgetBase::asMessageWidget() { return dynamic_cast(this); } NodeWidget* WidgetBase::asNodeWidget() { return dynamic_cast(this); } NoteWidget* WidgetBase::asNoteWidget() { return dynamic_cast(this); } ObjectNodeWidget* WidgetBase::asObjectNodeWidget() { return dynamic_cast(this); } ObjectWidget* WidgetBase::asObjectWidget() { return dynamic_cast(this); } PackageWidget* WidgetBase::asPackageWidget() { return dynamic_cast(this); } PinWidget* WidgetBase::asPinWidget() { return dynamic_cast(this); } PinPortBase *WidgetBase::asPinPortBase() { return dynamic_cast(this); } PortWidget* WidgetBase::asPortWidget() { return dynamic_cast(this); } PreconditionWidget* WidgetBase::asPreconditionWidget() { return dynamic_cast(this); } RegionWidget* WidgetBase::asRegionWidget() { return dynamic_cast(this); } SignalWidget* WidgetBase::asSignalWidget() { return dynamic_cast(this); } StateWidget* WidgetBase::asStateWidget() { return dynamic_cast(this); } FloatingTextWidget* WidgetBase::asFloatingTextWidget() { return dynamic_cast(this); } //TextWidget* WidgetBase::asTextWidget() { return dynamic_cast(this); } UseCaseWidget* WidgetBase::asUseCaseWidget() { return dynamic_cast(this); } UMLWidget *WidgetBase::asUMLWidget() { return dynamic_cast(this); } diff --git a/umbrello/umlwidgets/widgetbase.h b/umbrello/umlwidgets/widgetbase.h index fbe93cc56..4e27b7906 100644 --- a/umbrello/umlwidgets/widgetbase.h +++ b/umbrello/umlwidgets/widgetbase.h @@ -1,299 +1,303 @@ /*************************************************************************** * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * copyright (C) 2004-2014 * * Umbrello UML Modeller Authors * ***************************************************************************/ #ifndef WIDGETBASE_H #define WIDGETBASE_H #include "basictypes.h" #include "icon_utils.h" #include #include #include #include #include #include #include // forward declarations class QAction; class ActivityWidget; class ActorWidget; class ArtifactWidget; class AssociationWidget; class BoxWidget; class CategoryWidget; class ClassifierWidget; class CombinedFragmentWidget; class ComponentWidget; class DatatypeWidget; class EntityWidget; class EnumWidget; class FloatingDashLineWidget; class FloatingTextWidget; class ForkJoinWidget; //class InterfaceWidget; class MessageWidget; class NodeWidget; class NoteWidget; class ObjectNodeWidget; class ObjectWidget; class PackageWidget; class PinWidget; class PortWidget; class PinPortBase; class PreconditionWidget; class RegionWidget; class SignalWidget; class StateWidget; //class TextWidget; class UseCaseWidget; class UMLDoc; class UMLObject; class UMLScene; class UMLWidget; // required by function onWidget() /** * @short Common base class for UMLWidget and AssociationWidget * @author Oliver Kellogg * Bugs and comments to umbrello-devel@kde.org or http://bugs.kde.org */ class WidgetBase : public QGraphicsObject { Q_OBJECT Q_ENUMS(WidgetType) public: enum WidgetType { wt_Min = 299, // lower bounds check value wt_UMLWidget, // does not have UMLObject representation wt_Actor, // has UMLObject representation wt_UseCase, // has UMLObject representation wt_Class, // has UMLObject representation wt_Interface, // has UMLObject representation wt_Datatype, // has UMLObject representation wt_Enum, // has UMLObject representation wt_Entity, // has UMLObject representation wt_Package, // has UMLObject representation wt_Object, // has UMLObject representation wt_Note, // does not have UMLObject representation wt_Box, // does not have UMLObject representation wt_Message, // does not have UMLObject representation wt_Text, // does not have UMLObject representation wt_State, // does not have UMLObject representation wt_Activity, // does not have UMLObject representation wt_Component, // has UMLObject representation wt_Artifact, // has UMLObject representation wt_Node, // has UMLObject representation wt_Association, // has UMLObject representation wt_ForkJoin, // does not have UMLObject representation wt_Precondition, // does not have UMLObject representation wt_CombinedFragment, // does not have UMLObject representation wt_FloatingDashLine, // does not have UMLObject representation wt_Signal, // does not have UMLObject representation wt_Pin, wt_ObjectNode, wt_Region, wt_Category, // has UMLObject representation wt_Port, // has UMLObject representation wt_Instance, // has UMLObject representation == wt_Object wt_Max // upper bounds check value }; static QString toString(WidgetType wt); static QString toI18nString(WidgetType wt); static Icon_Utils::IconType toIcon(WidgetType wt); explicit WidgetBase(UMLScene * scene, WidgetType type= wt_UMLWidget); virtual ~WidgetBase(); UMLObject* umlObject() const; virtual void setUMLObject(UMLObject *obj); Uml::ID::Type id() const; void setID(Uml::ID::Type id); WidgetType baseType() const; void setBaseType(const WidgetType& baseType); QLatin1String baseTypeStr() const; virtual void setSelected(bool select); UMLScene* umlScene() const; UMLDoc* umlDoc() const; QString documentation() const; bool hasDocumentation(); virtual void setDocumentation(const QString& doc); QString name() const; virtual void setName(const QString &strName); QColor lineColor() const; virtual void setLineColor(const QColor& color); uint lineWidth() const; virtual void setLineWidth(uint width); QColor textColor() const; virtual void setTextColor(const QColor& color); QColor fillColor() const; virtual void setFillColor(const QColor& color); bool usesDiagramLineColor() const; void setUsesDiagramLineColor(bool state); bool usesDiagramLineWidth() const; void setUsesDiagramLineWidth(bool state); bool useFillColor(); virtual void setUseFillColor(bool state); bool usesDiagramTextColor() const; void setUsesDiagramTextColor(bool state); bool usesDiagramFillColor() const; void setUsesDiagramFillColor(bool state); bool usesDiagramUseFillColor() const; void setUsesDiagramUseFillColor(bool state); virtual QFont font() const; virtual void setFont(const QFont& font); bool autoResize(); void setAutoResize(bool state); + bool changesShape(); + void setChangesShape(bool state); + virtual bool showPropertiesDialog(); virtual bool loadFromXMI1(QDomElement &qElement); virtual void saveToXMI1(QDomDocument &qDoc, QDomElement &qElement); WidgetBase& operator=(const WidgetBase& other); QRectF rect() const; void setRect(const QRectF& rect); void setRect(qreal x, qreal y, qreal width, qreal height); virtual QRectF boundingRect() const; virtual UMLWidget* onWidget(const QPointF &p); virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0); bool isActivityWidget() { return baseType() == wt_Activity; } bool isActorWidget() { return baseType() == wt_Actor; } bool isArtifactWidget() { return baseType() == wt_Artifact; } bool isAssociationWidget() { return baseType() == wt_Association; } bool isBoxWidget() { return baseType() == wt_Box; } bool isCategoryWidget() { return baseType() == wt_Category; } bool isClassWidget() { return baseType() == wt_Class; } bool isCombinedFragmentWidget() { return baseType() == wt_CombinedFragment; } bool isComponentWidget() { return baseType() == wt_Component; } bool isDatatypeWidget() { return baseType() == wt_Datatype; } bool isEntityWidget() { return baseType() == wt_Entity; } bool isEnumWidget() { return baseType() == wt_Enum; } bool isFloatingDashLineWidget() { return baseType() == wt_FloatingDashLine; } bool isForkJoinWidget() { return baseType() == wt_ForkJoin; } bool isInterfaceWidget() { return baseType() == wt_Interface; } bool isMessageWidget() { return baseType() == wt_Message; } bool isNodeWidget() { return baseType() == wt_Node; } bool isNoteWidget() { return baseType() == wt_Note; } bool isObjectNodeWidget() { return baseType() == wt_ObjectNode; } bool isObjectWidget() { return baseType() == wt_Object; } bool isPackageWidget() { return baseType() == wt_Package; } bool isPinWidget() { return baseType() == wt_Pin; } bool isPortWidget() { return baseType() == wt_Port; } bool isPreconditionWidget() { return baseType() == wt_Precondition; } bool isRegionWidget() { return baseType() == wt_Region; } bool isSignalWidget() { return baseType() == wt_Signal; } bool isStateWidget() { return baseType() == wt_State; } bool isTextWidget() { return baseType() == wt_Text; } bool isUseCaseWidget() { return baseType() == wt_UseCase; } ActivityWidget* asActivityWidget(); ActorWidget* asActorWidget(); ArtifactWidget* asArtifactWidget(); AssociationWidget* asAssociationWidget(); BoxWidget* asBoxWidget(); CategoryWidget* asCategoryWidget(); ClassifierWidget* asClassifierWidget(); CombinedFragmentWidget* asCombinedFragmentWidget(); ComponentWidget* asComponentWidget(); DatatypeWidget* asDatatypeWidget(); EntityWidget* asEntityWidget(); EnumWidget* asEnumWidget(); FloatingDashLineWidget* asFloatingDashLineWidget(); ForkJoinWidget* asForkJoinWidget(); //InterfaceWidget* asInterfaceWidget(); MessageWidget* asMessageWidget(); NodeWidget* asNodeWidget(); NoteWidget* asNoteWidget(); ObjectNodeWidget* asObjectNodeWidget(); ObjectWidget* asObjectWidget(); PackageWidget* asPackageWidget(); PinWidget* asPinWidget(); PinPortBase* asPinPortBase(); PortWidget* asPortWidget(); PreconditionWidget* asPreconditionWidget(); RegionWidget* asRegionWidget(); SignalWidget* asSignalWidget(); StateWidget* asStateWidget(); FloatingTextWidget* asFloatingTextWidget(); // TextWidget* asTextWidget(); UseCaseWidget* asUseCaseWidget(); UMLWidget* asUMLWidget(); public Q_SLOTS: virtual void slotMenuSelection(QAction *trigger); protected: virtual void contextMenuEvent(QGraphicsSceneContextMenuEvent *event); private: WidgetType m_baseType; ///< Type of widget. protected: UMLScene *m_scene; QPointer m_umlObject; QString m_Doc; ///< Only used if m_umlObject is not set. QString m_Text; QRectF m_rect; ///< widget size /** * This ID is only used when the widget does not have a * corresponding UMLObject (i.e. the m_umlObject pointer is NULL.) * For UMLObjects, the ID from the UMLObject is used. */ Uml::ID::Type m_nId; QColor m_textColor; ///< Color of the text of the widget. Is saved to XMI. QColor m_lineColor; ///< Color of the lines of the widget. Is saved to XMI. QColor m_fillColor; ///< color of the background of the widget QBrush m_brush; QFont m_font; uint m_lineWidth; ///< Width of the lines of the widget. Is saved to XMI. bool m_useFillColor; ///< flag indicates if the UMLWidget uses the Diagram FillColour /** * true by default, false if the colors have * been explicitly set for this widget. * These are saved to XMI. */ bool m_usesDiagramFillColor; bool m_usesDiagramLineColor; bool m_usesDiagramLineWidth; bool m_usesDiagramTextColor; bool m_usesDiagramUseFillColor; bool m_autoResize; + bool m_changesShape; ///< The widget changes its shape when the number of connections or their positions are changed }; #endif