diff --git a/umbrello/umlwidgets/childwidgetplacement.h b/umbrello/umlwidgets/childwidgetplacement.h index 32483a7fc..84d8bea66 100644 --- a/umbrello/umlwidgets/childwidgetplacement.h +++ b/umbrello/umlwidgets/childwidgetplacement.h @@ -1,65 +1,65 @@ /*************************************************************************** * 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) 2016 * * Umbrello UML Modeller Authors * ***************************************************************************/ #ifndef CHILDWIDGETPLACEMENT_H #define CHILDWIDGETPLACEMENT_H class PinPortBase; class UMLWidget; #include #include /** * Base class for placing child widgets relative to their parent. Child widget positions * shall somehow be constrained, e.g. always in contact to the parent widgets border. * * @short Base class for child widget placement. */ class ChildWidgetPlacement { public: ChildWidgetPlacement(PinPortBase* widget); virtual ~ChildWidgetPlacement() {} virtual void detectConnectedSide() = 0; - virtual void setInitialPosition() = 0; + virtual void setInitialPosition(const QPointF &scenePos = QPointF()) = 0; virtual void setNewPositionWhenMoved(qreal diffX, qreal diffY) = 0; virtual void setNewPositionOnParentResize() = 0; protected: enum ConnectedSide { Undefined, Top, Right, Bottom, Left, TopLeft, TopRight, BottomRight, BottomLeft }; void setPos(const QPointF& pos); void setPos(qreal x, qreal y); void setX(qreal x); void setY(qreal y); qreal x() const; qreal y() const; qreal width() const; qreal height() const; UMLWidget* ownerWidget() const; PinPortBase* const m_widget; ConnectedSide m_connectedSide; }; #endif /* ! CHILDWIDGETPLACEMENT_H */ diff --git a/umbrello/umlwidgets/childwidgetplacementpin.cpp b/umbrello/umlwidgets/childwidgetplacementpin.cpp index fa09f16b0..c21a00e33 100644 --- a/umbrello/umlwidgets/childwidgetplacementpin.cpp +++ b/umbrello/umlwidgets/childwidgetplacementpin.cpp @@ -1,153 +1,154 @@ /*************************************************************************** * 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) 2016 * * Umbrello UML Modeller Authors * ***************************************************************************/ #include "umlwidgets/childwidgetplacementpin.h" #include "umlwidgets/umlwidget.h" ChildWidgetPlacementPin::ChildWidgetPlacementPin(PinPortBase* widget) : ChildWidgetPlacement(widget) { } ChildWidgetPlacementPin::~ChildWidgetPlacementPin() { } void ChildWidgetPlacementPin::detectConnectedSide() { } -void ChildWidgetPlacementPin::setInitialPosition() +void ChildWidgetPlacementPin::setInitialPosition(const QPointF &scenePos) { + Q_UNUSED(scenePos); m_connectedSide = Top; setPos(0, - height() ); // place above parent } void ChildWidgetPlacementPin::setNewPositionWhenMoved(qreal diffX, qreal diffY) { const qreal newX = x() + diffX; const qreal newY = y() + diffY; UMLWidget* owner = ownerWidget(); if (isAboveParent() || isBelowParent()) { if (newX < 0.0) { if (- diffX > width()) { jumpToLeftOfParent(); } else { setX(0); } } else if (newX > owner->width() - width()) { if (diffX > width()) { jumpToRightOfParent(); } else { setX(owner->width() - width()); } } else { setX(newX); } } else if (isLeftOfParent() || isRightOfParent()) { if (newY < 0.0) { if (- diffY > height()) { jumpToTopOfParent(); } else { setY(0); } } else if (newY > owner->height() - height()) { if (diffY > height()) { jumpToBottomOfParent(); } else { setY(owner->height() - height()); } } else { setY(newY); } } else { // error: client is not attached to parent jumpToTopOfParent(); } } void ChildWidgetPlacementPin::setNewPositionOnParentResize() { UMLWidget* owner = ownerWidget(); if (isRightOfParent()) { setPos(owner->width(), qMin(y(), owner->height() - height())); } else if (isBelowParent()) { setPos(qMin(x(), owner->width() - width()), owner->height()); } } bool ChildWidgetPlacementPin::isAboveParent() const { return m_connectedSide == Top; } bool ChildWidgetPlacementPin::isBelowParent() const { return m_connectedSide == Bottom; } bool ChildWidgetPlacementPin::isLeftOfParent() const { return m_connectedSide == Left; } bool ChildWidgetPlacementPin::isRightOfParent() const { return m_connectedSide == Right; } qreal ChildWidgetPlacementPin::getNewXOnJumpToTopOrBottom() const { return isLeftOfParent() ? 0 : ownerWidget()->width() - width(); } void ChildWidgetPlacementPin::jumpToTopOfParent() { setPos(QPointF(getNewXOnJumpToTopOrBottom(), - height())); m_connectedSide = Top; } void ChildWidgetPlacementPin::jumpToBottomOfParent() { setPos(QPointF(getNewXOnJumpToTopOrBottom(), ownerWidget()->height())); m_connectedSide = Bottom; } qreal ChildWidgetPlacementPin::getNewYOnJumpToSide() const { return isAboveParent() ? 0 : ownerWidget()->height() - height(); } void ChildWidgetPlacementPin::jumpToLeftOfParent() { setPos(QPointF(-width(), getNewYOnJumpToSide())); m_connectedSide = Left; } void ChildWidgetPlacementPin::jumpToRightOfParent() { setPos(QPointF(ownerWidget()->width(), getNewYOnJumpToSide())); m_connectedSide = Right; } diff --git a/umbrello/umlwidgets/childwidgetplacementpin.h b/umbrello/umlwidgets/childwidgetplacementpin.h index 2b96fef2a..219e96608 100644 --- a/umbrello/umlwidgets/childwidgetplacementpin.h +++ b/umbrello/umlwidgets/childwidgetplacementpin.h @@ -1,48 +1,48 @@ /*************************************************************************** * 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) 2016 * * Umbrello UML Modeller Authors * ***************************************************************************/ #ifndef CHILDWIDGETPLACEMENTPIN_H #define CHILDWIDGETPLACEMENTPIN_H #include "umlwidgets/childwidgetplacement.h" /** * Implementation of child widget placement where parent and child widget borders are * always connected. * * @short Child widget placement attached to parent */ class ChildWidgetPlacementPin : public ChildWidgetPlacement { public: ChildWidgetPlacementPin(PinPortBase* widget); virtual ~ChildWidgetPlacementPin(); virtual void detectConnectedSide(); - virtual void setInitialPosition(); + virtual void setInitialPosition(const QPointF &scenePos = QPointF()); virtual void setNewPositionWhenMoved(qreal diffX, qreal diffY); virtual void setNewPositionOnParentResize(); private: bool isAboveParent() const; bool isBelowParent() const; bool isLeftOfParent() const; bool isRightOfParent() const; qreal getNewXOnJumpToTopOrBottom() const; void jumpToTopOfParent(); void jumpToBottomOfParent(); qreal getNewYOnJumpToSide() const; void jumpToLeftOfParent(); void jumpToRightOfParent(); }; #endif /* ! CHILDWIDGETPLACEMENTPIN_H */ diff --git a/umbrello/umlwidgets/childwidgetplacementport.cpp b/umbrello/umlwidgets/childwidgetplacementport.cpp index d9bea01d8..ad917a05a 100644 --- a/umbrello/umlwidgets/childwidgetplacementport.cpp +++ b/umbrello/umlwidgets/childwidgetplacementport.cpp @@ -1,271 +1,287 @@ /*************************************************************************** * 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) 2016 * * Umbrello UML Modeller Authors * ***************************************************************************/ #include "umlwidgets/childwidgetplacementport.h" #include "umlwidgets/umlwidget.h" #include "pinportbase.h" ChildWidgetPlacementPort::ChildWidgetPlacementPort(PinPortBase* widget) : ChildWidgetPlacement(widget) { } ChildWidgetPlacementPort::~ChildWidgetPlacementPort() { } -void ChildWidgetPlacementPort::setInitialPosition() +void ChildWidgetPlacementPort::setInitialPosition(const QPointF &scenePos) { - m_connectedSide = TopLeft; - setPos(minX(), minY()); +#if 0 + QPointF p = ownerWidget()->mapFromScene(scenePos); + p -= QPointF(width()/2, height()/2); + setPos(p); +#else + if (ownerWidget()) { + QPointF p = ownerWidget()->mapFromScene(scenePos); + p -= QPointF(width()/2, height()/2); + setPos(p); + + detectConnectedSide(); + setNewPositionWhenMoved(0.0, 0.0); + //setNewPositionOnParentResize(); + } else { + m_connectedSide = TopLeft; + setPos(minX(), minY()); + } +#endif } void ChildWidgetPlacementPort::setNewPositionWhenMoved(qreal diffX, qreal diffY) { bool setXToMin = false, setXToMax = false; qreal newX = trimToRange(x() + diffX, minX(), maxX(), setXToMin, setXToMax); bool setYToMin = false, setYToMax = false; qreal newY = trimToRange(y() + diffY, minY(), maxY(), setYToMin, setYToMax); switch (m_connectedSide) { case Top: { if (setXToMin) { m_connectedSide = TopLeft; } else if (setXToMax) { m_connectedSide = TopRight; } else { newY = minY(); } } break; case Bottom: { if (setXToMin) { m_connectedSide = BottomLeft; } else if (setXToMax) { m_connectedSide = BottomRight; } else { newY = maxY(); } } break; case Left: { if (setYToMin) { m_connectedSide = TopLeft; } else if (setYToMax) { m_connectedSide = BottomLeft; } else { newX = minX(); } } break; case Right: { if (setYToMin) { m_connectedSide = TopRight; } else if (setYToMax) { m_connectedSide = BottomRight; } else { newX = maxX(); } } break; case TopLeft: { if (newX > minX()) { m_connectedSide = Top; newY = minY(); } else if (newY > minY()) { m_connectedSide = Left; newX = minX(); } } break; case TopRight: { if (newX < maxX()) { m_connectedSide = Top; newY = minY(); } else if (newY > minY()) { m_connectedSide = Right; newX = maxX(); } } break; case BottomRight: { if (newX < maxX()) { m_connectedSide = Bottom; newY = maxY(); } else if (newY < maxY()) { m_connectedSide = Right; newX = maxX(); } } break; case BottomLeft: { if (newX > minX()) { m_connectedSide = Bottom; newY = maxY(); } else if (newY < maxY()) { m_connectedSide = Left; newX = minX(); } } break; default: break; } setPos(newX, newY); } void ChildWidgetPlacementPort::detectConnectedSide() { if (m_widget->x() < 0) { if (m_widget->y() < 0) m_connectedSide = TopLeft; else if (m_widget->y() < maxY()) m_connectedSide = Left; else m_connectedSide =BottomLeft; } else if (m_widget->x() < maxX()) { if (m_widget->y() < 0) m_connectedSide = Top; else if (m_widget->y() < maxY()) m_connectedSide = Undefined; else m_connectedSide = Bottom; } else if (m_widget->x() >= maxX()) { if (m_widget->y() < 0) m_connectedSide = TopRight; else if (m_widget->y() < maxY()) m_connectedSide = Right; else m_connectedSide =BottomRight; } else { m_connectedSide = TopLeft; } } void ChildWidgetPlacementPort::setNewPositionOnParentResize() { switch (m_connectedSide) { case Right: { setPos(maxX(), qMin(y(), maxY())); } break; case Bottom: { setPos(qMin(x(), maxX()), maxY()); } break; case TopRight: { setPos(maxX(), minY()); } break; case BottomRight: { setPos(maxX(), maxY()); } break; case BottomLeft: { setPos(minX(), maxY()); } break; default: ; // nothing to do } } /** * Returns value bound between min and max, and flags whether value has been set. */ qreal ChildWidgetPlacementPort::trimToRange(qreal value, qreal min, qreal max, bool& setToMin, bool& setToMax) const { if (value < min) { setToMin = true; return min; } else if (value > max) { setToMax = true; return max; } return value; } /** * Returns minimum allowed x value. */ qreal ChildWidgetPlacementPort::minX() const { return - width() / 2; } /** * Returns maximum allowed x value. */ qreal ChildWidgetPlacementPort::maxX() const { UMLWidget* owner = ownerWidget(); return owner->width() - width() / 2; } /** * Returns minimum allowed y value. */ qreal ChildWidgetPlacementPort::minY() const { return - height() / 2; } /** * Returns maximum allowed y value. */ qreal ChildWidgetPlacementPort::maxY() const { UMLWidget* owner = ownerWidget(); return owner->height() - height() / 2; } diff --git a/umbrello/umlwidgets/childwidgetplacementport.h b/umbrello/umlwidgets/childwidgetplacementport.h index 525d8548f..8e64a4034 100644 --- a/umbrello/umlwidgets/childwidgetplacementport.h +++ b/umbrello/umlwidgets/childwidgetplacementport.h @@ -1,41 +1,41 @@ /*************************************************************************** * 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) 2016 * * Umbrello UML Modeller Authors * ***************************************************************************/ #ifndef CHILDWIDGETPLACEMENTPORT_H #define CHILDWIDGETPLACEMENTPORT_H #include "umlwidgets/childwidgetplacement.h" /** * Implementation of child widget placement where child widget is placed onto parent border. * * @short Child widget placement on parent border */ class ChildWidgetPlacementPort : public ChildWidgetPlacement { public: ChildWidgetPlacementPort(PinPortBase* widget); virtual ~ChildWidgetPlacementPort(); virtual void detectConnectedSide(); - virtual void setInitialPosition(); + virtual void setInitialPosition(const QPointF &scenePos = QPointF()); virtual void setNewPositionWhenMoved(qreal diffX, qreal diffY); virtual void setNewPositionOnParentResize(); private: qreal trimToRange(qreal value, qreal min, qreal max, bool& setToMin, bool& setToMax) const; qreal minX() const; qreal maxX() const; qreal minY() const; qreal maxY() const; }; #endif /* ! CHILDWIDGETPLACEMENTPORT_H */ diff --git a/umbrello/umlwidgets/pinportbase.cpp b/umbrello/umlwidgets/pinportbase.cpp index f9aa08a59..a72e5f9e7 100644 --- a/umbrello/umlwidgets/pinportbase.cpp +++ b/umbrello/umlwidgets/pinportbase.cpp @@ -1,324 +1,329 @@ /*************************************************************************** * 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) 2014 * * Umbrello UML Modeller Authors * ***************************************************************************/ // own header #include "pinportbase.h" // app includes #include "port.h" #include "package.h" #include "debug_utils.h" #include "listpopupmenu.h" #include "umldoc.h" #include "umlscene.h" #include "umlview.h" #include "floatingtextwidget.h" #include "umlwidgets/childwidgetplacementpin.h" #include "umlwidgets/childwidgetplacementport.h" // qt includes #include #include // sys includes #include PinPortBase::PinPortBase(UMLScene *scene, WidgetType type, UMLWidget *owner, UMLObject *o) : UMLWidget(scene, type, o), m_childPlacement(createPlacement(type)) { init(owner); } PinPortBase::PinPortBase(UMLScene *scene, WidgetType type, UMLWidget *owner, Uml::ID::Type id) : UMLWidget(scene, type, id), m_childPlacement(createPlacement(type)) { init(owner); } /** * Standard destructor. */ PinPortBase::~PinPortBase() { } ChildWidgetPlacement* PinPortBase::createPlacement(WidgetBase::WidgetType type) { if (type == wt_Pin) { return new ChildWidgetPlacementPin(this); } else if (type == wt_Port) { return new ChildWidgetPlacementPort(this); } else { return 0; } } /** * Performs initializations which are common to PinWidget and PortWidget. */ void PinPortBase::init(UMLWidget *owner) { m_ignoreSnapToGrid = true; m_ignoreSnapComponentSizeToGrid = true; m_resizable = false; setParentItem(owner); m_pName = 0; const int edgeLength = 15; // old: (m_baseType == wt_Pin ? 10 : 15); const QSizeF fixedSize(edgeLength, edgeLength); setMinimumSize(fixedSize); setMaximumSize(fixedSize); setSize(fixedSize); - m_childPlacement->setInitialPosition(); + //m_childPlacement->setInitialPosition(); } UMLWidget* PinPortBase::ownerWidget() const { return dynamic_cast(parentItem()); } +void PinPortBase::setInitialPosition(const QPointF &scenePos) +{ + m_childPlacement->setInitialPosition(scenePos); +} + /** * Overrides method from UMLWidget in order to set a tooltip. * The tooltip is set to the name(). * The reason for using a tooltip for the name is that the size of this * widget is not large enough to accommodate the average name. */ void PinPortBase::updateWidget() { QString strName = name(); uDebug() << " port name is " << strName; if (m_pName) { m_pName->setText(strName); } else { setToolTip(strName); } } /** * Overrides method from UMLWidget to set the name. */ void PinPortBase::setName(const QString &strName) { UMLWidget::setName(strName); updateGeometry(); if (m_pName) { m_pName->setText(strName); } } /** * Overridden from UMLWidget. * Moves the widget to a new position using the difference between the * current position and the new position. * Movement is constrained such that the port is always attached to its * owner widget. * * @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 PinPortBase::moveWidgetBy(qreal diffX, qreal diffY) { m_childPlacement->setNewPositionWhenMoved(diffX, diffY); } /** * Receive notification when parent is resized. * We need to track parent resize to always stay attached to it. */ void PinPortBase::notifyParentResize() { m_childPlacement->setNewPositionOnParentResize(); } /** * Overrides standard method. */ void PinPortBase::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { setPenFromSettings(painter); if (UMLWidget::useFillColor()) { painter->setBrush(UMLWidget::fillColor()); } else { painter->setBrush(m_scene->backgroundColor()); } painter->drawRect(0, 0, width(), height()); UMLWidget::paint(painter, option, widget); } QRectF PinPortBase::boundingRect() const { return QRectF(0, 0, width(), height()); } /** * Captures any popup menu signals for menus it created. */ void PinPortBase::slotMenuSelection(QAction* action) { uDebug() << "PinPortBase::slotMenuSelection"; ListPopupMenu::MenuType sel = ListPopupMenu::typeFromAction(action); switch(sel) { case ListPopupMenu::mt_NameAsTooltip: if (m_pName) { action->setChecked(true); delete m_pName; m_pName = 0; setToolTip(name()); } else { action->setChecked(false); m_pName = new FloatingTextWidget(m_scene, Uml::TextRole::Floating, name()); m_pName->setParentItem(this); m_pName->setText(name()); // to get geometry update m_pName->activate(); UMLWidget* owner = ownerWidget(); if (owner == 0) { uError() << "PinPortBase::slotMenuSelection: ownerWidget() returns NULL"; setX(x()); setY(y()); } else { const qreal w = width(); const qreal h = height(); if (x() < owner->x()) m_pName->setX(-m_pName->width()); else if (x() >= owner->x() + owner->width()) m_pName->setX(w); else m_pName->setX(-m_pName->width() / 2.0 + w / 2.0); if (y() < owner->y()) m_pName->setY(-m_pName->height() - 2); else if (y() >= owner->y() + owner->height()) m_pName->setY(h); else m_pName->setY(-m_pName->height() / 2.0 + h / 2.0); } m_pName->update(); setToolTip(QString()); QToolTip::hideText(); } break; default: UMLWidget::slotMenuSelection(action); } } FloatingTextWidget *PinPortBase::floatingTextWidget() { return m_pName; } void PinPortBase::setFloatingTextWidget(FloatingTextWidget *ft) { m_pName = ft; if (m_pName) m_pName->setParentItem(this); } /** * Override method from UMLWidget in order to additionally check m_pName. * * @param p Point to be checked. * * @return 'this' if UMLWidget::onWidget(p) returns non 0; * m_pName if m_pName is non NULL and m_pName->onWidget(p) returns non 0; * else NULL. */ UMLWidget* PinPortBase::onWidget(const QPointF &p) { if (UMLWidget::onWidget(p) != 0) return this; if (m_pName) { uDebug() << "floatingtext: " << m_pName->text(); return m_pName->onWidget(p); } return 0; } /** * Reimplement function from UMLWidget */ UMLWidget* PinPortBase::widgetWithID(Uml::ID::Type id) { if (UMLWidget::widgetWithID(id)) return this; if (m_pName && m_pName->widgetWithID(id)) return m_pName; return 0; } /** * Saves the widget to the "pinwidget" or "portwidget" XMI element. */ void PinPortBase::saveToXMI1(QDomDocument& qDoc, QDomElement& qElement) { QDomElement element = qDoc.createElement(baseType() == wt_Pin ? QLatin1String("pinwidget") : QLatin1String("portwidget")); Q_ASSERT(ownerWidget() != 0); element.setAttribute(QLatin1String("widgetaid"), Uml::ID::toString(ownerWidget()->id())); UMLWidget::saveToXMI1(qDoc, element); if (m_pName && !m_pName->text().isEmpty()) { m_pName->saveToXMI1(qDoc, element); } qElement.appendChild(element); } /** * Loads from a "pinwidget" or from a "portwidget" XMI element. */ bool PinPortBase::loadFromXMI1(QDomElement & qElement) { if (!UMLWidget::loadFromXMI1(qElement)) return false; QString widgetaid = qElement.attribute(QLatin1String("widgetaid"), QLatin1String("-1")); Uml::ID::Type aId = Uml::ID::fromString(widgetaid); UMLWidget *owner = m_scene->findWidget(aId); if (owner == 0) { DEBUG(DBG_SRC) << "owner object " << Uml::ID::toString(aId) << " not found"; return false; } setParentItem(owner); // Optional child element: floatingtext QDomNode node = qElement.firstChild(); QDomElement element = node.toElement(); if (!element.isNull()) { QString tag = element.tagName(); if (tag == QLatin1String("floatingtext")) { m_pName = new FloatingTextWidget(m_scene, Uml::TextRole::Floating, name(), Uml::ID::Reserved); if (!m_pName->loadFromXMI1(element)) { // Most likely cause: The FloatingTextWidget is empty. delete m_pName; m_pName = 0; } else { m_pName->setParentItem(this); m_pName->activate(); m_pName->update(); } } else { uError() << "unknown tag " << tag; } } return true; } bool PinPortBase::activate(IDChangeLog* changeLog) { Q_UNUSED(changeLog); m_childPlacement->detectConnectedSide(); return true; } diff --git a/umbrello/umlwidgets/pinportbase.h b/umbrello/umlwidgets/pinportbase.h index a8a572b40..2fccd3608 100644 --- a/umbrello/umlwidgets/pinportbase.h +++ b/umbrello/umlwidgets/pinportbase.h @@ -1,71 +1,73 @@ /*************************************************************************** * 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) 2014 * * Umbrello UML Modeller Authors * ***************************************************************************/ #ifndef PINPORTBASE_H #define PINPORTBASE_H #include "umlwidget.h" #include class FloatingTextWidget; class ChildWidgetPlacement; /** * @short Abstract base class for PinWidget and PortWidget * @author Oliver Kellogg * @see UMLWidget * Bugs and comments to umbrello-devel@kde.org or http://bugs.kde.org */ class PinPortBase : public UMLWidget { Q_OBJECT public: PinPortBase(UMLScene *scene, WidgetType type, UMLWidget *owner, UMLObject *o); PinPortBase(UMLScene *scene, WidgetType type, UMLWidget *owner = 0, Uml::ID::Type id = Uml::ID::None); virtual ~PinPortBase(); virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0); virtual QRectF boundingRect() const; virtual UMLWidget* ownerWidget() const; + void setInitialPosition(const QPointF &scenePos); + void updateWidget(); void setName(const QString &strName); void moveWidgetBy(qreal diffX, qreal diffY); virtual void notifyParentResize(); UMLWidget* onWidget(const QPointF& p); UMLWidget* widgetWithID(Uml::ID::Type id); FloatingTextWidget *floatingTextWidget(); void setFloatingTextWidget(FloatingTextWidget *ft); void saveToXMI1(QDomDocument& qDoc, QDomElement& qElement); bool loadFromXMI1(QDomElement& qElement); public slots: virtual void slotMenuSelection(QAction* action); protected: void init(UMLWidget *owner = 0); bool activate(IDChangeLog* changeLog = 0); private: ChildWidgetPlacement* createPlacement(WidgetBase::WidgetType type); protected: FloatingTextWidget *m_pName; private: QScopedPointer m_childPlacement; }; #endif diff --git a/umbrello/umlwidgets/toolbarstateonewidget.cpp b/umbrello/umlwidgets/toolbarstateonewidget.cpp index 1beb52569..0f59d1285 100644 --- a/umbrello/umlwidgets/toolbarstateonewidget.cpp +++ b/umbrello/umlwidgets/toolbarstateonewidget.cpp @@ -1,240 +1,242 @@ /*************************************************************************** * 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 "toolbarstateonewidget.h" // app includes #include "activitywidget.h" #include "dialog_utils.h" #include "floatingtextwidget.h" #include "messagewidget.h" #include "model_utils.h" #include "objectwidget.h" #include "pinwidget.h" #include "portwidget.h" #include "preconditionwidget.h" #include "regionwidget.h" #include "uml.h" #include "umldoc.h" #include "port.h" #include "umlscene.h" #include "umlwidget.h" #include "object_factory.h" #include "package.h" #include "widget_factory.h" #include "widget_utils.h" // kde includes #include #include // qt includes // using namespace Uml; /** * Creates a new ToolBarStateOneWidget. * * @param umlScene The UMLScene to use. */ ToolBarStateOneWidget::ToolBarStateOneWidget(UMLScene *umlScene) : ToolBarStatePool(umlScene), m_firstObject(0), m_isObjectWidgetLine(false) { } /** * Destroys this ToolBarStateOneWidget. */ ToolBarStateOneWidget::~ToolBarStateOneWidget() { } /** * Called when the current tool is changed to use another tool. * Executes base method and cleans the message. */ void ToolBarStateOneWidget::cleanBeforeChange() { ToolBarStatePool::cleanBeforeChange(); } /** * Called when a mouse event happened. * It executes the base method and then updates the position of the * message line, if any. */ void ToolBarStateOneWidget::mouseMove(QGraphicsSceneMouseEvent* ome) { ToolBarStatePool::mouseMove(ome); } /** * A widget was removed from the UMLView. * If the widget removed was the current widget, the current widget is set * to 0. * Also, if it was the first object, the message is cleaned. */ void ToolBarStateOneWidget::slotWidgetRemoved(UMLWidget* widget) { ToolBarState::slotWidgetRemoved(widget); } /** * Selects only widgets, but no associations. * Overrides base class method. * If the press event happened on the line of an object, the object is set * as current widget. If the press event happened on a widget, the widget is * set as current widget. */ void ToolBarStateOneWidget::setCurrentElement() { m_isObjectWidgetLine = false; ObjectWidget* objectWidgetLine = m_pUMLScene->onWidgetLine(m_pMouseEvent->scenePos()); if (objectWidgetLine) { setCurrentWidget(objectWidgetLine); m_isObjectWidgetLine = true; return; } UMLWidget *widget = m_pUMLScene->widgetAt(m_pMouseEvent->scenePos()); if (widget) { setCurrentWidget(widget); return; } } /** * Called when the release event happened on a widget. * If the button pressed isn't left button or the widget isn't an object * widget, the message is cleaned. * If the release event didn't happen on the line of an object and the first * object wasn't selected, nothing is done. If the first object was already * selected, a creation message is made. * If the event happened on the line of an object, the first object or the * second are set, depending on whether the first object was already set or * not. */ void ToolBarStateOneWidget::mouseReleaseWidget() { WidgetBase::WidgetType type = widgetType(); if (type == WidgetBase::wt_Precondition) { m_firstObject = 0; } if (type == WidgetBase::wt_Pin || type == WidgetBase::wt_Port) { m_firstObject = 0; } if (m_pMouseEvent->button() != Qt::LeftButton || (!currentWidget()->isObjectWidget() && !currentWidget()->isActivityWidget() && !currentWidget()->isComponentWidget() && !currentWidget()->isRegionWidget())) { return; } if (!m_firstObject && (type == WidgetBase::wt_Pin || type == WidgetBase::wt_Port)) { setWidget(currentWidget()); return ; } if (!m_isObjectWidgetLine && !m_firstObject) { return; } if (!m_firstObject) { setWidget(currentWidget()); } } /** * Called when the release event happened on an empty space. * Cleans the message. * Empty spaces are not only actual empty spaces, but also associations. */ void ToolBarStateOneWidget::mouseReleaseEmpty() { } /** * Sets the first object of the message using the specified object. * The temporal visual message is created and mouse tracking enabled, so * mouse events will be delivered. * * @param firstObject The first object of the message. */ void ToolBarStateOneWidget::setWidget(UMLWidget* firstObject) { m_firstObject = firstObject; UMLWidget * umlwidget = 0; //m_pUMLScene->viewport()->setMouseTracking(true); if (widgetType() == WidgetBase::wt_Precondition) { umlwidget = new PreconditionWidget(m_pUMLScene, static_cast(m_firstObject)); Dialog_Utils::askNameForWidget(umlwidget, i18n("Enter Precondition Name"), i18n("Enter the precondition"), i18n("new precondition")); // Create the widget. Some setup functions can remove the widget. } if (widgetType() == WidgetBase::wt_Pin && m_firstObject->isActivityWidget()) { QString name = i18n("new pin"); if (Dialog_Utils::askName(i18n("Enter Pin Name"), i18n("Enter the Pin"), name)) { umlwidget = new PinWidget(m_pUMLScene, m_firstObject); } } else if (widgetType() == WidgetBase::wt_Port && m_firstObject->isComponentWidget()) { UMLPackage* component = m_firstObject->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 = new PortWidget(m_pUMLScene, port, m_firstObject); + PortWidget *widget = new PortWidget(m_pUMLScene, port, m_firstObject); + widget->setInitialPosition(m_pUMLScene->pos()); + umlwidget = widget; } } if (umlwidget) { m_pUMLScene->setupNewWidget(umlwidget); } emit finished(); } /** * Returns the widget type of this tool. * * @return The widget type of this tool. */ WidgetBase::WidgetType ToolBarStateOneWidget::widgetType() { if (getButton() == WorkToolBar::tbb_Seq_Precondition) { return WidgetBase::wt_Precondition; } if (getButton() == WorkToolBar::tbb_Pin) { return WidgetBase::wt_Pin; } if (getButton() == WorkToolBar::tbb_Port) { return WidgetBase::wt_Port; } // Shouldn't happen Q_ASSERT(0); return WidgetBase::wt_Pin; } /** * Goes back to the initial state. */ void ToolBarStateOneWidget::init() { ToolBarStatePool::init(); } diff --git a/umbrello/umlwidgets/umlwidget.cpp b/umbrello/umlwidgets/umlwidget.cpp index faf3d0fac..56ee7a7bc 100644 --- a/umbrello/umlwidgets/umlwidget.cpp +++ b/umbrello/umlwidgets/umlwidget.cpp @@ -1,2074 +1,2077 @@ /*************************************************************************** * 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 "floatingtextwidget.h" #include "notewidget.h" #include "messagewidget.h" #include "object_factory.h" #include "idchangelog.h" #include "menus/listpopupmenu.h" #include "port.h" #include "portwidget.h" #include "settingsdialog.h" #include "statewidget.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); umlWidget->setParentItem(this); - umlScene()->setupNewWidget(umlWidget); + QPointF p = mapFromScene(umlScene()->pos()); + umlWidget->setPos(p); + umlScene()->setupNewWidget(umlWidget, false); + } break; } case ListPopupMenu::mt_UseCase: { UMLUseCase *useCase = new UMLUseCase; UMLWidget *widget = new UseCaseWidget(umlScene(), useCase); addConnectedWidget(widget, Uml::AssociationType::Association); break; } case ListPopupMenu::mt_MessageSynchronous: // MessageWidget *widget = new MessageWidget(umlScene(), this); // addConnectedWidget(widget, Uml::AssociationType::Coll_Message_Synchronous); case ListPopupMenu::mt_MessageAsynchronous: case ListPopupMenu::mt_MessageFound: case ListPopupMenu::mt_MessageLost: break; // state diagrams case ListPopupMenu::mt_Choice: addConnectedWidget(new StateWidget(umlScene(), StateWidget::Choice), Uml::AssociationType::State, false); break; case ListPopupMenu::mt_DeepHistory: addConnectedWidget(new StateWidget(umlScene(), StateWidget::DeepHistory), Uml::AssociationType::State, false); break; case ListPopupMenu::mt_End_State: addConnectedWidget(new StateWidget(umlScene(), StateWidget::End), Uml::AssociationType::State, false); break; case ListPopupMenu::mt_Junction: addConnectedWidget(new StateWidget(umlScene(), StateWidget::Junction), Uml::AssociationType::State, false); break; case ListPopupMenu::mt_ShallowHistory: addConnectedWidget(new StateWidget(umlScene(), StateWidget::ShallowHistory), Uml::AssociationType::State, false); break; case ListPopupMenu::mt_State: addConnectedWidget(new StateWidget(umlScene(), StateWidget::Normal), Uml::AssociationType::State, false); break; case ListPopupMenu::mt_StateFork: addConnectedWidget(new StateWidget(umlScene(), StateWidget::Fork), Uml::AssociationType::State, false); break; case ListPopupMenu::mt_StateJoin: addConnectedWidget(new StateWidget(umlScene(), StateWidget::Join), Uml::AssociationType::State, false); 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); } if (changesShape()) { updateGeometry(); } } /** * 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) { qDebug() << this; // 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) { QString msgX = i18n("Hold shift or control to move in X axis."); QString msgY = i18n("Hold shift and control to move in Y axis."); QString msg; if (isMessageWidget()) msg = msgY; else if (isObjectWidget()) msg = msgX; else msg = QString(QLatin1String("%1 %2")).arg(msgX, msgY); UMLApp::app()->document()->writeToStatusBar(msg); 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. * * @param withAssocs true - update associations too */ void UMLWidget::updateGeometry(bool withAssocs) { 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(); if (withAssocs) 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 * @param setupSize if true setup size to a predefined value */ void UMLWidget::addConnectedWidget(UMLWidget *widget, Uml::AssociationType::Enum type, bool setupSize) { umlScene()->addItem(widget); widget->setX(x() + rect().width() + 100); widget->setY(y()); if (setupSize) { widget->setSize(100, 40); QSizeF size = widget->minimumSize(); widget->setSize(size); } AssociationWidget* assoc = AssociationWidget::create(umlScene(), this, type, widget); umlScene()->addAssociation(assoc); umlScene()->clearSelected(); umlScene()->selectWidget(widget); widget->showPropertiesDialog(); } /** * 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); }